1.泛型的創造者讓泛型的使用者能夠在使用泛型的時候根據傳入泛型類型的不一樣而使用對應類型的API。java
2.使用泛型能夠解決沒必要要的類型轉換錯誤。android
針對第一點:舉系統用到泛型的兩個例子:程序員
例子一:findViewById()面試
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
複製代碼
這個方法應該是咱們平常開發中最經常使用的方法了,能夠看到根據咱們傳入的id最終解析找到返回與之對應的一個View。 層層深刻最終找到源頭:數組
protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
return null;
}
複製代碼
調用時:bash
TextView textView= findViewById(R.id.title);
textView.setText("123");
ImageView imageView= findViewById(R.id.image);
imageView.setImageResource(R.drawable.ic_smoll_android);
複製代碼
能夠看到咱們並無顯示的獲取或者建立對應的TextView或者ImageView實例就直接獲取到了對應的類型從而調用到了對應類型的API,好比TextView的setText方法和ImageView的setImageResource方法。app
這裏的泛型創造者也就是谷歌寫View的這個程序員,泛型的使用者天然就是咱們自身調用findViewById這個方法的人了,因爲這是一個泛型方法,咱們最終不用new TextView就能夠獲取對應的TextView實例,由於泛型的創造者已經幫咱們將新建的邏輯寫好了,咱們只須要根據須要用不用的View類型去接收就能夠獲取到對應類型的實例,這無疑是至關方便的。 這就是根據咱們傳入類型不一樣而獲取調用到對應類型API的一個最好的例子。性能
例子二:系統的Comparable接口ui
public interface Comparable<T> {
public int compareTo(T o);
}
複製代碼
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
...
public native int compareTo(String anotherString);
}
複製代碼
public final class Integer extends Number implements Comparable<Integer> {
...
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
}
複製代碼
能夠看到String和Integer類實現Comparable接口後經過傳入不一樣的泛型類型String,Integer從而在複寫compareTo方法時即可以調用到傳入的類型String,Integer的API。this
List list = new ArrayList();
list.add("123");
int a = (int) list.get(0);//須要作類型強轉,在運行時,ClassCastException
複製代碼
能夠看到若是沒有泛型在運行時有可能會出現這樣沒必要要的類型轉換錯誤,使用泛型之後在編譯時就將類型約束好了從而解決了這種問題的產生。
Java中的泛型類型在代碼運行時會被擦除掉(通常狀況下會被擦成Object類型,若是使用了上限通配符的話會被擦成extends右邊的類型,如T extends View則最終會被擦成View類型),也就是說泛型只在編譯期起做用。
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); true
複製代碼
能夠看到最後結果是true的,由於泛型類型在運行時都會被擦除掉,也就是說其實c1和c2是由相同的class字節碼文件加載出來的,他們是相同的Class,new ArrayList()和new ArrayList()最後都會被擦成new ArrayList()。
最主要仍是出於兼容性的考慮,泛型是JDK1.5之後才引入的,爲了兼容以前的JDK版本因此在運行時將泛型類型都擦掉以保證和以前JDK版本的java字節碼相同。
public class Test<T> {
private T b;
public void setB(T b) {
this.b = b;
}
public T getB() {
return b;
}
}
Test<Integer> a=new Test<Integer>();
a.setB(1);
int b=a.getB();//不須要作類型強轉,自動完成
複製代碼
//定義處已經被擦除成Object,沒法進行強轉,不知道強轉成什麼public T getB();
Code:
0: aload_0
1: getfield #23 // Field b:Ljava/lang/Object;
4: areturn
//調用處利用checkcast進行強轉
L5 {
aload1
invokevirtual com/ljj/A getB()Ljava.lang.Object);
checkcast java/lang/Integer
invokevirtual java/lang/Integer intValue(()I);
istore2
}
複製代碼
能夠看到若是使用了泛型,在運行期間是會自動進行類型強轉的而不用咱們主動去調用類型強轉。
泛型類是在實例化類的對象時才能肯定的類型,其定義譬如 class Test {},在實例化該類時必須指明泛型 T 的具體類型。
泛型接口與泛型類同樣,其定義譬如 interface Generator { E dunc(E e); }。
泛型方法是獨立的,它能夠不依附與你當前類中的泛型,當前類沒有泛型也可使用泛型方法。使用的時候將泛型類型定義到方法返回值的左邊,以後就可使用了。
<T> void func(T val) {}
<T> T func(Fruit val) {}
public static <T> void func(T val) {}
複製代碼
下邊舉一個例子
public class Test{
public static <T> T add(T x, T y){
return y;
}
public static void main(String[] args) {
int t0 = Test.add(10, 20.8);
int t1 = Test.add(10, 20);
Number t2 = Test.add(100, 22.2);
Object t3 = Test.add(121, "abc");
int t4 = Test.<Integer>add(10, 20);
int t5 = Test.<Integer>add(100, 22.2);
Number t6 = Test.<Number>add(121, 22.2);
}
}
複製代碼
Fruit<String>[] i=new Fruit<String>[10]; //Errot
Fruit<?>[] i=new Fruit<?>[10]; /能夠經過,可是沒有意義
複製代碼
再看一個例子
//Part1
List<Object> list=new ArrayList<String>();//Error
list.add("123");
//Part2
Object[] objects=new Long[10];
objects[0]="123"; //Runtime 異常
複製代碼
上面 Part 1 編譯出錯,Part 2 編譯 OK,運行出錯。 由於 List 和 ArrayList 沒有繼承關係,而 Java 的數組是在運行時類型檢查的。
泛型不支持傳入基本數據類型,只支持引用數據類型,例如咱們直接使用new ArrayList()是不合法的,由於類型擦除後會替換成Object(若是經過extends設置了上限,則替換成上限類型),int顯然沒法替換成Object,因此泛型參數必須是引用類型。
因此下列方法都是錯誤的
static <T> void test(T t){ //所有ERROR
T newInstance=new T();
T[] array=new T[0];
Class c=T.class;
List<T> list=new ArrayList<T>();
if(list instanceof List<String>){}
}
複製代碼
如何經過反射獲取泛型類型?
既然泛型類型在運行時會被擦除那麼咱們怎麼獲取到泛型類型呢?
其實在泛型擦除時並不會將全部的泛型類型都擦除掉,它只會擦除運行時的泛型類型,編譯時類中定義的泛型類型是不會被擦除的,對應的泛型類型會被保存在Signature中。 咱們若是想獲取對應對象中的泛型類型只需將動態建立的對象改成匿名內部類便可獲取,由於內部類實在編譯時建立的,泛型類型是會保存下來的。 對應API getGeneric...都是獲取泛型類型的。 下邊舉兩個例子:
List<Integer> list = new ArrayList<>();
list.getClass().getGenericSuperclass(); //獲取不到泛型信息
List<Integer> list1 = new ArrayList() {};
list1.getClass().getGenericSuperclass(); //能夠獲取到泛型信息
複製代碼
下邊以一道題爲例:
public class Demo {
public static void main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Bar.class.getGenericSuperclass();
System.out.println(type.getActualTypeArguments()[0]);
ParameterizedType fieldType = (ParameterizedType) Foo.class.getField("children").getGenericType();
System.out.println(fieldType.getActualTypeArguments()[0]);
ParameterizedType paramType = (ParameterizedType) Foo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
System.out.println(paramType.getActualTypeArguments()[0])
System.out.println(Foo.class.getTypeParameters()[0].getBounds()[0]);
}
class Foo<T extends CharSequence> {
public List<Bar> children = new ArrayList<Bar>();
public List<StringBuilder> foo(List<String> foo) {return null}
public void bar(List<? extends String> param) {}
}
class Bar extends Foo<String> {}
}
複製代碼
運行結果以下。
class java.lang.String
class Demo$Bar
class java.lang.String
interface java.lang.CharSequence
經過上面例子會發現泛型類型的每個類型參數都被保留了,並且在運行期能夠經過反射機制獲取到,由於泛型的擦除機制實際上擦除的是除結構化信息外的全部東西(結構化信息指與類結構相關的信息,而不是與程序執行流程有關的,即與類及其字段和方法的類型參數相關的元數據都會被保留下來經過反射獲取到)。
因爲泛型不是協變的,它不支持繼承,好比在使用 List< Number> 的地方不能傳遞 List< Integer>,因此引入了通配符去解決對應的問題,能夠理解通配符是對泛型功能的擴展和加強。
extends 上限通配符 能夠接收extend後的類型及子類
super 下限通配符 能夠接收super後的類型及父類
向下邊這種寫法都是被禁止的
List<Number> list=new ArrayList<Integer>() //Error
List<Object> list;
List<String> strlist=new ArrayList<>();
list=strlist; //Error
複製代碼
首先明確兩個概念:
形參和實參
public class Shop< T>的那個T
表示我要建立一個 Shop 類,它的內部會用到一個統一的類型,這個類型姑且稱他爲 T 。
其它地方尖括號裏的全是 Type argument,好比 Shop < Apple> appleShop;的 Apple ;
表示「那個統一代號,在這裏的類型我決定是這個」
其實用大白話來講形參就是定義泛型的地方,實參是傳入具體泛型類型的地方。
下邊以幾個例子來看看用法
Vector<? extends Number> x1 = new Vector<Integer>(); //正確
Vector<? extends Number> x2 = new Vector<String>(); //編譯錯誤
Vector<? super Integer> y1 = new Vector<Number>(); //正確
Vector<? super Integer> y2 = new Vector<Byte>(); //編譯錯誤
複製代碼
List<? extends Fruit> list = new ArrayList<>();
list.add(new Apple());//Error
list.get(0);//不報錯
List<? super Fruit> list2 = new ArrayList<>();
list2.add(new Apple());//不報錯
list2.get(0);//Error
複製代碼
當咱們使用上限通配符時對應方法參數中使用到泛型的方法將都沒法調用,由於咱們不能肯定具體傳入的是哪一種類型。
當咱們使用下限通配符時對應方法返回值中使用到泛型的方法將都沒法調用,由於咱們不能肯定具體返回的是哪一種類型。
這時咱們就有疑問了,既然使用通配符後對應的對象都處於報廢狀態,那麼這東西有啥用? 其實 ? 以及通配符一般都是用在方法參數中的,好比:
public int getTotalWeight(List<Fruit> list) {
float totalWeight = 0;
for (int i=0;i<list.size();i++) {
Fruit fruit=list.get(i);
totalWeight += fruit.getWeight();
}
return totalWeight;
}
List<Apple> listApple = new ArrayList<>();
listApple.add(new Apple());
List<Banana> listBanana = new ArrayList<>();
listBanana.add(new Banana());
int totalPrice=getTotalWeight(listApple)+getTotalWeight(listBanana);//編譯報錯
複製代碼
這種狀況是錯誤的,由於泛型不支持繼承,咱們是沒法直接傳入的。但咱們只要修改一下便可
public int getTotalWeight(List<? extends Fruit> list) {
float totalWeight = 0;
for (int i=0;i<list.size();i++) {
Fruit fruit=list.get(i);
totalWeight += fruit.getWeight();
}
return totalWeight;
}
複製代碼
這種狀況就OK了,由於這表明着咱們傳入的類型是可知的,上限通配符extends能夠接受extends右邊的類型及其子類。
定義一個方法去添加自身
public class Apple extends Fruit{
void addMeToList(List<Apple> list){
list.add(this);
}
}
複製代碼
List<Fruit> fruits = new ArrayList<>();
Apple apple=new Apple();
apple.addMeToList(fruits);//報錯
複製代碼
能夠看到當調用方法時會出現報錯,由於泛型不支持繼承。咱們修改一下
public class Apple implements Fruit{
void addMeToList(List<? super Apple> list){
list.add(this);
}
}
複製代碼
這樣調用時就不會出現任何問題了。由於下限通配符能夠接收super類型後的父類,天然Apple的父類Fruit是確定能夠接收的。
< T extends E> 和 <? extends E> 有什麼區別?
它們用的地方不同,< T extends E>只能用於形參(也就是泛型定義的時候),<? extends E>只能用於實參(也就是傳入具體泛型類型的時候)。 好比:
public void addAll(Bean<? extends E> bean;
public <T extends E> void addAll(Bean<T> bean;
複製代碼
下面程序合法嗎?
class Bean<T super Student> { //TODO }
複製代碼
編譯時報錯,由於super只能用做實參不能用於形參,extends實參形參均可以。
下面兩個方法有什麼區別?爲何?
public static <T> T get1(T t1, T t2) {
if(t1.compareTo(t2) >= 0);
return t1;
}
public static <T extends Comparable> T get2(T t1, T t2){
if(t1.compareTo(t2) >= 0);
return t1;
}
複製代碼