自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理
前言
经过几年的发展和沉淀,Android开发中涌现出许多优秀的框架,比如:Retrofit、Afinal、OKHttp、ButterKnife、AndFix等等。这些框架的出现极大地简化了开发流程,提高了工作效率。在项目开发的过程中我们主要是使用这些轮子完成项目,很难有时间去顾及框架的内部实现。在项目交付之后我们可能就要去看看这些框架的源码了。
这些主流框架的功能各不相同,但每当打开浩繁的源码时我们几乎都可以看到反射,注解,泛型的广泛应用;也正是这些技术使得框架具有了高度的灵活性,优良的扩展性和健壮的稳定性。鉴于这些框架必备知识的重要性故在此对这部分内容做一个全面的梳理和总结。
主要内容:
- ClassLoader
- ClassLoader的分析
- 几种不同ClassLoader的介绍
- ClassLoader的应用
- 泛型
- 泛型的由来
- 自定义泛型
- 泛型的擦除
- 反射
- Class
- 常用反射技术
- Type以及ParameterizedType
- 反射与泛型的结合使用
- 注解
- 常用注解的介绍和使用
- 元注解
- 自定义注解及其使用
ClassLoader
在程序运行时首先要将类加载到内存中,这个加载工作就是由ClassLoader完成的,故在中文文档中将其翻译为”类加载器”。
那么我们代码中所用到的类有什么不同呢?——它们的”来源”是不一样的。
有的类是属于系统提供的类,比如:String、Date、Object等
所以,在Android系统启动时会自动创建一个Boot类型的ClassLoader,该ClassLoader用于加载一些系统层级的类。
有的类属于我们自己写的类,比如:User、Girl、Beauty等等
所以,每个APP会创建一个自己的ClassLoader实例,该ClassLoader用于加载dex。
嗯哼,我们再通过代码来验证一下:
/**
* 原创作者:
* 谷哥的小弟
*
* 博客地址:
* http://blog.csdn.net/lfdfhl
*/
private void getClassLoaders() {
ClassLoader classLoader = getClassLoader();
while (null != classLoader) {
System.out.println("----> classLoader=" + classLoader);
classLoader = classLoader.getParent();
}
}
输出结果如下所示:
此处一共展示了两个ClassLoader
第一个ClassLoader,如下:
dalvik.system.PathClassLoader[DexPathList[[zip file
“/data/app/cc.testreflection-2/base.apk”],nativeLibraryDirectories=[/vendor/lib,/system/lib]]]
该PathClassLoader在应用启动时创建,用于加载/data/app/cc.testreflection-2/base.apk中的类。
ClassLoader是一个抽象类,它有三个常用的子类:PathClassLoader、URLClassLoader、DexClassLoader。
PathClassLoader
它只能加载已经安装的apk中的资源,比如dex文件
URLClassLoader
它只能用于加载jar文件中的资源。但是dalvik不能直接识别jar,所以这个加载器极少使用。
DexClassLoader
它用于从.jar和.apk类型的文件内部加载classes.dex。该类加载器常用来完成动态加载apk的需求。
第二个ClassLoader,如下:
classLoader=java.lang.BootClassLoader@21b737fd
该BootClassLoader在系统启动的时候创建,用于加载系统层级的类。
在认识了这两种类加载器之后,我们看看它们的应用。
/**
* 原创作者:
* 谷哥的小弟
*
* 博客地址:
* http://blog.csdn.net/lfdfhl
*/
private void testClassLoader() {
try {
Class clazz = Class.forName("cc.testreflection.Girl");
ClassLoader classLoader = clazz.getClassLoader();
System.out.println("----> classLoader=" + classLoader);
classLoader = mContext.getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("assets/ic_launcher.png");
System.out.println("----> classLoader=" + classLoader);
clazz = Class.forName("java.lang.String");
classLoader = clazz.getClassLoader();
System.out.println("----> classLoader=" + classLoader);
} catch (Exception e) {
}
}
输出结果如下所示:
嗯哼,和之前的分析一样:
我们自己的类cc.testreflection.Girl和assets文件夹中的图片ic_launcher.png都是由PathClassLoader加载的,而java.lang.String是由BootClassLoader加载的。
泛型
泛型始现于JDK1.5,从那以后大家在项目常常使用泛型,比如:
ArrayList<Girl> arrayList=new ArrayList<Girl>();
for(int i=0;i<10;i++){
Girl girl =new Girl();
arrayList.add(girl);
}
在与此类似的场景中利用泛型限定了集合中的输入类型,从而让编译器屏蔽了源程序中的非法数据输入,比如此时往ArrayList<Girl>中add一个Boy就无法通过编译器的编译。
刚才已经说了,泛型主要是给编译器看的;那么在编译完成之后生成的字节码里泛型会发生什么变化呢?来看个例子:
private void testArraylistClass() {
Class clazz1 = new ArrayList<Integer>().getClass();
Class clazz2 = new ArrayList<String>().getClass();
boolean isEqual=(clazz1 == clazz2);
System.out.println("----> isEqual=" +isEqual);
}
输出结果:
—-> isEqual=true
哇哈!看到了没有?——带不同泛型的ArrayList在编译后生成的Class是相同的!也就是说,泛型在编译生成字节码文件时会被”擦除”;不管ArrayList<E>带什么泛型,在编译后都是ArrayList所对应的字节码文件。
我们再来个更直观的:
请注意报错:both methods have same erasure
因为泛型擦除后这两个方法就是变成了完全相同了,当然也就不是我们认为的方法重载了,所以Android Studio直接就提示了这个错误。
嗯哼,在认识了泛型的擦除之后,我们来体验一下非一般的感觉:
private void testArraylistGeneric(){
try {
ArrayList<Integer> arrayList =new ArrayList<Integer>();
arrayList.add(9527);
arrayList.add(9528);
Method method=arrayList.getClass().getMethod("add",Object.class);
method.invoke(arrayList,"hello,java");
for (int i=0;i<arrayList.size();i++){
System.out.println("----> arrayList.get("+i+")=" + arrayList.get(i));
}
}catch (Exception e){
}
}
输出结果如下图所示:
看到了吧,之所以能把一个字符串add到该ArrayList<Integer>中,究其原因还是因为泛型的擦除所致。
嗯哼,接下来瞅瞅自定义泛型。
自定义泛型方法
public static <T> T genericMethod1(T t) { return null; } public <K, V> K genericMethod2(K k, V v) { return null; } public <K, V> String genericMethod3(K k, V v) { return null; }
在自定义泛型方法时,请注意在方法的返回值之前声明一个泛型,比如:<T>这就表示该方法使用到了泛型T。在此之后,在方法的输入参数中和方法体中均可以使用该泛型
自定义泛型接口
public interface UserInfo<T> { public void printUserInfo(T t); } private class UserInfoImpl<T> implements UserInfo<T> { @Override public void printUserInfo(T t) { } }
在自定义泛型接口时,请注意在接口名之后声明一个泛型,比如:<T>这就表示该接口使用到了泛型T。在此之后,在接口定义方法时就可以使用该泛型了
自定义泛型类
public class Collection<K, V> { private K key; private V value; private K getValue(K k) { return null; } private void printValue(V v) { } }
自定义泛型类与自定义泛型接口非常类似,不再赘述
反射
我们知道Java代码会被编译成字节码文件,当需要用一个类创建其对象的时候就会将其对应的字节码文件装载到内层,然后新建对象。也就是说,当一个类编译完成后,在生成的.class文件中会产生一个Class对象,该对象用于表示这个类的信息,比如类的属性,字段,构造方法等等。
既然Class中包含了这么多有用的信息,那么我们可以用什么方式获取Class呢?
private void testGetClass() {
try {
//第一种方式
Class clazz = Girl.class;
System.out.println("----> " + clazz.getName());
//第二种方式
Girl girl = new Girl();
clazz = girl.getClass();
System.out.println("----> " + clazz.getName());
//第三种方式
clazz = Class.forName("cc.testreflection.Girl");
System.out.println("----> " + clazz.getName());
} catch (Exception e) {
}
}
获取Class的三种方式:
- 利用类名.class获取
- 利用对象.getClass()获取
- 利用Class.forName(“类名”)获取
在获取到Class之后,就可以利用newInstance()方法生成一个对象。
Object object = clazz.newInstance();
其实,在调用newInstance()方法时实际上是调用了该类的无参构造方法。
当然,我们的目的不仅仅是利用newInstance()生成一个对象,更重要的是要采用反射技术结合Class获取到该类的构造方法,属性,方法等信息。
比如有这么一个类:
public class Girl {
public String country;
public String city;
private String name;
private int age;
private int bust;
private int waist;
private int hip;
public Girl(){
System.out.println("调用Girl的无参构造方法");
}
public Girl(String name, int waist, int bust, int hip, int age) {
this.name = name;
this.waist = waist;
this.bust = bust;
this.age = age;
this.hip = hip;
}
private Girl(String name,Integer age){
this.name = name;
this.age=age;
}
private String getMobile(String number){
String mobile="010-110"+"-"+number;
return mobile;
}
@Override
public String toString() {
return "Girl{" +
"country='" + country + '\'' +
", city='" + city + '\'' +
", name='" + name + '\'' +
", age=" + age +
", bust=" + bust +
", waist=" + waist +
", hip=" + hip +
'}';
}
}
在该类中有一些简单的属性,比如年龄,姓名,国家,城市,腰围,胸围,臀围。还有一些简单的方法比如,构造方法Girl(String name,Integer age),获取电话号码getMobile();看到这里获取大家可能发现了:这些属性和方法有的是公有的,有的是私有的。访问属性的不同会带来哪些差异呢?带着这个小疑问,我们来看看常见的反射使用方法。
利用反射获取构造方法
/** * 利用反射获取类的构造器 * * 1 getConstructors()获取类的构造器,但获取不到私有构造器 * 2 getDeclaredConstructors()获取类的所有构造器 * 3 getDeclaredConstructor()获取指定的构造器 */ private void testGetConstructor() { try { Class clazz = Class.forName("cc.testreflection.Girl"); Constructor[] Constructors = clazz.getConstructors(); for (Constructor constructor : Constructors) { System.out.println("----> constructor=" + constructor); } Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { System.out.println("----> declaredConstructor=" + declaredConstructor); } Constructor constructor = clazz.getDeclaredConstructor(String.class, Integer.class); constructor.setAccessible(true); Girl girl = (Girl) constructor.newInstance("liuyan", Integer.valueOf(22)); System.out.println("----> girl=" + girl); } catch (Exception e) { } }
获取类所有的构造器,这个没啥可说的。那么怎么获取指定的构造器呢?一个类可能有多个重载的构造方法,它们的方法名都是一样的;所以此时需要从构造器的输入参数入手,比如:
clazz.getDeclaredConstructor(String.class, Integer.class);
就可以获取到如下的构造方法:
private Girl(String name,Integer age){ }
但是请注意该构造方法是private的,所以需要将该方法的accessible标志设置为true 表示取消语言访问检查。即:
constructor.setAccessible(true);
在获取构造方法后即可利用newInstance()创建对象,即:
Girl girl = (Girl) constructor.newInstance(“liuyan”,Integer.valueOf(22));
利用反射获取字段
/** * 利用反射操作类的字段 * 1 getFields()获取类的字段,但是获取不到私有字段 * 2 getDeclaredFields()获取类的所有字段 * 3 获取指定的字段及其type * 4 获取指定对象的某个字段值 * 5 设置指定对象的某个字段值 */ private void testGetField() { try { Class clazz = Class.forName("cc.testreflection.Girl"); Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println("----> field=" + field); } Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("----> declaredField=" + declaredField); } //获取指定的字段及其type Field field = clazz.getDeclaredField("name"); Class type = field.getType(); System.out.println("----> field=" + field + ",type=" + type); //获取指定对象的某个字段值 Girl girl = new Girl("lucy", 100, 100, 100, 18); field.setAccessible(true); String name = (String) field.get(girl); System.out.println("----> name=" + name); //设置指定对象的某个字段值 field.setAccessible(true); field.set(girl, "hanmeimei"); System.out.println("----> girl=" + girl); } catch (Exception e) { } }
此处,对于私有字段同样要先取消语言访问检查再进行操作。
利用反射获取类中的方法
/** * 利用反射获取类的方法 * 1 getMethods()获取该类及其父类的方法,但不能获取到私有方法 * 2 getDeclaredMethods()获取该类本身所声明的所有方法 * 3 反射出类中的指定方法 */ private void testGetMethod() { try { Class clazz = Class.forName("cc.testreflection.Girl"); Object object = clazz.newInstance(); Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println("----> method=" + method); } Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("----> declaredMethod=" + declaredMethod); } Method method = clazz.getDeclaredMethod("getMobile", String.class); Class returnType = method.getReturnType(); System.out.println("----> method="+method+",returnType=" + returnType); method.setAccessible(true); String mobile = (String) method.invoke(object, "678"); System.out.println("----> mobile=" + mobile); } catch (Exception e) { } }
此处,请注意getMethods()和getDeclaredMethods()的区别;除此以外,对于私有方法同样要先取消语言访问检查再进行操作。
利用反射操作数组
/** * 利用反射操作数组 * 1 利用反射修改数组中的元素 * 2 利用反射获取数组中的每个元素 */ private void testArrayClass() { int[] intArray = new int[]{5,7,9}; Array.set(intArray,0,9527); Class clazz = intArray.getClass(); if (clazz.isArray()) { int length = Array.getLength(intArray); for (int i = 0; i < length; i++) { Object object = Array.get(intArray, i); String className=object.getClass().getName(); System.out.println("----> object=" + object+",className="+className); } } }
相对于类而言,数组要简单得多;所以在利用反射操作数组时较为容易。
利用反射获取泛型的参数类型
在许多框架中有这样的需求:根据不同的泛型参数响应不同的操作。
一说到泛型参数类型,可能大家立马就想到了刚才说的泛型擦除,比如ArrayList<String>在编译后就变成了ArrayList,它原本的泛型被”擦除”了。但是我们有时确实需要知道泛型的参数类型,又该怎么来实现呢?按照刚才的那些思路恐怕是走不通了,得另辟蹊径了。/** * 利用反射获取泛型的参数类型 * * 原创作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ public void getGenericHelper(HashMap<String, Integer> hashMap) { } private Class getGenericType() { try { HashMap<String, Integer> hashMap = new HashMap<String, Integer>(); Method method = getClass().getDeclaredMethod("getGenericHelper",HashMap.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); if (null == genericParameterTypes || genericParameterTypes.length < 1) { return null; } ParameterizedType parameterizedType=(ParameterizedType)genericParameterTypes[0]; Type rawType = parameterizedType.getRawType(); System.out.println("----> rawType=" + rawType); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); if (actualTypeArguments==genericParameterTypes || actualTypeArguments.length<1) { return null; } for (int i = 0; i < actualTypeArguments.length; i++) { Type type = actualTypeArguments[i]; System.out.println("----> type=" + type); } } catch (Exception e) { } return null; }
主要步骤如下
第一步:
定义getGenericHelper()方法其输入参数为带泛型的参数,比如ArrayList<String,Integer>第二步:
利用反射获取到该getGenericHelper()方法,即:Method method=getClass().getDeclaredMethod(“getGenericHelper”,HashMap.class);
第三步:
获取到该方法的带泛型的输入参数,即:Type[] genericParameterTypes = method.getGenericParameterTypes();
注意getGenericParameterTypes()方法返回的是一个数组,因为方法可能有多个参数,但是依据我们的需求这个数组中是仅有一个元素的
第四步:
获取到该带泛型参数的所有泛型的类型,即:Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();
因为一个参数的泛型可能有多个,所以getActualTypeArguments()的返值是一个数组。
比如,此处的ArrayList<String,Integer>该参数就有两个泛型第五步:
获取每个泛型的类型,即:Type type = actualTypeArguments[i];
Type
在刚才的示例中,我们看到了一个不太熟悉的东西——Type
嗯哼,我们一起来瞅瞅这个玩意儿
Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
这是官方文档对于Type的解释,这段描述是什么意思呢?
在JDK1.5之前还没有泛型时,我们的数据都是这样的:int,double,String,User,Girl…….在泛型出来之后又多了这些东西:<T>,HashMap<String,Number>,List<String>[]…..这些新出来的数据类型又该用什么去表示呢?为了兼容和统一以前的JDK版本,这才出现了Type。
Type一共有四个子接口:
TypeVariable,ParameterizedType,GenericArrayType,WildcardType
现在这四个子接口都洗干净整整齐齐地摆在这里了,我们来挨个解读。
TypeVariable
TypeVariable称为类型变量,比如<T>、<C extends String>中的变量T、C都是TypeVariable
/** * 原创作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private HashMap<K, Integer> generichashMap = null; public void testTypeVariable() throws Exception{ Class clazz=GenericTest.class; Field field=clazz.getDeclaredField("generichashMap"); Type genericType = field.getGenericType(); if(genericType instanceof ParameterizedType){ ParameterizedType parameterizedType= (ParameterizedType) genericType; Type[] types = parameterizedType.getActualTypeArguments(); for (int i = 0; i < types.length; i++) { Type type=types[i]; if( type instanceof TypeVariable){ System.out.println("----> type="+type +"是TypeVariable"); }else{ System.out.println("----> type="+type+"不是TypeVariable"); } } } }
此处的HashMap<K, Integer> generichashMap一共涉及到两个泛型:K和Integer;其中K是TypeVariable,但Integer不是
ParameterizedType
ParameterizedType称为参数化类型,比如HashMap<K, Integer>
/** * 原创作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private HashMap<K, Integer> hashMap = null; public void testParameterizedType() throws Exception{ Class clazz=GenericTest.class; Field field=clazz.getDeclaredField("hashMap"); Type genericType =field.getGenericType(); if(genericType instanceof ParameterizedType){ ParameterizedType parameterizedType= (ParameterizedType) genericType; Type rawType = parameterizedType.getRawType(); Type ownerType = parameterizedType.getOwnerType(); System.out.println("---->rawType="+rawType+",ownerType="+ownerType); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (int i = 0; i < actualTypeArguments.length; i++) { Type type=actualTypeArguments[i]; System.out.println("---->i="+i+",type="+type); } } }
由于HashMap<K, Integer>是参数化类型,所以可用getRawType()获取其原始的类型,即:
Type rawType = parameterizedType.getRawType();
HashMap<K, Integer>的原始类型就是class java.util.HashMap
除此以外,还可以获得参数的实际类型,即:Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();
其中,第一个参数为K,第二个参数为class java.lang.Integer
GenericArrayType
GenericArrayType称为数组类型,比如T[] tArray,List<String>[] listArray这些带有泛型的数组称为GenericArrayType
class Test<T> { public void test(List<String>[] stringList) { } } public void testGenericArrayType() throws Exception{ Class clazz=Test.class; Method method = clazz.getDeclaredMethods()[0]; Type[] types = method.getGenericParameterTypes(); for (Type type : types) { boolean isGenericArrayType=(type instanceof GenericArrayType); System.out.println("------> isGenericArrayType="+isGenericArrayType); } }
那么String[] stringArray,int[] intArray是GenericArrayType么?请自行验证,将其传入此处的test( ) 方法即可。
WildcardType
WildcardType称为通配符类型,比如:? extends Integer 和 ? super String都是WildcardType
/** * 原创作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private List<? extends Number> numberList; private List<? super String> stringList; public void testWildcardType() throws Exception{ Class clazz=GenericTest.class; Field numberField = clazz.getDeclaredField("numberList"); Field stringField = clazz.getDeclaredField("stringList"); ParameterizedType numberParameterizedType=(ParameterizedType)numberField.getGenericType(); ParameterizedType stringParameterizedType=(ParameterizedType)stringField.getGenericType(); Type [] numberActualTypeArguments=numberParameterizedType.getActualTypeArguments(); WildcardType numberWildcardType = (WildcardType) numberActualTypeArguments[0]; Type [] StringActualTypeArguments=stringParameterizedType.getActualTypeArguments(); WildcardType stringWildcardType = (WildcardType) StringActualTypeArguments[0]; System.out.println("----> numberWildcardType="+numberWildcardType); System.out.println("----> stringWildcardType="+stringWildcardType); Type numberUpperBound=numberWildcardType.getUpperBounds()[0]; Type stringLowerBound=stringWildcardType.getLowerBounds()[0]; System.out.println("----> numberUpperBound="+numberUpperBound); System.out.println("----> stringLowerBound="+stringLowerBound); }
我们知道在泛型中extends 表示上限,super表示下限。比如此处:
? extends Number 的上限是class java.lang.Number
? super String 的下限是class java.lang.String
所以,可利用getUpperBounds()和getLowerBounds()获取WildcardType的上限或下限
Type除了这四个子接口,它还有一个实现类Class
Class所表示的是原始类型,或者说是泛型出现之前的类型。比如String,User,Girl,Integer等等。
注解
An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.
Annotation作为元数据可以被添加到Java源代码类、方法、变量、参数、包。虽然源码中添加了注释,但是Annotation不会直接影响程序的执行,无论增加或者删除Annotation,原代码的执行都始终如一。在中文里,常将Annotation翻译为“注解”,在以下的论述中也采用该译法。
标准注解
在Java的JDK中内置了一些系统自带的注解,这些注解也常称为标准注解,常见的有:@Override, @Deprecated, @SuppressWarnings
@Override
@Override作用于方法,表示被标注的方法重载了父类的方法。若该重载的方法写错了方法名那么在编译期就会有出现警告
看到了吧,在子类Customer中用@Override标记了方法setId()表示这个方法是重载自父类;假若把方法名错写成了setIdd()编译器就会报错了。
@Deprecated
当一个类型或者类型成员使用@Deprecated标记则表示建议不再使用该元素。
在User类中,将setId()方法标记为@Deprecated,当我们准备调用该方法时编译器就会提示该方法已经过时,建议换用其他方法
@SuppressWarnings
@SuppressWarnings翻译成中文就是抑制警告,它被用于关闭编译器对类、方法、成员变量、变量初始化的警告。
定义了一个变量testSuppressWarnings,但是该变量从未被使用,所以提示了一个警告:private field ‘testSuppressWarnings’ is never used。为了抑制该警告,我们给testSuppressWarnings添加一个注解:
@SuppressWarnings(“unused”)
该注解就表示不再警告变量未被使用。
除了该处使用的unused以外@SuppressWarnings还有许多其他参数,比如:
serial:可序列化的类上缺少serialVersionUID定义的警告
finally:finally语句不能正常完成的警告
deprecation:使用了过时的类型或者类型成员方法时的警告
unchecked:执行了未检查的转换的警告
all:所有情况的警告。自定义注解
除了使用系统提供的注解,我们当然也可以自定义注解。
自定义注解和创建接口非常相似,但注解需要以@开头。方法体中的每一个方法实际上是声明了一个属性,其中方法名是属性的名称,方法的返回值类型就是属性的类型(返回值类型只能是基本类型、String、enum、Class)。当然也可以通过default来声明属性的默认值。
比如:public @interface TestAnnotation { public String name(); public int age() default 20; }
如上代码就定义了一个自定义的注解TestAnnotation,它有两个属性name和age,并且age的默认值为20
有时注解中只需要一个属性,为简便起见可将该属性命名为value,比如:@Retention(RetentionPolicy.RUNTIME) public @interface ValueAnnotation { String value(); }
如上代码就定义了一个自定义的注解ValueAnnotation,并且该注解只有一个属性value。其实,@SuppressWarnings也与此类似。
在定义完成之后,现再将这个两个自定义的注解作用于类@TestAnnotation(name="lucy") @ValueAnnotation("lucy9527") public class Beauty { }
在这段代码中为类Beauty添加了两个注解。在使用注解TestAnnotation时采用键值对的形式为属性name赋值,在使用注解ValueAnnotation时由于其只有一个属性value,所以可将value = “lucy9527”简写成”lucy9527”。
好了,在一个类上使用了自定义注解,现再瞅瞅怎么样把这些注解的属性值提取出来
/** * * 提取自定义注解的属性 * * 原创作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private void getAnnotationValue(){ TestAnnotation annotation=null; Class clazz=Beauty.class; boolean isPresent= clazz.isAnnotationPresent(TestAnnotation.class); if(isPresent){ annotation = (TestAnnotation)clazz.getAnnotation(TestAnnotation.class); String name=annotation.name(); int age=annotation.age(); System.out.println("----> annotation=" + annotation); System.out.println("----> name=" + name+",age="+age); } } /** * 提取自定义注解的属性 */ private void getAnnotationDefaultValue(){ ValueAnnotation annotation=null; Class clazz=Beauty.class; boolean isPresent= clazz.isAnnotationPresent(TestAnnotation.class); isPresent= clazz.isAnnotationPresent(ValueAnnotation.class); if(isPresent){ annotation= (ValueAnnotation) clazz.getAnnotation(ValueAnnotation.class); String value=annotation.value(); System.out.println("----> defaultValue=" + value); } }
主要步骤如下
第一步:
先判断该注解是否存在,即:clazz.isAnnotationPresent(TestAnnotation.class);
第二步:
获取注解,即:annotation =(TestAnnotation)clazz.getAnnotation(TestAnnotation.class);
第三步:
获取注解的属性值,即:String name=annotation.name();
int age=annotation.age();输出结果:
—-> annotation=@cc.testreflection.TestAnnotation(age=20, name=lucy)
—-> name=lucy,age=20
—-> defaultValue=lucy9527元注解
刚才通过三个系统的标准注解和自定义注解我们看到:注解是用来标记或者说明类,方法,变量的。
与此类似,Java还提供了元注解用于标记注解。
常见的元注解有:@Target、@Retention、@Documented、@Inherited@Target
@Target用于确定Annotation所修饰的对象范围。我们知道Annotation可用于packages、types(类、接口、枚举)、类型成员(方法、成员变量、枚举值)、方法参数等等。所以,可用@Target表示Annotation修饰的目标。
比如@Override的源码:@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
在此使用了元注解@Target表示@Override只能用于修饰方法。
除ElementType.METHOD以外,@Target还可以使用其他的值用于表示注解的修饰对象,比如:
CONSTRUCTOR:用于描述构造器
FIELD:用于描述类成员变量
LOCAL_VARIABLE:用于描述局部变量
PACKAGE:用于描述包
PARAMETER:用于描述参数@Retention
@Retention定义了Annotation的有效范围,类似于Android中常提到的生命周期。Java文件从生产到执行,要经过三个主要的阶段:java源文件,Class文件,JVM运行。与此类似,有的Annotation仅出现在源代码中而被编译器丢弃,而另一些却被编译在Class文件中;有的编译在Class文件中的Annotation在运行时会被虚拟机忽略,而另一些在运行时被读取读取。
所以,在@Retention中使用RetentionPoicy标明注解会存留到哪个阶段,RetentionPoicy有三个值:
SOURCE:在源文件中有效(即仅在源文件保留)
CLASS:在Class文件中有效(即Class保留)
RUNTIME:在运行时有效(即保留至运行时)比如@Deprecated的源码:
@Documented @Retention(RetentionPolicy.RUNTIME) public @interface Deprecated { }
在这段源码中用@Retention(RetentionPolicy.RUNTIME)标明该注解会保留至运行时。
@Documented
@Documented表示在生成javadoc文档时将该Annotation也写入到帮助文档。
比如有一个注解:@Target(ElementType.METHOD) @Documented public @interface TestDocumented { String name(); }
在此使用@Target(ElementType.METHOD)表示这个注解是用来修饰方法的,并且为该注解添加了元注解@Documented
public class CommonClass { /** * This is a java method */ @TestDocumented(name = "google") public void testMethod() { } }
然后在一个方法上使用注解@DocumentTest
@TestDocumented(name = “google”)
public void testMethod()
This is a java method最后生成类似如上所示的帮助文档
@Inherited
@Inherited用于指示注释类型被自动继承。
请参见如下完整示例
第一步:
定义一个注解,并在该注解上使用了元注解@Inherited@Retention(RetentionPolicy.RUNTIME) @Inherited public @interface InheritedAnnotation { String value( ); }
第二步:
定义一个父类,并且在该类上使用了自定义注解@InheritedAnnotation@InheritedAnnotation("@InheritedAnnotation on class of ParentClass") public class ParentClass { public void print() { System.out.println("----> This is parent print( )"); } }
第三步:
定义一个子类继承自父类。public class ChildClass extends ParentClass { }
第四步:
测试子类是否继承父类中类上的注解public void testInheritedOnClass(){ InheritedAnnotation annotation=null; Class clazz = ChildClass.class; Class annotationClass=InheritedAnnotation.class; boolean isPresent=clazz.isAnnotationPresent(annotationClass); if (isPresent) { annotation = (InheritedAnnotation) clazz.getAnnotation(annotationClass); String value = annotation.value(); System.out.println("----> value=" + value); } }
输出结果为:
—-> value=@InheritedAnnotation on class of ParentClass
后语
这篇文章有点长,内容不少,所以能看到这里的人可能不多了。
有人戏谑地说:26个英语字母每个我都认识,但是它们其中几个组合在一起我就不认识了。在开发中,我们在刚面对一个复杂的功能时有些不知所措;但在几经周折完成后再回过头来看时发现:这个功能涉及到的技术实际上不是特别难,只不过它将各方面的技术进行了合理的组合和综合的运用。框架又何尝不是如此呢?正所谓,万丈高楼平地起;再高的楼也是从地下室慢慢地一层层地往上修的。当我们还没有能力去建一幢大厦的时候,就应该在太阳底下多捡几块砖,多箍几捆钢筋,暗暗地积蓄。假以时日,这些材料终将成为大厦的基石。
好了,不多说了,搬砖去;工头又在催我了