JAVA基礎知識|泛型

1、什麼是泛型?

泛型,即「參數化類型」。java

好比定義一個變量A,咱們能夠經過以下方式將這個變量定義爲字符串類型或者整形。app

String A;
Integer A;

固然這是在變量類型已知的狀況下,若是有一種狀況,咱們在定義變量的時候不知道之後會須要什麼類型,或者說咱們須要兼容各類類型的時候,又該如何定義呢?ide

鑑於以上這種狀況,咱們能夠引入「泛型」,將String和Integer類型進行參數化。在使用的時候,再傳入具體的參數類型。測試

泛型的本質是爲了參數化類型(經過傳入的不一樣類型來決定形參的具體類型)。也就是說在泛型使用過程當中,數據的類型被指定爲一個參數,這種參數類型能夠用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。ui

 2、泛型類

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>();

 

3、泛型接口

//定義接口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;
    }
}

 

4、泛型方法

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的具體類型。

 

5、靜態方法與泛型

 若是靜態方法不可使用類中定義的泛型,若是靜態方法想要使用泛型,須要將自身聲明爲泛型方法。

public class Computer<T> {    
    public static <V> void get(V v){
        //T t;報錯,不能使用類中定義的泛型
    }
}

 

6、通配符及上下邊界

舉一個簡單的例子

//水果類
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));
    }
}
相關文章
相關標籤/搜索