編輯:關於Android編程
前言
經過幾年的發展和沉澱,Android開發中湧現出許多優秀的框架,比如:Retrofit、Afinal、OKHttp、ButterKnife、AndFix等等。這些框架的出現極大地簡化了開發流程,提高了工作效率。在項目開發的過程中我們主要是使用這些輪子完成項目,很難有時間去顧及框架的內部實現。在項目交付之後我們可能就要去看看這些框架的源碼了。
這些主流框架的功能各不相同,但每當打開浩繁的源碼時我們幾乎都可以看到反射,注解,泛型的廣泛應用;也正是這些技術使得框架具有了高度的靈活性,優良的擴展性和健壯的穩定性。鑒於這些框架必備知識的重要性故在此對這部分內容做一個全面的梳理和總結。
主要內容:
ClassLoaderClassLoader
在程序運行時首先要將類加載到內存中,這個加載工作就是由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<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrXa0ru49kNsYXNzTG9hZGVyo6zI58/Co7o8L3A+DQo8YmxvY2txdW90ZT4NCgk8cD5kYWx2aWsuc3lzdGVtLlBhdGhDbGFzc0xvYWRlcltEZXhQYXRoTGlzdFtbemlwIGZpbGU8YnIgLz4NCgkmbGRxdW87L2RhdGEvYXBwL2NjLnRlc3RyZWZsZWN0aW9uLTIvYmFzZS5hcGsmcmRxdW87XSxuYXRpdmVMaWJyYXJ5RGlyZWN0b3JpZXM9Wy92ZW5kb3IvbGliLC9zeXN0ZW0vbGliXV1dPC9wPg0KPC9ibG9ja3F1b3RlPg0KPHA+uMNQYXRoQ2xhc3NMb2FkZXLU2tOm08PG9LavyrG0tL2oo6zTw9PavNPU2C9kYXRhL2FwcC9jYy50ZXN0cmVmbGVjdGlvbi0yL2Jhc2UuYXBr1tC1xMDgoaM8L3A+DQo8cD5DbGFzc0xvYWRlcsrH0ru49rPpz/PA4KOsy/zT0Mj9uPazo9PDtcTX08Dgo7pQYXRoQ2xhc3NMb2FkZXKholVSTENsYXNzTG9hZGVyoaJEZXhDbGFzc0xvYWRlcqGjPC9wPg0KPHA+UGF0aENsYXNzTG9hZGVyPGJyIC8+DQrL/Na7xNy809TY0tG+rbCy17C1xGFwa9bQtcTXytS0o6yxyMjnZGV4zsS8/jxiciAvPg0KVVJMQ2xhc3NMb2FkZXI8YnIgLz4NCsv81rvE3NPD09q809TYamFyzsS8/tbQtcTXytS0oaO1q8rHZGFsdmlrsrvE3NaxvdPKtrHwamFyo6zL+dLU1eK49rzT1NjG97yrydnKudPDoaM8YnIgLz4NCkRleENsYXNzTG9hZGVyPGJyIC8+DQrL/NPD09q00y5qYXK6zS5hcGvA4NDNtcTOxLz+xNqyv7zT1NhjbGFzc2VzLmRleKGjuMPA4LzT1NjG97Oj08PAtM3qs8m2r8ysvNPU2GFwa7XE0OjH86GjPC9wPg0KPHA+tdq2/rj2Q2xhc3NMb2FkZXKjrMjnz8KjujwvcD4NCjxibG9ja3F1b3RlPg0KCTxwPmNsYXNzTG9hZGVyPWphdmEubGFuZy5Cb290Q2xhc3NMb2FkZXJAMjFiNzM3ZmQ8L3A+DQo8L2Jsb2NrcXVvdGU+DQo8cD64w0Jvb3RDbGFzc0xvYWRlctTaz7XNs8b0tq+1xMqxuvK0tL2oLNPD09q809TYz7XNs7LjvLa1xMDgoaM8L3A+DQo8cD7U2sjPyrbBy9Xiwb3W1sDgvNPU2Mb31q6686OsztLDx7+0v7TL/MPHtcTTptPDoaM8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> /** * 原創作者: * 谷哥的小弟 * * 博客地址: * 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,從那以後大家在項目常常使用泛型,比如:
ArrayListarrayList=new ArrayList (); for(int i=0;i<10;i++){ Girl girl =new Girl(); arrayList.add(girl); }
在與此類似的場景中利用泛型限定了集合中的輸入類型,從而讓編譯器屏蔽了源程序中的非法數據輸入,比如此時往ArrayList
剛才已經說了,泛型主要是給編譯器看的;那麼在編譯完成之後生成的字節碼裡泛型會發生什麼變化呢?來看個例子:
private void testArraylistClass() { Class clazz1 = new ArrayList().getClass(); Class clazz2 = new ArrayList ().getClass(); boolean isEqual=(clazz1 == clazz2); System.out.println("----> isEqual=" +isEqual); }
輸出結果:
—-> isEqual=true
哇哈!看到了沒有?——帶不同泛型的ArrayList在編譯後生成的Class是相同的!也就是說,泛型在編譯生成字節碼文件時會被”擦除”;不管ArrayList
我們再來個更直觀的:
請注意報錯:both methods have same erasure
因為泛型擦除後這兩個方法就是變成了完全相同了,當然也就不是我們認為的方法重載了,所以Android Studio直接就提示了這個錯誤。
嗯哼,在認識了泛型的擦除之後,我們來體驗一下非一般的感覺:
private void testArraylistGeneric(){ try { ArrayListarrayList =new ArrayList (); arrayList.add(9527); arrayList.add(9528); Method method=arrayList.getClass().getMethod("add",Object.class); method.invoke(arrayList,"hello,java"); for (int i=0;i
輸出結果如下圖所示:
看到了吧,之所以能把一個字符串add到該ArrayList
嗯哼,接下來瞅瞅自定義泛型。
自定義泛型方法
public staticT genericMethod1(T t) { return null; } public K genericMethod2(K k, V v) { return null; } public String genericMethod3(K k, V v) { return null; }
在自定義泛型方法時,請注意在方法的返回值之前聲明一個泛型,比如:
自定義泛型接口
public interface UserInfo{ public void printUserInfo(T t); } private class UserInfoImpl implements UserInfo { @Override public void printUserInfo(T t) { } }
在自定義泛型接口時,請注意在接口名之後聲明一個泛型,比如:
自定義泛型類
public class Collection{ 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
/** * 利用反射獲取泛型的參數類型 * * 原創作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ public void getGenericHelper(HashMaphashMap) { } private Class getGenericType() { try { HashMap hashMap = new HashMap (); 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
第二步:
利用反射獲取到該getGenericHelper()方法,即:
Method method=getClass().getDeclaredMethod(“getGenericHelper”,HashMap.class);
第三步:
獲取到該方法的帶泛型的輸入參數,即:
Type[] genericParameterTypes = method.getGenericParameterTypes();
注意getGenericParameterTypes()方法返回的是一個數組,因為方法可能有多個參數,但是依據我們的需求這個數組中是僅有一個元素的
第四步:
獲取到該帶泛型參數的所有泛型的類型,即:
Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();
因為一個參數的泛型可能有多個,所以getActualTypeArguments()的返值是一個數組。
比如,此處的ArrayList
第五步:
獲取每個泛型的類型,即:
Type type = actualTypeArguments[i];
Type
在剛才的示例中,我們看到了一個不太熟悉的東西——Type
嗯哼,我們一起來瞅瞅這個玩意兒
Type是Java編程語言中所有類型的公共高級接口。它們包括原始類型、參數化類型、數組類型、類型變量和基本類型。
這是官方文檔對於Type的解釋,這段描述是什麼意思呢?
在JDK1.5之前還沒有泛型時,我們的數據都是這樣的:int,double,String,User,Girl…….在泛型出來之後又多了這些東西:
Type一共有四個子接口:
TypeVariable,ParameterizedType,GenericArrayType,WildcardType
現在這四個子接口都洗干淨整整齊齊地擺在這裡了,我們來挨個解讀。
TypeVariable
TypeVariable稱為類型變量,比如
/** * 原創作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private HashMapgenerichashMap = 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
ParameterizedType
ParameterizedType稱為參數化類型,比如HashMap
/** * 原創作者: * 谷哥的小弟 * * 博客地址: * http://blog.csdn.net/lfdfhl */ private HashMaphashMap = 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
Type rawType = parameterizedType.getRawType();
HashMap
除此以外,還可以獲得參數的實際類型,即:
Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();
其中,第一個參數為K,第二個參數為class java.lang.Integer
GenericArrayType
GenericArrayType稱為數組類型,比如T[] tArray,List
class Test{ public void test(List [] 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 numberList; private List 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();
輸出結果:
—-> [email protected](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個英語字母每個我都認識,但是它們其中幾個組合在一起我就不認識了。在開發中,我們在剛面對一個復雜的功能時有些不知所措;但在幾經周折完成後再回過頭來看時發現:這個功能涉及到的技術實際上不是特別難,只不過它將各方面的技術進行了合理的組合和綜合的運用。框架又何嘗不是如此呢?正所謂,萬丈高樓平地起;再高的樓也是從地下室慢慢地一層層地往上修的。當我們還沒有能力去建一幢大廈的時候,就應該在太陽底下多撿幾塊磚,多箍幾捆鋼筋,暗暗地積蓄。假以時日,這些材料終將成為大廈的基石。
結構型模式中的適配器模式、外觀模式、裝飾模式、代理模式都屬於包裝模式,都是對另外的類或對象的包裝,只是各自的意圖不同。
正文結構型包含以下類型:適配器 Adapter Class/Object 橋接 Bridge 組合 Composite 裝飾 Decorator 外觀 Facade 享元
這篇來介紹一下工廠方法模式(Factory Method Pattern),在實際開發過程中我們都習慣於直接使用 new 關鍵字用來創建一個對象,可是有時候對象的創造需要
本文實例講述了Android編程重寫ViewGroup實現卡片布局的方法。分享給大家供大家參考,具體如下:實現效果如圖:實現思路1. 重寫onMeasure(int wi