注解与反射

注解(Annotation)

什么是注解

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

Java 中所有的注解,默认都实现了 Annotation 接口

package java.lang.annotation;
public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class annotationType();
}

JDK注解

@Target : 标识给谁注解

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //类、接口(包括注释类型)或枚举声明

    /** Field declaration (includes enum constants) */
    FIELD, //字段声明(包括枚举常量)

    /** Method declaration */
    METHOD, //方法声明 

    /** Formal parameter declaration */
    PARAMETER, //参数声明 

    /** Constructor declaration */
    CONSTRUCTOR, //构造方法声明

    /** Local variable declaration */
    LOCAL_VARIABLE, //局部变量声明 

    /** Annotation type declaration */
    ANNOTATION_TYPE, //注释类型声明. 定义元注解

    /** Package declaration */
    PACKAGE, //包声明

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Retention : 标识这个注解的保留时。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler. 源码级别
     * 标记的注解仅保留在源级别中,并被编译器忽略
     */
    SOURCE, 

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.运行时
     *
     * 标记的注解由 JVM 保留,因此运行时环境可以使用它。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、 CLASS

自定义注解

定义一个注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface CustomAnnotation {
    int Num() default 0; // default 声明默认值
    String value();
}

使用注解

public class MainActivity extends AppCompatActivity {

    //如果只存在value元素需要传值的情况,则可以省略:元素名=
    @CustomAnnotation("我的自定义注解")
    String name;

    //在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
    @CustomAnnotation(num = 10, value = "自定义注解")
    int age;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

注解的应用场景

按照@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:

RetentionPolicy.SOURCE

作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用,在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。

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

@Retention(SOURCE) //源码级别注解 
@Target({ANNOTATION_TYPE}) 
public @interface IntDef {
    int[] value() default {};
    boolean flag() default false;
    boolean open() default false;
}

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

定义枚举

public enum Fruit {
    Apple,
    Banana
}

使用枚举

void  fruit(Fruit fruit){
        
 }

如果使用注解则可以使用如下写法:

public static final int DOG = 1;
public static final int CAT = 2;
@IntDef(value = {MainActivity.DOG,MainActivity.CAT})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
public @interface Animal {
}
void animal(@Animal int animal){

}

当我们在使用的时候就会出现提示:


image.png

APT 注解处理器 (Annotation Processor Tools)

注解处理器,用于处理注解。
我们编写好的 Java 文件,都需要经过 javac 的编译,翻译为虚拟机可以加载的字节码 Class 文件。注解处理器就是 javac 自带的一个工具,用来在编译时期处理注解信息。我们可以为某些注解注册自己的注解处理器。注册的注解处理器由 javac 调用,并将注解信息传递给注解处理器进行处理。

RetentionPolicy.CLASS

定义为 CLASS 的注解,会保留在 class 文件中,但是会被虚拟机忽略,无法在运行时通过反射获取注解,一般会用到字节码操作的场景。
所谓的字节码操作指的是直接修改 Class 文件以达到修改代码执行逻辑的目的。

RetentionPolicy.RUNTIME

注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

反射

一般情况,我们使用某个类时必定知道它是什么类,使用来干什么的,并且能获得到此类的引用,我们可以直接对这个类进行实例化,之后对类对象进行操作。

反射则是一开始并不知道我要初始化的这个类的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们则需要使用 JDK 提供的反射 API 进行反射调用。

反射就是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法。对于任意一个对象,都能调用它的任意方法和属性,并且能改变他的属性,是 Java 被视为动态语言的关键。

Java 反射机制主要提供了以下功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取或者修改任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法或属性

Class

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

对于每个类,都有一个不变的 Class 类型的对象,一个 Class 对象包含了特定某个类的有关信息。一个类(不是一个对象) 在 JVM 中只会有一个 Class 实例。

获取 Class 对象

获取 Class 对象的三种方式

  • 通过类名获取
    类名.class
  • 通过对象获取
    对象名.class
  • 通过全类名获取
    Class.forName(全类名)
    ClassLoader.loadClass(全类名)

判断是否是某个类的实例

通常我们使用 instanceof 来判断是否为某个类的实例,我们也可以使用反射中 Class 对象中的 isInstance() 方法来判断是否为某个类的实例。

创建实例

通过反射来生成对象主要有两种方式

  1. 使用 Class 对象的 newInstance() 方法来创建对象对应类的实例。
 Class stringClass = String.class;
        try {
            String s = stringClass.newInstance();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
  1. 通过 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法来创建实例。这种方法可以用指定的构造器构造类的实例。
// 获取 Class 对象
Class stringClass = String.class;
Constructor constructor = null;
try {
    // 获取带指定参数的构造器
    constructor = stringClass.getConstructor(String.class);
    // 根据构造器创建实例
    String s = constructor.newInstance("constructor.newInstance");
    System.out.println(s);
} catch (Exception e) {
    e.printStackTrace();
}

获取构造器

  1. 得到构造器方法 (Class 中的方法)

获取含有指定参数的构造器(包含父类)

public Constructor getConstructor(Class... parameterTypes)

获取类的所有公共的构造函数

public Constructor[] getConstructors()

获取使用指定参数的构造函数(包含私有)

public Constructor getDeclaredConstructor(Class... parameterTypes)

获取类的所有构造函数

public Constructor[] getDeclaredConstructors()

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

public T newInstance(Object ... initargs)

获取类的成员变量信息

获取类的指定名称的公共字段

public Field getField(String name)

获取类中所有的公共字段

public Field[] getFields()

获取指定参数的字段(包含私有)

public native Field getDeclaredField(String name)

获取类声明的所有的字段

public native Field[] getDeclaredFields();

获取方法

获取指定方法名和指定参数的方法

public Method getMethod(String name, Class... parameterTypes)

获取所有方法

public Method[] getMethods()

获取自定方法名和指定参数的方法(包含私有)

public Method getDeclaredMethod(String name, Class... parameterTypes)

获取所有的方法

public Method[] getDeclaredMethods()

当我们获取到一个方法后,我们就可以使用 invoke() 方法来调用这个方法。

invoke 的方法原型为:

public native Object invoke(Object obj, Object... args)
method.invoke(class,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 通配符泛型,获取上下限信息。

TypeVariable

public class TestType {
    K key;
    V value;

    public static void main(String[] args) {

        try {

            Field fk = TestType.class.getDeclaredField("key");
            Field fv = TestType.class.getDeclaredField("value");

            TypeVariable genericTypeK = (TypeVariable) fk.getGenericType();
            TypeVariable genericTypeV = (TypeVariable) fv.getGenericType();


            System.out.println(genericTypeK.getName() + "name");  //Kname
            System.out.println(genericTypeV.getName() + "name");  //Vname

            System.out.println(genericTypeK.getGenericDeclaration()); //class com.androidstudy.type.TestType
            System.out.println(genericTypeV.getGenericDeclaration()); //class com.androidstudy.type.TestType

            System.out.println("K 的上界:");
            for (Type type : genericTypeK.getBounds()) {
                System.out.println(type);
                //interface java.lang.Comparable
                //interface java.io.Serializable
            }

            System.out.println("V 的上界:");
            for (Type type : genericTypeV.getBounds()) {
                System.out.println(type);
                //class java.lang.Object  没明确声明上界的, 默认上界是 Object
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

ParameterizedType

public class TestType1 {
    Map mMap;

    public static void main(String[] args) {
        try {
            Field field = TestType1.class.getDeclaredField("mMap");
            System.out.println(field.getGenericType()); //java.util.Map
            ParameterizedType parameterizedType  = (ParameterizedType) field.getGenericType();
            System.out.println(parameterizedType.getRawType()); //interface java.util.Map

            for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
                System.out.println(actualTypeArgument);
                //class java.lang.String
                //class java.lang.String
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

GenericArrayType


public class TestType2 {
    List[] mLists;

    public static void main(String[] args) {
        try {
            Field field = TestType2.class.getDeclaredField("mLists");
            GenericArrayType genericArrayType = (GenericArrayType) field.getGenericType();
            System.out.println(genericArrayType.getGenericComponentType()); //java.util.List
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

WildcardType

public class TestType3 {

    private List a; // 上限
    private List b; // 下限

    public static void main(String[] args) {
        try {
            Field a1 = TestType3.class.getDeclaredField("a");
            Field b1 = TestType3.class.getDeclaredField("b");

            ParameterizedType parameterizedTypeA = (ParameterizedType) a1.getGenericType();
            ParameterizedType parameterizedTypeB = (ParameterizedType) b1.getGenericType();

            WildcardType wildcardType1 = (WildcardType) parameterizedTypeA.getActualTypeArguments()[0];
            WildcardType wildcardType2 = (WildcardType) parameterizedTypeB.getActualTypeArguments()[0];

            System.out.println(wildcardType1.getUpperBounds()[0]); //class java.lang.Number
            System.out.println(wildcardType2.getUpperBounds()[0]); //class java.lang.Object

            System.out.println(wildcardType1); //? extends java.lang.Number
            System.out.println(wildcardType2); //? super java.lang.String

        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

Gosn 反序列化


static class Response {
    T data;
    int code;
    String message;

    @Override
    public String toString() {
        return "Response{" +
                "data=" + data +
                ", code=" + code +
                ", message='" + message + '\'' +
                '}';
    }

    public Response(T data, int code, String message) {
        this.data = data;
        this.code = code;
        this.message = message;
    }
}

static class Data {
    String result;

    public Data(String result) {
        this.result = result;
    }

    @Override
    public String toString() {
        return "Data{" +
                "result=" + result +
                '}';
    }
}

    public static void main(String[] args) {
        Response dataResponse = new Response(new Data("数据"), 1, "成功");
        Gson gson = new Gson();
        String json = gson.toJson(dataResponse);
        System.out.println(json);
        //为什么TypeToken要定义为抽象类?
        Response resp = gson.fromJson(json, new TypeToken>() {
        }.getType());
        System.out.println(resp.data.result);
    }

在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么 TypeToken 要被定义为抽象类呢?
因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型 signature信息记录到Class元数据中。

demo

https://github.com/ios-yifan/testType

通过注解来实现 findViewById 的自动注入
https://github.com/ios-yifan/annotation_findviewbyid

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