標籤:pre declare reflect code 產生 情況 tor row 檔案
建議的採用順序是List中泛型順序依次為T、?、Object
(1)、List是確定的某一個類型
List表示的是List集合中的元素都為T類型,具體類型在運行期決定;List<?>表示的是任意類型,與List類似,而List則表示List集合中的所有元素為Object類型,因為Object是所有類的父類,所以List也可以容納所有的類類型,從這一字面意義上分析,List更符合習慣:編碼者知道它是某一個類型,只是在運行期才確定而已。
(2)List可以進行讀寫操作
List可以進行諸如add,remove等操作,因為它的類型是固定的T類型,在編碼期不需要進行任何的轉型操作。
List是唯讀類型的,不能進行增加、修改操作,因為編譯器不知道List中容納的是什麼類型的元素,也就無法校正類型是否安全了,而且List<?>讀取出的元素都是Object類型的,需要主動轉型,所以它經常用於泛型方法的傳回值。注意List<?>雖然無法增加,修改元素,但是卻可以刪除元素,比如執行remove、clear等方法,那是因為它的刪除動作與泛型型別無關。
List 也可以讀寫操作,但是它執行寫入操作時需要向上轉型(Up cast),在讀取資料的時候需要向下轉型,而此時已經失去了泛型存在的意義了。
嚴格限定泛型型別採用多重界限
List介面的toArray方法可以把一個集合轉化為數組,但是使用不方便,toArray()方法返回的是一個Object數組,所以需要自行轉變。 當一個泛型類(特別是泛型集合)轉變為泛型數組時,泛型數組的真實類型不能是泛型的父類型(比如頂層類Object),只能是泛型型別的子類型(當然包括自身類型),否則就會出現類型轉換異常。 通過反射類Array聲明了一個T類型的數組,由於我們無法在運行期獲得泛型型別的參數,因此就需要調用者主動傳入T參數類型。 List轉數組:
public static <T> T[] toArray(List<T> list,Class<T> tClass) {//聲明並初始化一個T類型的數組T[] t = (T[])Array.newInstance(tClass, list.size());for (int i = 0, n = list.size(); i < n; i++) {t[i] = list.get(i);}return t;}
注意Class類的特殊性
class類的特殊性:
無建構函式:Java中的類一般都有建構函式,用於建立執行個體對象,但是Class類卻沒有建構函式,不能執行個體化,Class對象是在載入類時由Java虛擬機器通過調用類載入器中的difineClass方法自動構造的。 可以描述基本類型:雖然8個基本類型在JVM中並不是一個對象,它們一般存在於棧記憶體中,但是Class類仍然可以描述它們,例如可以使用int.class表示int類型的類對象。 其對象都是單例模式:一個Class的執行個體對象描述一個類,並且只描述一個類,反過來也成立。一個類只有一個Class執行個體對象
獲得Class對象的三種途徑:
類屬性方式:如String.class 對象的getClass方法,如new String().getClass() forName方法載入:如Class.forName(" java.lang.String") 獲得了Class對象後,就可以通過getAnnotations()獲得註解,通過getMethods()獲得方法,通過getConstructors()獲得建構函式等
適時選擇getDeclaredXXX和getXXX
getXXX法獲得的是所有public存取層級的方法,包括從父類繼承的方法,而getDeclaredXXX獲得的是自身類的方法,包括公用的(public)方法、私人(private)方法,而且不受限於存取權限。 如果需要列出所有繼承自父類的方法,該如何?呢?簡單,先獲得父類,然後使用getDeclaredMethods,之後持續遞迴即可。
反射訪問屬性或方法時將Accessible設定為true
通過反射執行一個方法的過程如下:
擷取一個方法對象;
然後根據isAccessible傳回值確定是否能夠執行,如果傳回值為false則需要調用setAccessible(true);
最後再調用invoke執行方法
Method method= ...; //檢查是否可以訪問 if(!method.isAccessible()){ method.setAccessible(true); } //執行方法 method.invoke(obj, args);
AccessibleObject源碼:
public class AccessibleObject implements AnnotatedElement { //定義反射的預設操作許可權suppressAccessChecks static final private java.security.Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks"); //是否重設了安全檢查,預設為false boolean override; //建構函式 protected AccessibleObject() {} //是否可以快速擷取,預設是不能 public boolean isAccessible() { return override; } }
AccessibleObject是Filed、Method、Constructor的父類,決定其是否可以快速存取而不進行存取控制檢查,在AccessibleObject類中是以override變數儲存該值的,但是具體是否快速執行時在Method的invoke方法中決定的:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { //是否可以快速擷取,其值是父類AccessibleObject的override變數 if (!override) { //不能快速擷取,執行安全檢查 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(1); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } //直接執行方法 return ma.invoke(obj, args); }
Accessible屬性只是用來判斷是否需要進行安全檢查的,如果不需要則直接執行,這就可以大幅度的提升系統效能了(當然了,取消了安全檢查,也可以運行private方法、訪問private屬性的)。經過測試,在大量的反射情況下,設定Accessible為true可以提高效能20倍左右。 Accessible屬性決定Field和Constructor是否受存取控制檢查。我們在設定Field或執行Constructor時,務必要設定Accessible為true。
使用forName動態載入類檔案
動態載入(Dynamic Loading)是指在程式運行時載入需要的類庫檔案,對Java程式來說,一般情況下,一個類檔案在啟動時或首次初始化時會被載入到記憶體中,而反射則可以在運行時再決定是否需要載入一個類。 舉個栗子:
public class Client103 { public static void main(String[] args) throws ClassNotFoundException { //動態載入 Class.forName("com.study.advice103.Utils"); }}class Utils{ //靜態代碼塊 static{ System.out.println("Do Something....."); }}
結果: Do Something.....
forName只是把一個類載入到記憶體中,並不保證由此產生一個執行個體對象,也不會執行任何方法,之所以會初始化static代碼,那是由類載入機制所決定的,而不是forName方法決定的。也就是說,如果沒有static屬性或static代碼塊,forName就是載入類,沒有任何的執行行為。
動態載入不適合數組
數組是一個非常特殊的類,雖然它是一個類,但沒有定義類類路徑。
String [] strs = new String[10]; Class.forName("java.lang.String[]");
會產生bug:
Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[] at java.lang.Class.forName0(Native Method)
動態載入數組:
// 動態建立數組 String[] strs = (String[]) Array.newInstance(String.class, 8); // 建立一個多維陣列 int[][] ints = (int[][]) Array.newInstance(int.class, 2, 3);
編寫高品質代碼:改善Java程式的151個建議 --[98~105]