美文网首页
注解与反射

注解与反射

作者: _一叶孤帆 | 来源:发表于2021-01-25 11:13 被阅读0次

    注解(Annotation)

    什么是注解

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

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

    package java.lang.annotation;
    public interface Annotation {
        boolean equals(Object obj);
        int hashCode();
        String toString();
        Class<? extends Annotation> 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<String> 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<String> stringClass = String.class;
    Constructor<String> 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<T> getConstructor(Class<?>... parameterTypes)
    

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

    public Constructor<?>[] getConstructors()
    

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

    public Constructor<T> 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 extends Comparable & Serializable, V>{
        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<String, String> mMap;
    
        public static void main(String[] args) {
            try {
                Field field = TestType1.class.getDeclaredField("mMap");
                System.out.println(field.getGenericType()); //java.util.Map<java.lang.String, java.lang.String>
                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<String>[] 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<java.lang.String>
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    

    WildcardType

    public class TestType3 {
    
        private List<? extends Number> a; // 上限
        private List<? super String> 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> {
        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<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
            Gson gson = new Gson();
            String json = gson.toJson(dataResponse);
            System.out.println(json);
            //为什么TypeToken要定义为抽象类?
            Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() {
            }.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

    相关文章

      网友评论

          本文标题:注解与反射

          本文链接:https://www.haomeiwen.com/subject/ewhvzktx.html