泛型,即「參數化類型」。java
好比定義一個變量A,咱們能夠經過以下方式將這個變量定義爲字符串類型或者整形。app
String A; Integer A;
固然這是在變量類型已知的狀況下,若是有一種狀況,咱們在定義變量的時候不知道之後會須要什麼類型,或者說咱們須要兼容各類類型的時候,又該如何定義呢?ide
鑑於以上這種狀況,咱們能夠引入「泛型」,將String和Integer類型進行參數化。在使用的時候,再傳入具體的參數類型。測試
泛型的本質是爲了參數化類型(經過傳入的不一樣類型來決定形參的具體類型)。也就是說在泛型使用過程當中,數據的類型被指定爲一個參數,這種參數類型能夠用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。ui
Computer類,這個類中包含一個屬性t,類型爲String。this
public class Computer { private String t; public void set(String t) { this.t = t; } public String get() { return this.t; } }
泛型類Computerspa
//這裏的"T"並非固定寫法,也能夠用V或M等字符 public class Computer<T> { private T t; public void set(T t) { this.t = t; } public T get() { return this.t; } } //能夠傳入多種泛型參數 public class Computer<T, V> { private T t; private V v; public void set(T t, V v) { this.t = t; this.v = v; } public T getT() { return this.t; } public V getV() { return this.v; } }
定義泛型類的好處就是,咱們能夠在須要的時候,再去指定屬性t的類型,加強了通用性。code
Computer<String> computer1= new Computer<String>(); Computer<Integer> computer2= new Computer<Integer>(); Computer<Double> computer3= new Computer<Double>();
//定義接口Calculatable public interface Calculatable<T> { T execute(); } //傳入String類型的實參 public class Computer implements Calculatable<String> { @Override public String execute() { return "ok"; } } //傳入Integer類型的實參 public class Calculator implements Calculatable<Integer> { @Override public Integer execute() { return 100; } } //未傳入具體實參,繼續拋出,由下層傳入 public class Phone<V> implements Calculatable<V> { private V v; @Override public V execute() { return this.v; } }
public class Computer<T> { private T t; //不是泛型方法 public void set(T t) { this.t = t; } //不是泛型方法 public T get() { return this.t; } //泛型方法 //首先public與返回值類型之間的<V>必不可少,只有聲明瞭<V>的方法纔是泛型方法 //能夠聲明多個泛型,如 public <V,M> genericMethod(V v,M m) public <V> V genericMethod(V v){ return v; } }
Computer<String> computer = new Computer<String>(); Integer v= 100; System.out.println(computer.genericMethod(v).getClass().toString()); Double v2= 100d; System.out.println(computer.genericMethod(v2).getClass().toString());
輸出結果:對象
class java.lang.Integer class java.lang.Double
泛型類,是在實例化類的時候指明泛型的具體類型;泛型方法,是在調用方法的時候指明泛型的具體類型。繼承
泛型方法,能夠經過傳入的實參,判斷V的具體類型。
若是靜態方法不可使用類中定義的泛型,若是靜態方法想要使用泛型,須要將自身聲明爲泛型方法。
public class Computer<T> { public static <V> void get(V v){ //T t;報錯,不能使用類中定義的泛型 } }
舉一個簡單的例子
//水果類 public class Fruit { private String name; public Fruit(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
fsadfdsfd
//蘋果類 public class Apple extends Fruit { public Apple(String name) { super(name); } }
//裝水果的袋子 public class GenericHolder<T> { private T obj; public GenericHolder() { } public GenericHolder(T obj) { this.obj = obj; } public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
測試類:
public class AppTest extends TestCase { @Test public void test() { //這是一個貼了水果標籤的袋子貼了 GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>(); //這是一個貼了蘋果標籤的袋子 GenericHolder<Apple> appHolder = new GenericHolder<Apple>(); //這是一個水果 Fruit fruit = new Fruit("水果"); //這是一個蘋果 Apple apple = new Apple("蘋果"); //如今咱們把水果放進去 fruitHolder.setObj(fruit); //調用一下吃水果的方法 eatFruit(fruitHolder); //貼了水果標籤的袋子放水果固然沒有問題 //如今咱們把水果的子類——蘋果放到這個袋子裏看看 fruitHolder.setObj(apple); //一樣是能夠的,其實這時候會發生自動向上轉型,apple向上轉型爲Fruit類型後再傳入fruitHolder中 //但不能再將取出來的對象賦值給redApple了,由於袋子的標籤是水果,因此取出來的對象只能賦值給水果類的變量 //沒法經過編譯檢測 redApple = fruitHolder.getObj(); //調用一下吃水果的方法 eatFruit(fruitHolder); //放蘋果的標籤,天然只能放蘋果 appHolder.setObj(apple); //報錯,這時候沒法把appHolder傳入eatFruit,由於GenericHolder<Fruit> 和 GenericHolder<Apple>是兩種不一樣的類型,GenericHolder<Fruit>只容許傳入水果類的袋子 // eatFruit(appHolder); } public static void eatFruit(GenericHolder<Fruit> fruitHolder){ System.out.println("我正在吃 " + fruitHolder.getObj().getName()); } }
執行結果:
我正在吃 水果 我正在吃 蘋果
GenericHolder<Fruit> 和 GenericHolder<Apple>是兩種不一樣的類型,因此沒法經過編譯。
從Java繼承的角度上能夠分析:
蘋果 IS-A 水果
裝蘋果的袋子 NOT-IS-A 裝水果的袋子
那麼問題來了,若是我想讓eatFruit方法能同時處理GenericHolder<Fruit> 和 GenericHolder<Apple>兩種類型怎麼辦?並且這也是很合理的需求,畢竟Apple是Fruit的子類,能吃水果,爲啥不能吃蘋果???若是要把這個方法重載一次,未免也有些小題大作了。
這個時候,泛型的邊界符就有它的用武之地了。咱們先來看效果:
public class AppTest extends TestCase { @Test public void test() { //這是一個貼了水果標籤的袋子 GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>(); //這是一個貼了蘋果標籤的袋子 GenericHolder<Apple> appHolder = new GenericHolder<Apple>(); //這是一個水果 Fruit fruit = new Fruit("水果"); //這是一個蘋果 Apple apple = new Apple("蘋果"); //如今咱們把水果放進去 fruitHolder.setObj(fruit); //調用一下吃水果的方法 eatFruit(fruitHolder); //放蘋果的標籤,天然只能放蘋果 appHolder.setObj(apple); // 這時候能夠順利把appHolder 傳入eatFruit eatFruit(appHolder); //這種泛型 ? extends Fruit不能存放Fruit,可是可使用 ? extends Fruit的變量指向GenericHolder<Fruit>的對象 GenericHolder<? extends Fruit> fruitHolder22 = fruitHolder; // fruitHolder22.setObj(fruit);//報錯,不能存放fruit System.out.println(fruitHolder22.getObj().getName()+"----"); //這種泛型 ? extends Fruit不能存放apple,可是可使用 ? extends Fruit的變量指向GenericHolder<apple>的對象 fruitHolder22 = appHolder; // fruitHolder22.setObj(apple);//報錯不能存放apple System.out.println(fruitHolder22.getObj().getName()+"^^^^"); } public static void eatFruit(GenericHolder<? extends Fruit> fruitHolder){ System.out.println("我正在吃 " + fruitHolder.getObj().getName()); Fruit fruit1 = new Fruit("水果1"); //報錯,不能存入fruit1 。由於引入傳入的參數fruitHolder是Fruit的子類。 //Error:(35, 28) java: 不兼容的類型: Fruit沒法轉換爲capture#1, 共 ? extends Fruit //fruitHolder.setObj(fruit1); } }
運行結果:
我正在吃 水果 我正在吃 蘋果 水果---- 蘋果^^^^
這就是泛型的邊界符,用<? extends Fruit>的形式表示。邊界符的意思,天然就是定義一個邊界,這裏用?表示傳入的泛型類型不是固定類型,而是符合規則範圍的全部類型,用extends關鍵字定義了一個上邊界。
有上邊界,天然有下邊界,泛型裏使用形如<? super Fruit>的方式使用下邊界。
這兩種方式基本上解決了咱們以前的問題,可是同時,也有必定的限制(PECS原則)。
1.上界<? extends T>不能往裏存,只能往外取
不要太疑惑,其實很好理解,由於編譯器只知道容器裏的是Fruit或者Fruit的子類,但不知道它具體是什麼類型,因此存的時候,沒法判斷是否要存入的數據的類型與容器種的類型一致,因此會拒絕set操做。注意:取出來的是實際的類型,例如上面指向的是GenericHolder<Fruit>,那麼取出的就是Fruit,若是指向的是GenericHolder<Apple>,那麼取出的就是Apple。
2.下界<? super T>往外取只能賦值給Object變量,不影響往裏存
由於編譯器只知道它是Fruit或者它的父類,這樣其實是放鬆了類型限制,Fruit的父類一直到Object類型的對象均可以往裏存,可是取的時候,就只能當成Object對象使用了。
3.若是既要存又要取,那麼就不要使用任何通配符
因此若是須要常常往外讀,則使用<? extends T>,若是須要常常往裏存,則使用<? super T>。
如何閱讀過一些Java集合類的源碼,能夠發現一般咱們會將二者結合起來一塊兒用,好比像下面這樣:
public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i, src.get(i)); } }
這種就我看來,應該是至關於:
public class Collections { public static <T> void copy(List<T> dest, List<T> src) { for (int i=0; i<src.size(); i++) dest.set(i, src.get(i)); } }