Java中的泛型 --- Java 編程思想

前言

​ 我一直都認爲泛型是程序語言設計中一個很是基礎,重要的概念,Java 中的泛型究竟是怎麼樣的,爲何會有泛型,泛型怎麼發展出來的。通透理解泛型是學好基礎裏面中很是重要的。因而,我對《Java編程思想》這本書中泛型章節進行了研讀。惋惜遺憾的是,本身沒有太多的經驗,有些東西看了幾回也是有點懵。只能之後有機會,再進行學習了。可是本身也理解了挺多的。下面就是本身對於泛型的理解與感悟。若有不對,望指出。java

概念

由來: Java 一開始設計之初是沒有泛型這個特性的,直到jdk 1.5中引入了這個特性。Java 的泛型是由擦除來實現的。要知道擦除是什麼?往下看。ios

概念:通常的類和方法,只能使用具體的類型;要麼是基本類型,要麼是自定義的類。若是要編寫能夠應用於多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。泛型實現了參數化類型的概念,使代碼應用於多個類型。泛型在編程語言中出現時,其最初的目的是但願類和方法具備普遍的表達能力。c++

簡單泛型

​ 有不少緣由促成泛型的出現,其中最重要的一個緣由就是爲了創造容器類。咱們暫時不指定類型,而是稍後再決定具體使用什麼類型。要達到這個目的,須要使用類型參數,用尖括號括住,放在類名的後面。而後使用這個類的時候,再用實際的類型替換此類型參數。在下面例子中,T就是類型參數。代碼以下:編程

public class Holder<T> {
    private T a;

    public Holder(T a) {
        this.a = a;
    }

    public void set(T a) {
        this.a = a;
    }

    public T get(){
        return a;
    }

    public static void main(String[] args) {
        Holder<String> h = new Holder<>();
        h3.set("hello");
        String a = h3.get();
    }
}
class Automobile{}
複製代碼

​ 可是每每不少的源碼中一些通用的類都是有多個泛型參數,譬如 java.util.function.BiFunction 中就有三個類型參數 T,U,R 。安全

接口泛型

​ 泛型在接口上的運用是很是多的,例如迭代器(Iterable)中的 Iterator 。框架

public interface Itreator<T>{
    // 判斷是否還有元素
    boolean hasNext();
    // 洗一個元素
    E next();
    ....
}
複製代碼

​ 這個是咱們都很經常使用的吧,其實接口使用泛型和類使用泛型沒什麼區別。編程語言

泛型方法

​ 泛型方法,在返回參數類型前面添加泛型參數列表,由<>括着 eg:學習

// 泛型方法 兩個泛型參數,T,R 返回類型爲 R
  public <T, R> R test(T t, R r){
        return r;
    }
複製代碼

​ 泛型方法使得該方法獨立於類而產生變化。在須要編寫泛型代碼的時候,基本的的指導原則是:不管什麼時候,只要你能作到,就儘可能使用泛型方法。意思是若是使用泛型方法能夠代替整個類的泛型化,那就用泛型方法,由於它可使事情更加清楚明白。另外對於static的方法而言,沒法訪問泛型類的類型參數,因此若是static方法須要使用泛化能力,就必須使其成爲泛型方法。ui

泛型的擦除

​ 在看 《Java編程思想》 中泛型章節中 ’擦除的神祕之處‘ 這一小節的時候,看的我特別的暈暈乎乎的,而後再往下面看時就愈來愈混了。 特別是看到’邊界‘,’通配符‘ 這塊了就有點懵了。首先看下什麼是擦除。在泛型代碼內部,沒法得到有關泛型參數類型的信息。 Java 的泛型是由擦拭來實現的,這意味着當你使用泛型的時,任何具體的類型都被擦除了,你惟一知道的就是你在使用一個對象。因爲 Java 一開始沒有引入 泛型這個特性,在爲了兼容 JDK 老版本的狀況下。擦除是 Java 泛型實現的一種折中。 所以你在運行時 List<String>List<Integer> 是同樣的,注意是在運行時,可是在編譯時,List<String> 表示這個 String 類型的 List 容器, List<Integer> 表示這個時 Integer 類型的List容器。 舉個例子,例子來源於 Java編程思想this

#include <iostream>
using namespace std;

temple<class T> class Manipulator{
    T obj;
    public :
        Manipulator(T x){obj = x;}
        void manipylate() {obj.f();}

};
class HasF{
public:
    void f(){cout<<"HasF::f()"<< endl;}
};

int main(){
    HasF hf;
    Manipulator<HasF> manipulator(hf);
    manipulator.manipylate();
}
複製代碼

輸出 HasF:f()

​ Manipulator 類存儲了一個類型T的對象,在 manipylate 方法裏,他在 obj上調用了方法 f() ; 它是怎麼知道 f() 是類型參數T 中有的方法呢? 當你實例化這個模板的時,c++編譯器將進行檢查,若是他在 Manipulator<HasF> 被實例化的這刻,它看到HasF若是擁有這個方法 f(), 就編譯經過,不然不經過。 這個代碼就算不會 c++ 的人應該也看的懂吧。咱們看下 Java 的版本:

public class HasF{
    public void f(){
        System.out.println("HasF::f()");
    }
}

class Manipulator<T>{
    private T obj;
    public Manipulator(T obj){
        this.obj = obj;
    }

    public void manipulator(){
        //錯誤: 這個是不能夠調用 f() 這個方法的。 
        // obj.f();
    }

    public static void main(String[] args){
        HasF hf = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hf);
        manipulator.manipulator();
    }
}
複製代碼

看到沒有,Java 中有了擦除, Java 編譯器不知道 obj 中有沒有 f() 這個方法的事情。

泛型的邊界

T extends Class

​ Java 中的泛型,在編譯時,T表明一種類型,若是沒有指定任何的邊界,那麼他就至關於 Object 。 咱們能夠經過 extends 關鍵字,給泛型指定邊界。 上面代碼咱們爲了可以調用f(), 咱們能夠協助泛型類,給定泛型類的邊界,告訴編譯器必須接受遵循這個邊界的類型。這裏用 extends 關鍵字。 將上面代碼改爲

public class HasF{
    public void f(){
        System.out.println("HasF::f()");
    }
}

class Manipulator<T extends HasF>{
    private T obj;
    public Manipulator(T obj){
        this.obj = obj;
    }

    public void manipulator(){
        obj.f();
    }

    public static void main(String[] args){
        HasF hf = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hf);
        manipulator.manipulator();
    }
}
複製代碼

輸出:HasF::f()

這樣子在編譯的時候就至關告訴編譯器 Manipulator 類中 obj 的參數類型 爲 HasF 或着它的子類。

? extends T

? 是泛型表達式中的通配符。 ? extends T 表示: T 數據類型或着 T 的子類數據類型。 舉個例子

class Vegetables{}
// 白菜
class Cabbage extends Vegetables{}
// 小白菜
class Pakchoi extends Cabbage{}
//大蒜
class Garlic extends Vegetables{}

public class Main{
    public static void main(String[] args) {
        // 這個是會報錯的。
        // ArrayList<Vegetables> vegetables = new ArrayList<Cabbage>();
    }
}
複製代碼

​ 上面的中 main 方法裏面是報錯的由於,ArrayList<Vegetables>表示這個ArrayList容器中只能存放蔬菜,new ArrayList<Cabbage>() 表示 這個使一個只能放白菜的容器。這兩個容器是不能夠相等的,類型不同。可是咱們能夠經過 ? extends T 來解決這個問題,代碼以下:

public class Main{
    public static void main(String[] args) {
        ArrayList<? extends Vegetables> vegetables = new ArrayList<Cabbage>();
        // 報錯 不能添加白菜進去
        // vegetables.add(new Cabbage());
    }
}
複製代碼

​ 咱們能夠用 vegetables 表示 new ArrayList<Cabbage>(), 這是向上轉型。可是,爲何 vegetables.add(new Cabbage()) ; 會報錯,由於 ArrayList<? extends Vegetables>表示這個 ArrayList 容器中可以存聽任何蔬菜。 可是 ArrayList 具體是什麼容器,徹底不知道,add 的時候,你添加什麼東西進去到這個容器中都是不安全的。 這個時候,咱們能夠用 ? super T 來進行操做,具體往下看。

? super T

? super T 表示: T 數據類型 或着 T的超類數據類型, super 表示超類通配符。 上面代碼能夠用如下表示

public class Main{
    public static void main(String[] args) {
        ArrayList<? super Cabbage> cabbages = new ArrayList<Vegetables>();

        cabbages.add(new Cabbage());
        cabbages.add(new Pakchoi());

        // cabbages.add(new Vegetables());
        System.out.println(cabbages);
    }
}
複製代碼

上面ArrayList<? super Cabbage> 表示ArrayList這個容器中怎麼均可以存放 Cabbage 以及Cabbage子類的數據類型。 cabbages 指向的是 蔬菜的容器類。 add 進去的是白菜以及白菜的子類型數據。這個固然是支持的。

? extends T VS ? super T

? extends T ,? super T 通常用於方法參數列表。

public class Main{
    public static void main(String[] args) {

        List<Cabbage> cabbages = new ArrayList<>();
        cabbages.add(new Cabbage());
        cabbages.add(new Cabbage());
        cabbages.add(new Cabbage());
        extendsTest(cabbages);
        List<? super Cabbage> list = superTest(new ArrayList<Vegetables>());
        System.out.println(list);
    }

    public static void extendsTest(List<? extends Vegetables> list){
        for (Vegetables t : list){
            System.out.println(t);
        }
    }

    public static List<? super Cabbage> superTest(List<? super Cabbage> list){
        list.add(new Cabbage());
        list.add(new Pakchoi());
        return list;
    }
}
複製代碼

? extends T 表示消費者 list 而後把裏面的數據消費掉。 ? super T 表示生產者 傳入一個list 而後往裏面添加數據,進行其餘操做。

總結

​ 對於 ? extends Class ,? extends T,? super T,不是很理解的,能夠本身把例子寫一下,而後想想。Java 泛型的特性在不少開源的框架上是用的很是多的。這快須要深刻的理解一下,我想隨着敲代碼的年限上,應該到了後面會有不同得理解吧。如今經過書上可以知道,理解得就只有這麼多了。

相關文章
相關標籤/搜索