美文网首页
注解与反射

注解与反射

作者: _一叶孤帆 | 来源:发表于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

相关文章

  • 反射与注解

    反射:框架设计的灵魂 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码反射:将类的各个组成部分封装为其他...

  • 注解与反射

    注解 Annotation, JDK5.0 引入的一种注释机制 注解是元数据的一种形式,提供有关于程序但不属于程序...

  • 注解与反射

    注解(Annotation) 什么是注解 注解又叫 Java 标注,是 JDK5.0 引入的一种注释机制。注解是元...

  • 注解与反射

    注解与反射 自定义注解 @Target 描述的注解可以用在什么地方@Retention 表示被它所注解的注解在...

  • 注解与反射

    注解 声明一个注解类型 元注解 在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之...

  • 第12章 元编程与注解、反射

    第12章 元编程与注解、反射 反射(Reflection)是在运行时获取类的函数(方法)、属性、父类、接口、注解...

  • java注解与反射,泛型与反射

    一、反射与注解 内置注解 java内置了3种注解,用来为编译器提供检查。 自定义注解 元注解 元注解是用来修饰注解...

  • 一文搞懂反射泛型和反射注解以及通过注解方式写一个BaseDao

    反射泛型和反射注解概括起来就三步: 自定义注解 通过反射获取注解值 使用自定义注解 最终案例 通过自定义注解,将数...

  • 通过自定义注解A类给B类赋值

    自定义注解与反射说明 元注解:1)@Retention:RetentionPolicy.SOURCE :只在jav...

  • 注解反射以及动态代理

    注解与反射以及动态代理 注解 注解的定义 Annotation(注解)就是Java提供了一种元程序中的元素关联任何...

网友评论

      本文标题:注解与反射

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