引言
注解Annotation和反射虽然本该是作为Java 的基本知识,但是现实中很多应用开发者却不甚了解,如果你有阅读目前主流的开源框架,你会发现几乎所有的框架的实现离不开注解、泛型和反射,greenDAO、Arouter、Glide、Retrofit、ButterKnife等等还有很多框架,所以如果你想要读懂框架甚至写出自己的框架这些都是最基本的知识。
一、注解(Annotation)
1、注解概述
Annotation是Java5开始引入的特性,它提供了一种安全的类似于注释和Java doc的机制。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效,这些元数据与程序业务逻辑无关,并且是供指定的工具或框架使用的。java.lang.annotation.Retention可以在定义Annotation型态时,指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation信息留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供。Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)等处。简而言之,注解给我们提供了一种将Java源程序的元素与任何数据和任何元素相关联的方法和途径。
2、元注解(Meta Annotation)
元注解就是负责注解其他注解的系统内置注解,Java5定义了四个标准的元注解:@Target、@Retention、@Documented和@Inherited。
2.1、@Target用于说明Annotation所修饰的对象范围
@Target用于指定被描述的注解可以用在什么地方, @Target的值只能来自java.lang.annotation.ElementType的枚举类型值
- CONSTRUCTOR——用于指定该注解只能使用在构造方法
- FIELD——用于指定该注解只能使用在域
- LOCAL_VARIABLE——用于指定该注解只能使用在局部变量
- METHOD——用于指定该注解只能使用在方法
- PACKAGE——用于指定该注解只能使用在包
- PARAMETER——用于指定该注解只能使用在参数
- ANNOTATION_TYPE——用于指定该注解只能使用注解类型
2.2、@Retention用于声明Annotation的生命周期
@Retention用于指定Annotation的生命周期(表示需要在什么级别保存该注解信息)。因为有些Annotation仅需要出现在源代码中,有些一些却被编译到class文件中,还有些需要在运行的时候还一直存在。@Retention的值只能来自java.lang.annotation.RetentionPolicy的枚举类型值
- SOURCE——仅存在源码阶段,编译器处理完注解就没了(即在源码文件有效)
- CLASS——编译器将注解储存于class文件中(即在class文件中有效),VM不能读取到
- RUNTIME——编译器将注解储存于class档中,可由VM读入,可以在运行时候通过能够在运行时刻通过反射API来获取到注解的信息(即在运行时有效)
2.3、@Documented和@Inherited
@Documented和@Inherited是两个标记注解,没有成员,其中@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。而@Inherited 是一个标记注解,如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个Annotation将被用于该class的子类。
3、Java其他内置注解
- @Override ——标记型Annotation,指明被标注的方法是覆盖了父类的方法,如果给一个非覆盖父类方法的方法添加该Annotation或者删除已被子类覆盖(且被Override 修饰)的父类方法时,将报编译错误。
- @Deprecated ——标记型Annotation,指明被标注的元素已被废弃并不推荐使用,编译器会在该元素上加一条横线以作提示。该修饰具有一定的“传递性”,如果我们通过继承的方式使用了这个弃用的元素,即使继承后的元素(类,成员或者方法)并未被标记为@Deprecated,编译器仍然会给出提示。
@SuppressWarnnings ——用于告知Java编译器关闭对特定类、方法、成员变量、变量初始化的警告,例如当我们使用一个Generic collection类而未提供它的类型时,编译器可能提示“unchecked warning”的警告。要想处理这种经过,我们需要查找引起警告的代码,若它真的是错误,就需要纠正它;然而,有时我们无法避免这种警告,例如使用必须和非Generic的旧代码交互的Generic collection类时就无法避免这个unchecked warning,此时可以在调用的方法前增加@SuppressWarnnings通知编译器关闭对此方法的警告。和上面两个注解不同,@SuppressWarnnings不是标记型Annotation,它有一个类型为String[]的成员,这个成员的值为被禁止的警告名称:
- unchecked ——执行了未检查的转换时的警告。例如当使用集合时没有用泛型来指定集合的类型
- finally ——finally子句不能正常完成时的警告
- fallthrough—— 当switch程序块直接通往下一种情况而没有break时的警告
- deprecation—— 使用了弃用的类或者方法时的警告
- seriel ——在可序列化的类上缺少serialVersionUID时的警告
- path ——在类路径、源文件路径等中有不存在的路径时的警告
- all ——对以上所有情况的警告
3、自定义注解
在Java 中实现自定义注解十分简单,使用@interface关键字声明,编译程序会自动继承java.lang.annotation.Annotation接口和完成其他细节,但注解不能继承其他的注解或接口。注解体内的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型(其中返回值类型只能是基本类型、Class、String、enum,可以通过default来声明参数的默认值)注解参数的可支持数据类型:所有基本数据类型(int,float,boolean,byte,double,char,long,short)、String类型、Class类型、enum类型、Annotation类型及以上所有类型的数组,自定义注解步骤也很简单:
- 使用 关键字 @interface 定义注解
- 使用元注解指定自定义注解使用的地方、生命周期等信息
- 在注解体内,以无参无返回值形式声明参数成员,在声明的时候可使用关键字 default 设置默认值(若注解体内只有一个参数成员,则必须命名为String value();若注解体内一个参数也没有则该注解为标注类型的注解)。
-
-
-
//通用注解格式
@Target()
@Retention()
public @interface 注解名 {//定义注解体}
自定义注解里面的参数只能用public或默认(default)这两个访问权修饰(比如String value(); 即把方法设为defaul默认类型,参数成员为String,而若只有一个参数成员,最好把参数名称设为”value”,后加小括号) 而且参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型以及对应类型的数组。
@Target(value = {ElementType.TYPE,ElementType.Method})
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface MyAnnotation {
String value() default "cmo";
/***
* 默认isCached属性为false
* @return boolean
*/
boolean isCached() default false;
String name() default "";
}
二、泛型(Generics)
Java泛型(即“参数化类型”)是JDK5引入的一个新特性,允许在定义类和接口的时候使用类型参数,泛型信息只存在于代码编译阶段。顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
1、类型擦除(type erasure)
Java 泛型在运行的时候是会进行类型擦除,泛型信息只存在于代码编译阶段,比如对于以下List< String >和List< Float>在运行阶段都是ArrayList.class,因为泛型信息被擦除了,因为不管是ArrayList< String >还是ArrayList< Integer >,都会在编译期被编译器擦除成ArrayList
List<String> strList=new ArrayList<>();
List<Float> floatList=new ArrayList<>();
Class <?> strClz=strList.getClass();
Class <?> floatClz=strList.getClass();
System.out.println(strClz+" "+floatClz+" "+(strClz==floatClz) + " "+strClz.equals(floatClz)); //输出 class java.util.ArrayList class java.util.ArrayList true true
受类型擦除的影响,泛型存在自己一些特性包括:
泛型类并没有自己独有的Class类对象。比如并不存在List< String >.class或是其他List< T >.class,而只有List.class。
静态变量是被泛型类的所有实例所共享的。对于声明为MyClass< T >的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass< String >还是new MyClass< Integer >创建的对象,都是共享一个静态变量。
不允许将泛型类型参数声明为静态属性
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException< String >和MyException< Integer >的。对于JVM来说,它们都是MyException类型的,也就无法执行与异常对应的catch语句。
一个类的子类可以通过对象多态性,为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接收的。比如List< String >不能使用List< Object >接收。
2、通配符和上下界
2.1、通配符 ?
通配符 ? 来表示未知类型的一组类型,但具体的类型是未知的,当集合不确定将要接收什么类型的对象时,使用“?”作为接收未来传递来的对象的通配符。比如List< ? >就声明了List中包含的元素类型是未知的,List< ? >所声明的就是所有的类型都是可以的,但是List< ? >并不等同于List< Object >,而List< Object >实际上确定了List中包含的是Object及其子类,在使用的时候可以通过Object来进行引用,而对于List< ? >则表示其中所包含的元素类型是不确定,就不能通过new ArrayList< ? >()的方法来创建一个新的ArrayList对象,因为编译器无法知道具体的类型是什么。
2.2、上界 extends
首先此处的extends 和用于表示继承关系的关键字 extends 毫无关联,仅仅是同名而已,泛型中的extends代表向上限定,即上限被限定了, <? extends T> 接收T和T的子类,从另一个角度上来说,extends 只能读不能写通常作为返回值来使用。
- 定义泛型类——【访问权限】泛型类名称< 泛型标识 extends 类 >{}
- 声明对象——泛型类名称
2.3、下界 super
首先此处的super 和用于表示父类的关键字 super毫无关联,仅仅是同名而已,泛型中的super代表向下限定,即下限被限定了, <? super E> 接收E和E的父类,从另一个角度上来说,super只能写不能读通常作为参数来使用。
- 定义泛型类——【访问权限】泛型类名称< 泛型标识 super类 >{}
- 声明对象——泛型类名称
3、获取泛型类的Class< ? >
对于普通非泛型的类来说获取对应的Class< ? >很简单,只需要通过Class< ? > type = Field.getType获得需要的Class< ? >,而对于泛型类来说则不能T.getClass()或者T.class,好在Java为我们提供了 Type 接口(它的实现类有Class,子接口有 ParameterizedType, WildcardType等),通过Type接口可以间接获取对应的类型。
3.1、ParameterizedType (参数化类型)
Type[] getActualTypeArguments()返回类型的参数的实际类型数组如Map< String,Integer >的类型(Type)是属于参数化类型(ParameterizedType),
则可以获得:
Type key = getActualTypeArguments()[0];
Type value = getActualTypeArguments()[1];//其中Key即为String,Value则是Integer。
3.2、WildcardType (通配符的类型)
在泛型中可以通过通配符 ? 来声明一个泛型类型,用以指明 ? 的上下边界。比如带上边界的通配符: List< ? extends Number > list;带下边界的通配符:List< ? super Integer > list。
JAVA泛型通配符的使用规则:”PECS”(生产者使用“? extends T”通配符,消费者使用“? super T”通配符)
- “?”和“? extends T”不能添加元素
//以下编译失败
List<?> list=new ArrayList<>();
list.add(22);//ErroXXXXXXXX
List<? extends Object> list=new ArrayList<>();
list.add(22);//ErroXXXXXXXX
- ? super Object可以添加元素
//可以编译成功
List <? super Object> list2=new ArrayList<>();
list2.add("bj");
list2.add(1009);
3.3、定义类(匿名内部类)的方式,在类信息中保留泛型信息,从而在运行时获得这些泛型信息。
如果需要保证一个泛型类在运行期可以通过反射机制获取到Class对应的泛型类型。可以通过以下方式构造,
public class TypeReference<T> {
//如果要将构造函数改为public,则需要将类声明为abstract才不会出现问题
protected TypeReference() {
//获取当前对象的直接超类的 Type
Type superClass = getClass().getGenericSuperclass();
Type oriType = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}
//使用的时候必须使用这种方式:
new TypeReference< Object >(){}.getType()
这里为什么没有“类型擦除”?观察上面的类,如果将构造函数的作用域改为public,那么会得到一个java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType异常,因为当new这个类的时候,实际上是创建了一个匿名内部类,TypeReference的子类,泛型参数限定为Object。而引入泛型擦除的原因是避免因为引入泛型而导致运行时创建不必要的类。Java 的泛型擦除是有范围的,除了结构化信息外的所有东西会擦除,与类及其字段和方法的类型参数相关的元数据都会被保留下来,可以通过反射获取到。
3.4、Java 的Type 体系
没有泛型的时候,只有所谓的原始类型,所有的原始类型都通过字节码文件类Class类进行抽象即Class类的一个具体对象就代表一个指定的原始类型,而泛型出现之后,泛型出现之后,从只有原始类型扩充了ParameterizedType参数化类型(比如泛型List、Map)、TypeVariable类型变量类型(即泛型中的变量例如T、K、V、E等变量)、WildcardType泛型限定的参数化类型 (含通配符+通配符限定表达式)、GenericArrayType泛型数组类型(如T[])等。而为了程序的扩展性,最终引入了Type接口作为Class和这些扩展类型的父接口。
- ParameterizedType
public interface ParameterizedType extends Type {
/**
* Returns an array of {@code Type} objects representing the actual type arguments to this type.
* <p>Note that in some cases, the returned array be empty. This can occur
* if this type represents a non-parameterized type nested within a parameterized type.
* 返回表示此类型的实际类型参数的对象数组,即< > 中的类型,获取泛型中的实际类型,可能会存在多个泛型,例如Map< K,V>,所以会返回Type[]数组,无论<>中有几层嵌套,
* 如(List< Map< String,Integer>),getActualTypeArguments()方法永远都是脱去最外层的<>(也就是List< >),将口号内的内容(Map< String,Integer>)返回,
* 又如List<T>,通过getActualTypeArguments()方法,得到的返回值是TypeVariableImpl对象
*/
Type[] getActualTypeArguments();
/**
* @return the {@code Type} object representing the class or interface
* that declared this type
* 返回表示声明此类型的类或接口的对象。即< >外的类型
*/
Type getRawType();
/**
* Returns a {@code Type} object representing the type that this type
* is a member of. For example, if this type is {@code O<T>.I<S>},
* return a representation of {@code O<T>}.
* <p>If this type is a top-level type, {@code null} is returned.
* 若这个类型是某个类型所属的,返回这个所有者类型否则返回null
*/
Type getOwnerType();
}
- TypeVariable
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
/**
* 获取泛型的上界Type数组
*/
Type[] getBounds();
/**
* 返回表示声明此类型变量的泛型声明的GenericDeclaration对象。
*/
D getGenericDeclaration();
String getName();
/**
* 返回一个AnnotatedType对象数组,表示使用类型来表示由此TypeVariable表示的类型参数的上限。
*/
AnnotatedType[] getAnnotatedBounds();
}
- GenericArrayType
泛型数组类型,用来描述ParameterizedType、TypeVariable类型的数组比如List[] 、T[]等
public interface GenericArrayType extends Type {
Type getGenericComponentType();
}
3.5、获取泛型类类型以及对应的原始类型
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GenericReflect {
//接收Object和Object的子类
static class User<T extends Object>{
private List<? super User> list;
Map<String, ? extends Object> map;
public List<?> name;
public List<List<Integer>> nameList;
}
public static void main(String[] args) {
Class<User> clz=User.class;
Field[] fields=clz.getDeclaredFields();
for (Field field : fields) {
getGenericClz(field);
System.out.println("\n-------------------------------------------------------------------------------------------------\n");
}
}
private static void getGenericClz(Field field){
String fieldName=field.getName();
//获得 genericType
Type genericType = field.getGenericType();
System.out.print("【成员名】:"+fieldName+"\n\t【field.getGenericType()】: "+field.getGenericType().getTypeName());
//参数化类型 ParameterizedType
if (genericType instanceof ParameterizedType) {
System.out.print("\n\t【getRawType】"+((ParameterizedType) genericType).getRawType().getTypeName());
//获得泛型类型
Type type = ((ParameterizedType) genericType).getActualTypeArguments()[0];
System.out.print("\n\t"+"【((ParameterizedType) genericType).getActualTypeArguments()[0]】: "+type.getTypeName());
//WildcardType 如果使用了通配符
if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
Type[] upperBounds = wildcardType.getUpperBounds();
if (upperBounds.length == 1) {
Type actualTypeArgument = upperBounds[0];
System.out.print("\n\t"+"【获得泛型上边界类型】:" + actualTypeArgument.getTypeName());
}
Type[] lowerBounds = wildcardType.getLowerBounds();
if (lowerBounds.length == 1) {
Type actualTypeArgument = lowerBounds[0];
System.out.print("\n\t"+"【获得泛型下边界类型】:" + actualTypeArgument.getTypeName());
}
} else {
System.out.print("\n【获得泛型类型】:" + type.getTypeName());
}
}
}
}
三、反射(Reflect)
反射可以说就是基于Class.class,通过Class.class可以获取类的构造方法、类的Field和类的方法。
1、获取类的类类型Class< ? >
有以下方式可以获取类的类类型Class< ? >:
- Class< ? > clz=T.class
- Class< ? > clz=t.getClass(),其中t为T的实例对象再调用Object的getClass()
- 调用Class.class中的静态方法 Class< ? > forName(“全包名”)或者Class< ? > forName(String name, boolean initialize,ClassLoader loader)就可以获取Class< ? >
2、构造对象
通过反射调用构造方法主要通过两步:
- 先获取对应的类类型Class < ? >
- 再通过Class.class实例调用newInstance()创建默认的构造对象或者通过Constructor实例调用newInstance(Object … initargs)创建有参数初始化的对象。
3、执行方法
反射执行类的方法主要是通过Method的invoke方法
首先通过Class.class的public Method getMethod(String name, Class< ? >…parameterTypes)获取对应的Method
再通过Method实例调用public Object invoke(Object methodObj, Object… paramsArgs)方法。
package train;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Data {
private String name;
public Data(String name) {
this.name = name;
}
public Data() {}
private void test(){
System.out.println("调用Data类的test方法");
}
public String blog(String blog){
return "https://"+blog+"/"+this.name;
}
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("train.Data");
Method method = clazz.getDeclaredMethod("test", new Class[]{});
method.setAccessible(true);
method.invoke(clazz.newInstance()); //输出——>调用Data类的test方法
Constructor<?> constructor=clazz.getConstructor(String.class);
method = clazz.getMethod("blog",String.class);
method.setAccessible(true);
String blog =(String) method.invoke(constructor.newInstance("CrazyMo_"), "blog.csdn.net");
System.out.println("博客地址:"+blog); //输出——> 博客地址:https://blog.csdn.net/CrazyMo_
}
}