List<T>、List<?>、List<Object>這三者均可以容納全部的對象,但使用的順序應該是首選List<T>,次之List<?>,最後選擇List<Object>,緣由以下:java
(1)、List<T>是肯定的某一個類型數組
List<T>表示的是List集合中的元素都爲T類型,具體類型在運行期決定;List<?>表示的是任意類型,與List<T>相似,而List<Object>則表示List集合中的全部元素爲Object類型,由於Object是全部類的父類,因此List<Object>也能夠容納全部的類類型,從這一字面意義上分析,List<T>更符合習慣:編碼者知道它是某一個類型,只是在運行期才肯定而已。安全
(2)List<T>能夠進行讀寫操做ide
List<T>能夠進行諸如add,remove等操做,由於它的類型是固定的T類型,在編碼期不須要進行任何的轉型操做。函數
List<T>是隻讀類型的,不能進行增長、修改操做,由於編譯器不知道List中容納的是什麼類型的元素,也就沒法校驗類型是否安全了,並且List<?>讀取出的元素都是Object類型的,須要主動轉型,因此它常常用於泛型方法的返回值。注意List<?>雖然沒法增長,修改元素,可是卻能夠刪除元素,好比執行remove、clear等方法,那是由於它的刪除動做與泛型類型無關。編碼
List<Object> 也能夠讀寫操做,可是它執行寫入操做時須要向上轉型(Up cast),在讀取數據的時候須要向下轉型,而此時已經失去了泛型存在的意義了。spa
打個比方,有一個籃子用來容納物品,好比西瓜,番茄等.List<?>的意思是說,「嘿,我這裏有一個籃子,能夠容納固定類別的東西,好比西瓜,番茄等」。List<?>的意思是說:「嘿,我有一個籃子,我能夠容納任何東西,只要是你想獲得的」。而List<Object>就更有意思了,它說" 嘿,我也有一個籃子,我能夠容納全部物質,只要你認爲是物質的東西均可以容納進來 "。code
推而廣之,Dao<T>應該比Dao<?>、Dao<Object>更先採用,Desc<Person>則比Desc<?>、Desc<Object>更優先採用。對象
從哲學來講,很難描述一個具體的人,你能夠描述他的長相、性格、工做等,可是人都是由多重身份的,估計只有使用多個And(與操做)將全部的描述串聯起來才能描述一個完整的人,好比我,上班時我是一個職員,下班了坐公交車我是一個乘客,回家了我是父母的孩子,是兒子的父親......角色時刻在變換。那若是咱們要使用Java程序來對一類人進行管理,該如何作呢?好比在公交車費優惠系統中,對部分人員(如工資低於2500元的上班族而且是站立的乘客)車費打8折,該如何實現呢?blog
注意這裏的類型參數有兩個限制條件:一個爲上班族;二爲乘客。具體到咱們的程序中就應該是一個泛型參數具備兩個上界(Upper Bound),首先定義兩個接口及實現類,代碼以下:
1 interface Staff { 2 // 工資 3 public int getSalary(); 4 } 5 6 interface Passenger { 7 // 是不是站立狀態 8 public boolean isStanding(); 9 } 10 //定義我這個類型的人 11 class Me implements Staff, Passenger { 12 13 @Override 14 public boolean isStanding() { 15 return true; 16 } 17 18 @Override 19 public int getSalary() { 20 return 2000; 21 } 22 23 }
"Me"這種類型的人物有不少,好比系統分析師也是一個職員,也坐公交車,但他的工資實現就和我不一樣,再好比Boss級的人物,偶爾也坐公交車,對大老闆來講他也只是一個職員,他的實現類也不一樣,也就是說若是咱們使用「T extends Me」是限定不了需求對象的,那該怎麼辦呢?能夠考慮使用多重限定,代碼以下:
public class Client99 { //工資低於2500的而且站立的乘客車票打8折 public static <T extends Staff & Passenger> void discount(T t) { if (t.getSalary() < 2500 && t.isStanding()) { System.out.println(" 恭喜您,您的車票打八折!"); } } public static void main(String[] args) { discount(new Me()); } }
使用「&」符號設定多重邊界,指定泛型類型T必須是Staff和Passenger的共有子類型,此時變量t就具備了全部限定的方法和屬性,要再進行判斷就一如反掌了。在Java的泛型中,可使用"&"符號關聯多個上界並實現多個邊界限定,並且只有上界纔有此限定,下界沒有多重限定的狀況。想一想你就會明白:多個下界,編碼者可自行推斷出具體的類型,好比「? super Integer」 和 「? extends Double」,能夠更細化爲Number類型了,或者Object類型了,無需編譯器推斷了。
爲何要說明多重邊界?是由於編碼者太少使用它了,好比一個判斷用戶權限的方法,使用的是策略模式(Strategy Pattern) ,示意代碼以下:
1 class UserHandler<T extends User> { 2 // 判斷用戶是否有權限執行操做 3 public boolean permit(T user, List<Job> jobs) { 4 List<Class<?>> iList = Arrays.asList(user.getClass().getInterfaces()); 5 // 判斷 是不是管理員 6 if (iList.indexOf(Admin.class) > -1) { 7 Admin admin = (Admin) user; 8 // 判斷管理員是否有此權限 9 } else { 10 // 判斷普通用戶是否有此權限 11 } 12 return false; 13 } 14 } 15 16 class User {} 17 18 class Job {} 19 20 class Admin extends User {}
此處進行了一次泛型參數類別判斷,這裏不只僅違背了單一職責原則(Single Responsibility Principle),並且讓泛型很「汗顏」 :已經使用了泛型限定參數的邊界了,還要進行泛型類型判斷。事實上,使用多重邊界能夠很方便的解決此問題,並且很是優雅,建議你們 在開發中考慮使用多重限定。
List接口的toArray方法能夠把一個集合轉化爲數組,可是使用不方便,toArray()方法返回的是一個Object數組,因此須要自行轉變。toArray(T[] a)雖然返回的是T類型的數組,可是還須要傳入一個T類型的數組,這也挺麻煩的,咱們指望輸入的是一個泛型化的List,這樣就能轉化爲泛型數組了,來看看能不能實現,代碼以下:
public static <T> T[] toArray(List<T> list) { T[] t = (T[]) new Object[list.size()]; for (int i = 0, n = list.size(); i < n; i++) { t[i] = list.get(i); } return t; }
上面要輸出的參數類型定義爲Object數組,而後轉型爲T類型數組,以後遍歷List賦值給數組的每一個元素,這與ArrayList的toArray方法很相似(注意只是相似),客戶端的調用以下:
public static void main(String[] args) { List<String> list = Arrays.asList("A","B"); for(String str :toArray(list)){ System.out.println(str); } }
編譯沒有任何問題,運行後出現以下異常:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at com.study.advice100.Client100.main(Client100.java:16)
類型轉換異常,也就是說不能把一個Object數組轉換爲String數組,這段異常包含了兩個問題:
public static Object[] toArrayTwo(List list) { // 此處的強制類型轉換不必存在,只是爲了與源代碼對比 Object[] t = (Object[]) new Object[list.size()]; for (int i = 0, n = list.size(); i < n; i++) { t[i] = list.get(i); } return t; } public static void main(String[] args) { List<String> list = Arrays.asList("A", "B"); for (String str : (String [])toArrayTwo(list)) { System.out.println(str); } }
閱讀完此段代碼後就很清楚了:toArray方法返回後進行一次類型轉換,Object數組轉換成了String數組,因而就報ClassCastException異常了。
Object數組不能轉爲String數組,T類型又沒法在運行期得到,那該如何解決這個問題呢?其實,要想把一個Object數組轉換爲String數組,只要Object數組的實際類型也就是String就能夠了,例如:
// objArray的實際類型和表面類型都是String數組 Object[] objArray = { "A", "B" }; // 拋出ClassCastException String[] strArray = (String[]) objArray; String[] ss = { "A", "B" }; //objs的真實類型是String數組,顯示類型爲Object數組 Object objs[] =ss; //順利轉換爲String數組 String strs[]=(String[])objs;
明白了這個問題,咱們就把泛型數組聲明爲泛型的子類型吧!代碼以下:
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; }
經過反射類Array聲明瞭一個T類型的數組,因爲咱們沒法在運行期得到泛型類型的參數,所以就須要調用者主動傳入T參數類型。此時,客戶端再調用就不會出現任何異常了。
在這裏咱們看到,當一個泛型類(特別是泛型集合)轉變爲泛型數組時,泛型數組的真實類型不能是泛型的父類型(好比頂層類Object),只能是泛型類型的子類型(固然包括自身類型),不然就會出現類型轉換異常。
Java語言是先把Java源文件編譯成後綴爲class的字節碼文件,而後再經過ClassLoader機制把這些類文件加載到內存中,最後生成實例執行的,這是Java處理的基本機制,可是加載到內存中的數據的如何描述一個類的呢?好比在Dog.class文件中定義一個Dog類,那它在內存中是如何展示的呢?
Java使用一個元類(MetaClass)來描述加載到內存中的類數據,這就是Class類,它是一個描述類的類對象,好比Dog.class文件加載到內存中後就會有一個class的實例對象描述之。由於是Class類是「類中類」,也就有預示着它有不少特殊的地方:
// 類的屬性class所引用的對象與實例對象的getClass返回值相同 boolean b1=String.class.equals(new String().getClass()); boolean b2="ABC".getClass().equals(String.class); // class實例對象不區分泛型 boolean b3=ArrayList.class.equals(new ArrayList<String>().getClass());
Class類是Java的反射入口,只有在得到了一個類的描述對象後才能動態的加載、調用,通常得到一個Class對象有三種途徑:
得到了Class對象後,就能夠經過getAnnotations()得到註解,經過getMethods()得到方法,經過getConstructors()得到構造函數等,這位後續的反射代碼鋪平了道路。