注解和反射

注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

注解的定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation{
    String value() default "xxx";
 }

元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta�annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :Target和Retention,另外还有@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类继承父类中定义的注解。

@Target

注解标记另一个注解,以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值:
ElementType.ANNOTATION_TYPE 可以应用于注解类型。
ElementType.CONSTRUCTOR 可以应用于构造函数。
ElementType.FIELD 可以应用于字段或属性。
ElementType.LOCAL_VARIABLE 可以应用于局部变量。
ElementType.METHOD 可以应用于方法级注解。
ElementType.PACKAGE 可以应用于包声明。
ElementType.PARAMETER 可以应用于方法的参数。
ElementType.TYPE 可以应用于类的任何元素。

@Retention

注解制定标记注解的储存方式:
RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。

@IntDef

IDE语法检查
在Android开发中, support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义如
下:

@WeekDay
private int currentDay;
private static final int SUNDAY = 0;
private static final int MONDAY= 1;


@IntDef({SUNDAY,MONDAY})
@Target(ElementType.FIELD,ElementType.PARAMETER)
@interface WeekDay{

}
public static void setCurrentDay(@WeekDay int currentDay){
  this.currentDay = currentDay;
}
//android中还有个用于@DrawableRes用于资源的语法检查

Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。比常量多5到10倍的内存占用。此注解的意义在于能够取代枚举,实现如方法入参限制。

@Documented

用于被javadoc工具提取成文档。

@Inherited

后者表示允许子类继承父类中定义的注解。

注解的应用场景

@Retention(RetentionPolicy.SOURCE)

技术:APT Anotation Processor Tools 注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息
说明:在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。

@Retention(RetentionPolicy.CLASS)

技术:字节码增强
说明:在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解,aop面向切面编程。
定义: CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否
登录的判断。

@Retention(RetentionPolicy.RUNTIME)

技术:反射
说明:在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。

反射

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
Java反射机制主要提供了以下功能:
1、在运行时构造任意一个类的对象
2、在运行时获取或者修改任意一个类所具有的成员变量和方法
3、在运行时调用任意一个对象的方法(属性)

Class

反射始于Class,Class是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。

Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例。

获得 Class 对象

获取Class对象的三种方式
1、 通过类名获取 类名.class
2、通过对象获取 对象名.getClass()
3、通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
使用 Class 类的 forName 静态方法

public static Class forName(String className)

直接获取某一个对象的 class

Class klass = int.class; 
Class classInt = Integer.TYPE

调用某个对象的 getClass() 方法

StringBuilder str = new StringBuilder("123");
 Class klass = str.getClass();
判断是否为某个类的实例

一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:

public native boolean isInstance(Object obj);

判断是否为某个类的类型

public boolean isAssignableFrom(Class cls)
创建实例

通过反射来生成对象主要有两种方式。
1、使用Class对象的newInstance()方法来创建Class对象对应类的实例

Class c = String.class; 
Object str = c.newInstance();

2、先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例

//获取String所对应的Class对象 
Class c = String.class; 
//获取String类带一个String参数的构造器 
Constructor constructor = c.getConstructor(String.class); 
//根据构造器创建实例 
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取构造器信息

得到构造器的方法

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类) 
Constructor[] getConstructors() -- 获得类的所有公共构造函数 
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有) 
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

public T newInstance(Object ... initargs)
获取类的成员变量(字段)信息

获得字段信息的方法

Field getField(String name) -- 获得命名的公共字段 
Field[] getFields() -- 获得类的所有公共字段 
Field getDeclaredField(String name) -- 获得类声明的命名的字段 
Field[] getDeclaredFields() -- 获得类声明的所有字段
调用方法

获得方法信息的方法

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法 
Method[] getMethods() -- 获得类的所有公共方法 
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法 
Method[] getDeclaredMethods() -- 获得类声明的所有方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:

public Object invoke(Object obj, Object... args)
利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为
java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

public static Object newInstance(Class componentType, int length);
反射获取泛型真实类型

当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:
TypeVariable
泛型类型变量。可以泛型上下限等信息;
ParameterizedType
具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
GenericArrayType
当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。
WildcardType
通配符泛型,获得上下限信息;

public class TestType  { 
  K key; 
  V value; 
  public static void main(String[] args) throws Exception { 
    // 获取字段的类型 
    Field fk = TestType.class.getDeclaredField("key"); 
    Field fv = TestType.class.getDeclaredField("value"); 
    TypeVariable keyType = (TypeVariable)fk.getGenericType(); 
    TypeVariable valueType = (TypeVariable)fv.getGenericType(); 
    // getName 方法 
    System.out.println(keyType.getName());  // K 
    System.out.println(valueType.getName()); // V 
    // getGenericDeclaration 方法 
    System.out.println(keyType.getGenericDeclaration()); // class com.test.TestType
    System.out.println(valueType.getGenericDeclaration()); // class com.test.TestType 
    // getBounds 方法 
    System.out.println("K 的上界:"); // 有两个 
    for (Type type : keyType.getBounds()) { // interface java.lang.Comparable 
      System.out.println(type); // interface java.io.Serializable 
    }
    System.out.println("V 的上界:"); // 没明确声明上界的, 默认上界是 Object 
    for (Type type : valueType.getBounds()) { // class java.lang.Object 
      System.out.println(type); 
    } 
  } 
}

ParameterizedType

public class TestType { Map map; 
  public static void main(String[] args) throws Exception { 
    Field f = TestType.class.getDeclaredField("map");
    System.out.println(f.getGenericType()); // java.util.Map 
    ParameterizedType pType = (ParameterizedType) f.getGenericType();
    System.out.println(pType.getRawType()); // interface java.util.Map 
    for (Type type : pType.getActualTypeArguments()) { 
      System.out.println(type); // 打印两遍: class java.lang.String 
    } 
  } 
}

GenericArrayType

public class TestType { 
  List[] lists; 
  public static void main(String[] args) throws Exception {
   Field f = TestType.class.getDeclaredField("lists"); 
   GenericArrayType genericType = (GenericArrayType) f.getGenericType(); 
   System.out.println(genericType.getGenericComponentType());
  }
 }

WildcardType

public class TestType { 
  private List a; // 上限 
  private List b; //下限 
  public static void main(String[] args) throws Exception { 
    Field fieldA = TestType.class.getDeclaredField("a"); 
    Field fieldB = TestType.class.getDeclaredField("b"); 
    // 先拿到范型类型 
    ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType(); 
    ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType(); 
    // 再从范型里拿到通配符类型 
    WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0]; 
    WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0]; 
    // 方法测试 
    System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number
    System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String 
    // 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
    System.out.println(wTypeA);
  }
}

在GSON反序列化过程中为什么TypeToken要定义为抽象类?

Response resp = gson.fromJson(json, new TypeToken>() { }.getType());

因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。

你可能感兴趣的:(注解和反射)