反射,注解中级篇

Java知识是作为Android开发的语言基础,虽然现在已经推出了kotlin,但是基于以下原因我们还是需要好好牢牢掌握

java:

*1. SDK还是java,kotlin也需要编译成为java运行;

\2. Java语言应用不仅仅在Android,就是在后台开发中也是一个最流行的语言;

\3. 大公司面试都要求我们有扎实的Java语言基础。

所以,请大家不要轻视提高自己Java基础的机会,请大家认真学习,做好笔记,争取取得更大的进步。

注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关

于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

注解声明

声明一个注解类型

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

与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:

元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta�

annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :

package java.lang.annotation;

public interface Annotation {

boolean equals(Object obj);

int hashCode();

String toString();

Class<? extends Annotation> annotationType();

}

public @interface Lance{

}

另外还有@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 保留,因此运行时环境可以使用它。

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

CLASS。下文会介绍他们不同的应用场景。

下面来看例子:

注解类型元素

在上文元注解中,允许在使用注解时传递参数。我们也能让自定义注解的主体包含 annotation type element (注解

类型元素) 声明,它们看起来很像方法,可以定义可选的默认值。

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

//@Target(ElementType.TYPE) 只能在类上标记该注解

@Target({ElementType.TYPE,ElementType.FIELD}) // 允许在类与类属性上标记该注解

@Retention(RetentionPolicy.SOURCE) //注解保留在源码中

public @interface Lance {

}

@Target({ElementType.TYPE,ElementType.FIELD})

@Retention(RetentionPolicy.SOURCE)

public @interface Lance {

String value(); //无默认值

int age() default 1; //有默认值

}

注解应用场景

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

SOURCE

RetentionPolicy.SOURCE ,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。

在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。

@Lance("帅") //如果只存在value元素需要传值的情况,则可以省略:元素名=

@Lance(value="帅",age = 2)

int i;IDE**语法检查**

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

下:

Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。

比常量多5到10倍的内存占用。

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

@Retention(SOURCE) //源码级别注解

@Target({ANNOTATION_TYPE})

public @interface IntDef {

int[] value() default {};

boolean flag() default false;

boolean open() default false;

}如:我们定义方法 test ,此方法接收参数 teacher 需要在:**Lance**、**Alvin**中选择一个。如果使用枚举能够实现

为:

public enum Teacher{

LANCE,ALVIN

}

public void test(Teacher teacher) {

}

而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:

public static final int LANCE = 1;

public static final int ALVIN = 2;

public void test(int teacher) {

}

然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:

public static final int LANCE = 1;

public static final int ALVIN = 2;

@IntDef(value = {MAN, WOMEN}) //限定为LANCE,ALVIN

@Target(ElementType.PARAMETER) //作用于参数的注解

@Retention(RetentionPolicy.SOURCE) //源码级别注解

public @interface Teacher {

}

public void test(@Teacher int teacher) {

}

此时,我们再去调用 test 方法,如果传递的参数不是 LANCE 或者 ALVIN 则会显示 Inspection 警告(编译不会报

错)。

可以修改此类语法检查级别:以上注解均为 SOURCE 级别,本身IDEA/AS 就是由Java开发的,工具实现了对Java语法的检查,借助注解能对被注

解的特定语法进行额外检查。

APT**注解处理器**

APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文

件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工

具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac

调起,并将注解信息传递给注解处理器进行处理。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用

框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的

是 CLASS 级别,别忘了:CLASS**包含了SOURCE,RUNTIME包含SOURCE、CLASS。**

关于注解处理器的实现,在后续课程中会有相当多的介绍。此处先不进行详细介绍。

CLASS

定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合

此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。

所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否

登录的判断。如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加

入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录

两种类型,即两个切面。对于切面的区分即可采用注解。

//Java源码

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.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));

}

}

注解能够设置类型元素(参数),结合参数能实现更为丰富的场景,如:运行期权限判定等。

RUNTIME

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

反射

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接

对这个类进行实例化,之后使用这个类对象进行操作。

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

使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中**,对于任意一个类,都能够知道这个类的所有属性和**

方法**;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。**是Java被视为动态语言的关键。

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

在运行时构造任意一个类的对象

在运行时获取或者修改任意一个类所具有的成员变量和方法

在运行时调用任意一个对象的方法(属性)

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 void jumpB() {

startActivity(new Intent(this,BActivity.class));

}

public static Class<?> forName(String className)直接获取某一个对象的 class

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

判断是否为某个类的实例

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

isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:

判断是否为某个类的类型

创建实例

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

使用Class对象的newInstance()方法来创建Class对象对应类的实例。

先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这

种方法可以用指定的构造器构造类的实例。

获取构造器信息

Class<?> klass = int.class;

Class<?> classInt = Integer.TYPE;

StringBuilder str = new StringBuilder("123");

Class<?> klass = str.getClass();

public native boolean isInstance(Object obj);

public boolean isAssignableFrom(Class<?> cls)

Class<?> c = String.class;

Object str = c.newInstance();

//获取String所对应的Class对象

Class<?> c = String.class;

//获取String类带一个String参数的构造器

Constructor constructor = c.getConstructor(String.class);

//根据构造器创建实例

Object obj = constructor.newInstance("23333");

System.out.println(obj);得到构造器的方法

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的

一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

获取类的成员变量(字段)信息

获得字段信息的方法

调用方法

获得方法信息的方法

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

利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为

java.lang.reflflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

反射获取泛型真实类型

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)

Constructor[] getConstructors() -- 获得类的所有公共构造函数

Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)

Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

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() -- 获得类声明的所有方法

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

public static Object newInstance(Class<?> componentType, int length);当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通

过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:

TypeVariable

泛型类型变量。可以泛型上下限等信息;

ParameterizedType

具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)

GenericArrayType

当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。

WildcardType

通配符泛型,获得上下限信息;

TypeVariable

ParameterizedType

public class TestType <K extends Comparable & Serializable, V> {

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);

}

}

}**GenericArrayType**

**WildcardType**

public class TestType {

Map<String, String> map;

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

Field 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

}

}

}

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());

}

}

public class TestType {

private List<? extends 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

// 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number**Gson****反序列化**

System.out.println(wTypeA);

}

}

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元数据中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容