Java进阶基础--注解与反射
一、注解
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
1、注解声明
Java中所有的注解,默认实现Annotation接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
于声明一个“Class”不同的是,注解的声明使用@interface关键字。
public @interface Lance{ }
1.1、元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为meta-annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个:
@Target
注解标记另一个注解,以限制可以应用注解的Java元素类型。目标注解指定以下元素类型之一作为其值:
①ElementType.ANNOTATION_TYPE 可以应用于注解类型。
②ElementType.CONSTRUCTOR 可以应用于构造函数。
③ElementType.FIELD 可以应用于字段或属性。
④ElementType.LOCAL_VARIABLE 可以应用于局部变量。
⑤ElementType.METHPD 可以应用于方法级注解。
⑥ElementType.PACKAGE 可以应用于包声明。
⑦ElementType.PARAMETER 可以应用于方法的参数。
⑧ElementType.TYPE 可以应用于类的任何元素。
@Retention
注解指定标记注解的存储方式:
①RetentionPolicy.SOURCE 标记的注解仅保留在源码级别中,并被编译器忽略。
②RetentionPolicy.CLASS 标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略。
③RetentionPolicy.RUNTIME 标记的注解由JVM保留,因此运行时环境可以使用它。
另外还有@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类继承父类中定义的注解。
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
例子:
@Target({ElementType.TYPE,ElementType.FIELD}) // 允许在类与类属性上标记该注解
@Retention(RetentionPolicy.SOURCE) //注解保留在源码中
public @interface Lance {}
1.2、注解类型元素
在声明注解中,允许在使用注解时传递参数。
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Lance {
String value(); //无默认值
int age() default 1; //有默认值
}
注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
1.3、注解应用场景
按照@Retention元注解定义的注解存储方式,注解可以被在三种场景中使用:
1.4、Android注解语法检查
在Android中我们需要设计接口以供使用者调用时,如出现需要对入参进行类型限定,如限定为资源ID,布局ID等类型参数,将参数类型直接给定int即可。然而,我们可以利用Android为我们提供的语法检查注解,来辅助进行更为直接的参数类型检查与提示。
参数限制为:图片资源ID
public Drawable getDrawable(@Drawable int id) throw是 NotFoundException
同时,我们也可以使用@intDef来定义自己的入参类型检查。
二、反射
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能都获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类进行操作。
反射则是一开始并不知道我们初始化的类对象是什么,自然也无法使用new关键字来创建对象。这时候,我们使用JDK提供的反射API进行反射调用。
反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
Java反射机制主要提供了以下功能:
①在运行时构造任意一个类的对象
②在运行时获取或者修改任意一个类所具有的成员变量和方法
③在运行时调用任意一个对象的方法和属性
1、Class
反射始于Class,Class是一个类,封装了当前对象所对应的类的信息。
1.1、获得Class对象
①通过类名获取 类名.class
②通过对象获取 对象名.getClass()
③通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
2、判断是否为某个类的实例
一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法: public native boolean isInstance(Object obj);
判断是否为某个类的类型: public boolean isAssignableFrom(Class<?> cls)
3、创建实例
①使用Class对象的newInstance()方法来创建Class对象对应类的实例
Class<?> c = String.class; Object str = c.newInstance();
②先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。
//获取String所对应的Class对象 Class<?> c = String.class;
//获取String类带一个String参数的构造器 Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例 Object obj = constructor.newInstance("23333");
4、获取构造器信息
获取构造器的方法:
①Constructor getConstructor(Class[] params) 获得使用特殊的参数类型的public构造函数(包括父类)
②Constructor[] getConstructors() 获得类的所有公共构造函数
③Constructor getDeclaredConstructor(Class[] params) 获得使用特定参数类型的构造函数(包括私有)
④Constructor[] getDeclaredConstructors() 获得类的所有构造函数(与接入级别无关)
5、获取类的成员变量(字段)信息
获取字段信息的方法:
①Field getField(String name) 获得命名的公共字段(public修饰)
②Field[] getFields() 获得类的所有公共字段(public修饰)
③Field getDeclaredField(String name) 获得类声明的命名的字段(包括private修饰)
④Field[] getDeclaredFields 获得类声明的所有字段(包括private修饰)
当我们从类中获取了一个方法后,就可以用 invoke() 方法来调用这个方法。
public Object invoke(Object obj, Object... args);
6、利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值一个Object Reference其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象
public static Object newInstance(Class<?> componentType, int length);
7、反射获取泛型真实类型
当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成如JSON反序列化的操作。此时需要通过Type体系来完成。Type接口包含了一个实现类(Class)和四个实现接口:
①TypeVariable 泛型类型变量。可以获得泛型上下限等信息
②ParameterizedType 具体的泛型类型。可以获得元数据中泛型签名类型(泛型真实类型)
③GenericArrayType 当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现
④WildcardType 通配符泛型。可以获得上下限信息