目录
  1. 1. 注解
    1. 1.1. 概述
    2. 1.2. 注解声明
      1. 1.2.1. 基本使用
      2. 1.2.2. 元注解
        1. 1.2.2.1. @Target
        2. 1.2.2.2. @Retention
      3. 1.2.3. 注解类型元素
    3. 1.3. 注解应用场景
      1. 1.3.1. RUNTIME
      2. 1.3.2. CLASS
      3. 1.3.3. SOURCE
        1. 1.3.3.1. IDE语法检查
        2. 1.3.3.2. APT注解处理器
  2. 2. 反射
    1. 2.1. 概述
    2. 2.2. Class
      1. 2.2.1. 获得Class对象
      2. 2.2.2. 创建实例
      3. 2.2.3. 获取构造器信息
      4. 2.2.4. 获取类的成员变量(字段)信息
      5. 2.2.5. 调用方法
      6. 2.2.6. 利用反射创建数组
      7. 2.2.7. 反射获取泛型的真实类型
        1. 2.2.7.1. TypeVariable
        2. 2.2.7.2. ParameterizedType
        3. 2.2.7.3. GenericArrayType
        4. 2.2.7.4. WildcardType
      8. 2.2.8. 实战小项目 Gson反序列化
重拾Android-Java进阶之反射

第二天 注解与反射(Annotation & Reflect)

注解

概述

Java注解(Annotation)又称Java标注,是JDK 5.0引入的一种注释机制。注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。因此注解对其注解的代码没有直接影响。

注解声明

基本使用

Java中所有的注解。默认实现 $Annotation$ 接口:

package java.lang.annotation; 

public interface Annotation {

boolean equals(Object obj);

int hashCode();

String toString();

Class<? extends Annotation> annotationType();

}

注解的声明使用 $@interface$ 关键字,注解声明如下:

public @interface XXXXX{

}

元注解

在定义注解时,注解类也能够使用其他注解声明。对注解类型进行注解的注解类,亦即注解上面的注解,我们称之为“meta-annotation”(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个:

@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 标记的注解在编译时由编译器保留,但JVM会忽略
  • RetentionPolicy.RUNTIME 标记的注解由JVM保留,因此运行时环境可以使用

注解类型元素

在元注解中,允许使用注解时传递参数,同时也能让自定义注解的主体包含annotation type element(注解类型元素),例如:

@Target({ELementType.TYPE, ElementType.FIELD}) //可以注解类也可以注解字段
@Retention(RetentionPolicy.SOURCE) //注解保留到源码级别当中
public @interface LeoCheung{
String value(); //无默认值
int age() default 1; // 有默认值
}

注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值

@LeoCheung("干爆弟弟们") //如果只存在value元素需要传值的情况,则可以省略元素名=
@LeoCheung(value="阿里我来啦", age = 18)

注解应用场景

RUNTIME

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

CLASS

注解会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。

这种注解的应用场景为字节码操作,如:Aspectj、热修复Robust

所谓的字节码操作即为:直接修改字节码class文件以打到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断

具体讲解如下

如果我们使用普通的编程方式,需要在代码中疯狂进行if-else判断,如果存在十处,就需要在这十个判断点加入校验判断。此时,我们可以借助AOP(面向切面) 编程思想,将程序中所有功能点划分为:$需要登录$ 和 $无需登录$ 两种类型。即两个切面。对于切面的切分即可采用注解。

@Target(ElementType.METHOD) //注解为方法
@Retention(RetentionPolicty.CLASS) //注解存留到字节码期间
public @interface Login{
}

@Login
public void jumpA(){
startActivity(new Intent(this, AActivity.class))
}

public void JumpB(){
startActivity(new Intent(this, BActivity.class))
}

上述代码中,$jumpA$ 方法需要具备登录条件,而 $Login$ 注解的定义被设置为 $CLASS$ ,因此我们能够在该类所编译的字节码中获得方法的注解 $Login$。在操作字节码时,就能够根据方法是否具备该注解来修改 class 中该方法的内容加入 if-else的代码段:

//Class字节码 

@Login public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}

SOURCE

IDE语法检查

在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倍的内存占用。

此注解的意义在于能够取代枚举,实现如方法入参限制。

APT注解处理器

APT全称为:”Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac
调起,并将注解信息传递给注解处理器进行处理。

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

反射

概述

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

而反射不一样,它应用于一开始并不知道自己要初始化的类对象是什么情形下,自然也无法使用new关键字来创建对象。此时,我们可以使用JDKJDK提供的反射API进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能够改变它的属性。 这也是Java被视为动态语言的关键。

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

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

Class

反射始于Class,Class是一个类,封装了当前对象所对应的类的信息。 一个类中有属性、方法、构造器等,比如说又一个Animal类,一个Car类、一个Tree类,这些都是不同的类,现在需要一个类,用来描述这些对象,这就是Class,它包含雷类名,属性、方法、构造函数等等。

CLass类是一个对象镜面映射出的结果,对象可以在Class这面镜子里看到自己的属性、方法、构造器,实现了哪些接口等等,即使是私有的,藏在内心处的,也可以通过手段查到。对于每个类而言,Jre都为其保留了一个不变的Class类型的对象,同样,一个类在JVMJVM中只会有一个CLass类。

获得Class对象

获取Class对象的三种方法:

  • 1.通过类名获取 类名.class
  • 2.通过对象获取 对象名.getClass()
  • 3.通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)

创建实例

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

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> cls = Integer.class;
Object i = cls.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法来构建对象。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class<?> cls = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = cls.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("加油啊夏目");

获取构造器信息

得到构造器的方法:

Constructor getConstructor(Class[] params) -------- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor getConstructor() -------- 获得类的所有公共的构造函数
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) -------- 使用特定的参数类型,获得命名的公共方法
Methods[] getMethods() -------- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -------- 使用特定的参数类型,获得类声明的命名的方法
Methods[] getDeclaredMethods() -------- 获得类声明的所有方法(包括私有)

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

public Object invoke(Object obj, Object... args)

利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object RefReference,其中的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 extend Comparable & Serializable, V>{

K key;
V value;

public static void main(String[] args) throw Exception{
//获取字段的类型
Filed fk = TestType.class.getDeclaredField("key");
Filed 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<String, String> map;

public static void main(String[] args) throws Exception{

Filed f = TestType.class.getDeclaredField("map");
System.out.println(f.getGenericType()); // java.util.Map<java.lang.String, java.lang.String>

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<T> {

List<String>[] 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<? extend Number> a; //上限

private List<? super String> 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

//查看通配符类型
System.out.println(wTypeA); //打印结果为: ? extends java.lang.Number
}
}

实战小项目 Gson反序列化

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 要被定义成抽象类呢?

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

打赏
  • 微信
  • 支付宝

评论