您的位置:首页 > 编程语言 > Java开发

Java学习记录 Day23(注解、反射)

2019-06-09 22:52 686 查看

Day 23

2019年6月7日。
这是我学习Java的第二十三天。
这一天,我学到了以下的知识。

注解

注解(Annotation),是从JDK5.0开始引入的新技术。
注解的性质如下:

  • 注解的作用
    - 注解不是程序本身,可以对程序作出解释(这一点和注释(comment)没什么区别)
    - 可以被其他程序(比如:编译器等)读取
  • 注解的格式
    - 注解是以“@注解名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”)
  • 注解的使用范围
    - 注解可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问

内置的注解如下:

  • @Override:定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
  • @Deprecated:定义在java.lang.Deprecated中,此注解可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
  • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。与前两个注解有所不同,需要添加一个参数才能正确使用,这些参数都已经定义好的,只需要有选择性的使用就好

示例如下:

public class Test {
//Override:重写方法的注解
@Override
public String toString() {
return "Test{}";
}

//Deprecated:不推荐使用,但是可以使用
@Deprecated
public static void stop(){
System.out.println("123");
}

//SuppressWarnings:抑制警告信息的注解
@SuppressWarnings("all")
public  void test(){

}

public static void main(String[] args) {
stop();
}
}

元注解,就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,它们被用来提供对其他annotation类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inheriter),它们的说明如下:

  • @Target:用于描述注解的使用范围(即被描述的注解可以用在什么地方,例如ElementType.METHOD为作用在方法上,ElementType.TYPE为运用在类上)
  • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME)
  • @Document:说明该注解被包含在javadoc中
  • @Inherited:说明子类可以继承父类中的该注解

示例如下:

//测试元注解
@MyAnnotation
public class Test2 {
}

@Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域
@Retention(RetentionPolicy.RUNTIME) //运行时级别
@Documented //生成Doc文档时候使用
@Inherited //子类可以继承父类的注解
@interface  MyAnnotation{

}

自定义注解,就是根据自身的需要,自己定义出来的注解。使用关键字@interface自定义注解时,便自动继承了java.lang.annotation.Annotation接口。自定义注解的说明如下:

  • @interface用来声明一个注解,格式:public @interface 注解名{ 定义内容 }
  • 其中的每一个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型(返回值只能是基本类型:Class,String,enum)
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值

示例如下:

//测试自定义注解
@SuppressWarnings("all")
public class Test3 {

@MyAnnotation2(name = "123",age = 3,id = 001,schools = "aa")
public void test(){
}

//注解只有一个参数的时候,默认使用value当做参数名字,使用的时候可以省略参数名
@MyAnnotation3("a")
public void test2(){

}
}

@Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域
@Retention(RetentionPolicy.RUNTIME) //运行时级别
@interface MyAnnotation2{
//参数类型 参数名
String name() default "";
int age() default  0;
// -1 代表不存在,类似于Indexof的返回值
int id() default -1;
String[] schools() default {"a","b"};
}

@Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域
@Retention(RetentionPolicy.RUNTIME) //运行时级别
@interface MyAnnotation3{
String value();
}

反射

反射(reflection),是Java被视为动态语言的关键,反射机制允许程序在执行器借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
反射的基本格式为:

Class c = Class.forName("java.lang.String")

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构。所以,就形象的称之为:反射
反射的示意图如下:

反射机制提供的功能如下:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射机制的优缺点如下:

  • 优点
    可以实现动态创建对象和编译,体现出很大的灵活性
  • 缺点
    对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作

示例如下:

//初始反射
//Class
public class Test {
public static void main(String[] args) {
try {
//通过反射获取类的class
Class c1 = Class.forName("Test2.User");
Class c2 = Class.forName("Test2.User");
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());

//正常创建对象的方式
User user = new User();
Class c3 = user.getClass();
System.out.println(c3.hashCode());

/
4000
/总结:一个类无论创造多少个对象,只有一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}
}

//实体类
class User extends Object{

private String name;
private int id;
private int age;

public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}

public User() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

跟反射机制息息相关的类:Class类
在Object类中定义了一个方法:

public final Class getClass()
,此方法被所有的子类继承。该方法的返回值类型Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解。即:可以通过对象反射求出类的名称。
对象照镜子可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息,Class的特点如下所示:

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class所生成
  • 通过Class,可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何想要动态加载、运行的类,唯有先获得相应的Class对象
  • Class类的常用方法如下:
    -
    static ClassforName(String name)
    :返回指定类名name的Class对象
    -
    Object newInstance()
    :调用缺省构造函数,返回Class对象的一个实例
    -
    getName()
    :返回此Class对象所表示的实体(类,接口,数组类或void)的名称
    -
    Class getSuperClass()
    :返回当前Class对象的父类的Class对象
    -
    Class[] getinterfaces()
    :获取当前Class对象的接口
    -
    ClassLoader getClassLoader()
    :返回该类的类加载器
    -
    Constructor[] getConstructors()
    :返回一个包含某些Constructor对象的数组
    -
    Method getMother(String name,Class.. T)
    :返回一个Method对象,此对象的形参类型为paramType
    -
    Field[] getDeclaredFields()
    :返回Field对象的一个数组
  • 获取Class类的实例,有以下几种方式:
    - 若已知具体的类,通过类的Class属性获取,该方法最为安全可靠,且程序性能最高:
    Class clazz = Person.class;

    - 若已知某个类的实例,调用该实例的getClass()方法获取Class对象:
    Class clazz = person.getClass();

    - 若已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能会抛出ClassNotFoundException:
    Class clazz = Class.forName("demo1.Student")

    - 内置基本数据类型可以直接使用类名.Type
    - 通过ClassLoader
    - 注意:若获取了数组类的实例,在数组类型一样的情况下,同个维度,只有一个Class对象
    - 获取Class类的示例如下:
//测试如何获得class
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("这个人是:" + person);

//1.通过对象获得class
Class c1 = person.getClass();
System.out.println(c1);

//2.通过forname :
Class c2 = Class.forName("Test2.Person");
System.out.println(c2);

//3,通过类的静态成员变量class 获得
Class c3 = Person.class;
System.out.println(c3);

//4.只有默认的基本类型才有的方法
//八大基本数据类型
//byte,int,short,long,double,float,char,boolean
Class type = Integer.TYPE;
//Integer.class
System.out.println(type);

//5.获得父类的类型
Class c4 = person.getClass();
Class c5 = c4.getSuperclass();
System.out.println(c4);
System.out.println(c5);

}
}

class Person{
String name;

public Person() {
}

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}

class Student extends Person{
public Student(){
this.name = "学生";
}
}

class Teacher extends Person{
public Teacher(){
this.name = "老师";
}
}

有了Class对象之后,可以创建类的对象:即调用Class对象的newInstance()方法,调用的要求如下:

  • 该类必须要有一个无参的构造方法(只有在操作的时候明确地调用类中的构造方法,并将参数传递进去,才可以进行实例化操作)
  • 类的构造方法的访问权限需要足够

满足了调用要求后,调用步骤如下:

  1. 通过Class类的getDeclaredConstructor(Class… parameterTypes)取得本类的指定形参类型的构造方法
  2. 向构造方法的形参中传递一个对象数组进去,里面包含了构造方法中所需要的各个参数
  3. 通过Constructor实例化对象

调用结束后,通过反射,调用类中的方法,可以使用Method类完成。步骤如下:

  1. 通过Class类的getMethod(String name,Class… parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  2. 之后使用Object invoke(Object obj,Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息,该方法的性质如下:
    (1).Object对应原方法的返回值,若原方法无返回值,则返回null
    (2).若原方法为静态方法,此时形参Object obj可为null
    (3).若原方法形参为空,则Object[] args为null
    (4).若原方法声明为private,则需要在调用此invoke()方法前,显示调用方法对象的setAccessible(true),将可访问private的方法
    (5).一般来说,正常创建对象的运行时间 < 通过反射创建对象的运行时间(令setAccessible为true) < 通过反射创建对象的运行时间(令setAccessible为false)

示例图如下:

示例如下:

User类:

//实体类
class User extends Object{

private String name;
private int id;
private int age;

public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}

public User() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

示例一

//获得类的信息
@SuppressWarnings("all")
public class Test4 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {

Class c1 = Class.forName("Test2.User");
//获得名字
System.out.println(c1.getName()); //获得类的名字 : 包名 + 类名
System.out.println(c1.getSimpleName()); //获得类的名字简称
System.out.println("--------------------");
//获得属性
Field[] fields = c1.getFields(); //只能获得类的public属性
for (Field field : fields) {
System.out.println(field);
}

Field[] declaredFields = c1.getDeclaredFields(); // 获得类的所有属性
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}

Field name = c1.getDeclaredField("name");
System.out.println(name);
System.out.println("--------------------");
//获得方法
Method[] methods = c1.getMethods(); // 获得本类及其父类的所有public方法
for (Method method : methods) {
System.out.println("默认:" + method);
}
Method[] declaredMethods = c1.getDeclaredMethods();// 获得本类的所有方法
for (Method declaredMethod : declaredMethods) {
System.out.println("Declared:" + declaredMethod);
}
System.out.println("--------------------");
//重载,如果只知道方法的名字,是找不到具体的方法
Method getName = c1.getDeclaredMethod("getName",null);
Method setName = c1.getDeclaredMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
System.out.println("--------------------");
//获取构造器
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("--------------------");
//获得指定的构造器
Constructor constructor = c1.getConstructor(null); // 无参构造
System.out.println("默认:" + constructor);
constructor = c1.getConstructor(String.class,int.class,int.class);
System.out.println(constructor);
}
}

实例二

//动态构造对象,调用方法
public class Test5 {

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c = Class.forName("Test3.User");

//1.通过动态调用构造方法,构造对象
User user = (User)c.newInstance();
System.out.println(user);

//2.通过有参构造创建对象
System.out.println("--------------------------------");
Constructor<User> declaredConstructor = (Constructor<User>) c.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = declaredConstructor.newInstance("a", 007, 18);
System.out.println(user2.getName());

//3.调用普通方法
System.out.println("--------------------------------");
User user3 =  (User) c.newInstance();

//获得指定的方法
Method method = c.getDeclaredMethod("setName", String.class);

//invoke(方法执行的对象,方法参数的值)
method.invoke(user3,"a");
System.out.println(user3.getName());

//4.操作属性
User user4 = (User)c.newInstance();
Field field = c.getDeclaredField("name");

field.setAccessible(true); // 关闭权限访问检测,默认是false[打开权限访问检测]
//字段设置值(对象,值)
field.set(user4,"b");
System.out.println(user4.getName());
}
}

反射的其他性质

  • setAccessible
    - Method和Field、Construtor对象都有setAccessible()方法
    - setAccessible作用是启动和禁用访问安全检查的开关
    - 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查(此举会使得原本无法访问的私有成员也可以访问,提高反射的效率。如果代码中必须用反射,而该句代码需要频繁地被调用,请设置为true)
    - 性能测试示例如下:
//分析性能问题
public class Test6 {

//正常创建对象
public static void test1(){
User user = new User();

//获取时间
long startTime = System.currentTimeMillis();

for (int i = 0; i < 1000000000L; i++) {
user.getName();
}

long endTime = System.currentTimeMillis();

System.out.println("正常方式调用10亿次方法" + (endTime - startTime) + "毫秒");
}

//通过反射创建对象耗时
public  static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();

Class c = user.getClass();
Method method = c.getMethod("getName",null);
//获取时间
long startTime = System.currentTimeMillis();

for (int i = 0; i < 1000000000L; i++) {
method.invoke(user,null);
}

long endTime = System.currentTimeMillis();

System.out.println("反射方式调用10亿次方法" + (endTime - startTime) + "毫秒");
}

//通过反射创建对象耗时,关闭安全检测
public  static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();

Class c = user.getClass();
Method method = c.getMethod("getName",null);
method.setAccessible(true);  //提高性能
//获取时间
long startTime = System.currentTimeMillis();

for (int i = 0; i < 1000000000L; i++) {
method.invoke(user,null);
}

long endTime = System.currentTimeMillis();

System.out.println("关闭安全检测-->反射方式调用10亿次方法" + (endTime - startTime) + "毫秒");
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test1();
test2();
test3();
}
}
  • 反射操作泛型
    - Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题。但是,一旦编译完成,所有和泛型有关的类型就会全部擦除
    - 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArray,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的数据,这四种类型介绍如下:
    (1).ParameterizedType:表示一种参数化类型,比如Collection<String.>
    (2).GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
    (3).TypeVariable:是各种类型变量的公共父接口
    (4).WildcardType:代表一种通配符类型表达式
    - 示例如下:
//测试反射获取泛型
@SuppressWarnings("all")
public class Test7 {

//带有泛型参数的方法
public void test(Map<String,User> map,List<User> list){
System.out.println("test01");
}

//带有泛型返回值的方法
public Map<Integer,User> test2(){
System.out.println("test02");
return  null;
}

public static void main(String[] args) throws NoSuchMethodException {
//1.获得制定方法的泛型信息

Method method = Test7.class.getDeclaredMethod("test", Map.class, List.class);

//获得泛型参数类型信息
Type[] t = method.getGenericExceptionTypes();
for (Type type : t) {
System.out.println("#" + type);
if (type instanceof ParameterizedType){
//getActualTypeArguments 获得真实的类型参数
Type[] actualTypeArguments = ((ParameterizedType)
24000
type).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("真实的泛型类型:" + actualTypeArgument);
}
}
}

//2.获得返回值泛型信息

Method method2 = Test7.class.getDeclaredMethod("test2",null);

//获得泛型返回的信息
Type genericReturnType = method2.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("真实的返回值类型:" + actualTypeArgument);
}
}

}
}
  • 反射操作注解
    通过反射,可以获取注解内的信息。示例如下:
package Test3;

//使用反射读取注解
/*
1.定义注解
2.在类中使用注解
3.使用反射获取注解
*/
public class Test09 {
public static void main(String[] args) throws Exception {
//通过反射获取注解信息
Class c1 = Class.forName("Test3.Student2");

Annotation[] annotations = c1.getAnnotations();//获得这个类的所有注解信息
for (Annotation annotation : annotations) {
System.out.println(annotation);
}

//获得注解的值
//通过注解的 value方法 获得注解的值
Annotation annotation = c1.getAnnotation(TableKuang.class);
TableKuang annotation1 = (TableKuang) annotation;
System.out.println(annotation1.value());

Field field = c1.getDeclaredField("id");
System.out.println(field);
//获得字段的注解
FieldKuang annotation2 = field.getAnnotation(FieldKuang.class);
//获得注解的参数信息
System.out.println(annotation2.columnName());
System.out.println(annotation2.length());
System.out.println(annotation2.type());

}
}

//学生的实体类
//db : database -->数据库
@TableKuang("db_student")
class Student2{

@FieldKuang(columnName = "id",type = "int",length = 10)
private int id;
@FieldKuang(columnName = "db_age",type = "int",length = 3)
private int age;
@FieldKuang(columnName = "name",type = "varchar",length = 20)
private String name;

public Student2() {
}
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}

//表名-->类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableKuang{
String value();
}

//字段类的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldKuang{

//参数类型  参数名
String columnName();//列名
String type();//类型
int length();//长度

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: