注解(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){
}
当我们在使用的时候就会出现提示:
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()
方法来判断是否为某个类的实例。
创建实例
通过反射来生成对象主要有两种方式
- 使用 Class 对象的
newInstance()
方法来创建对象对应类的实例。
Class stringClass = String.class;
try {
String s = stringClass.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
- 通过 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();
}
获取构造器
- 得到构造器方法 (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 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 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