Java Generic 自定義泛型

如何自定義泛型

考慮我們要實現(xiàn)了一個節(jié)點對象,這個對象可以自定義類型,我們可以用泛型語法進行如下的定義:

package Generic;

public class Node<T> {
    private T value;
    Node<T> next;
    T getValue() {
        return value;
    }
    
    void setValue(T value) {
        this.value = value;
    }
    
    public static void main(String[] args) {
        Node<String> first = new Node<String>();
        first.setValue("Justin");
        first.next = new Node<String>();
        first.next.setValue("momor");
    }
}

同樣,在定義接口的時候,也可以使用泛型,例如iterator接口就是泛型定義的

package java.util;

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

自定義泛型的邊界

在定義泛型的時候,可以定義泛型的邊界,例如下面的例子

class Animal {}
class Human extends Animal {}
class Toy {}

class Duck<T extends Animal> {}

public class Main {
    public static void main(String[] args) {
        Duck<Animal> ad = new Duck<Animal>();
        Duck<Human> hd = new Duck<Human>();
        Duck<Toy> hd = new Duck<Toy>();  // 編譯錯誤
    }
}

在這個例子中,使用extends定義指定泛型的真正的形態(tài)的時候,必須是animal的子類,你可以使用animal與human來指定形態(tài),但不可以使用toy來指定,因為toy不是animal的子類。

下面舉一個快速排序的例子來說明:

class Sort {
    public void quick(int[] number) {
        sort(number, 0, number.length-1);
    }
    
    private void sort(int[] number, int left, int right) {
        if(left < right) { 
            int q = partition(number, left, right); 
            sort(number, left, q-1); 
            sort(number, q+1, right); 
        } 

    }

    private int partition(int number[], int left, int right) {  
        int i = left - 1; 
        for(int j = left; j < right; j++) { 
            if(number[j] <= number[right]) { 
                i++; 
                swap(number, i, j); 
            } 
        } 
        swap(number, i+1, right); 
        return i+1; 
    } 

    private void swap(int[] number, int i, int j) {
        int t = number[i]; 
        number[i] = number[j]; 
        number[j] = t;
    }
}

在這個例子中,使用的是int寫死的類型,為了讓這個排序算法更為通用,我們可以使用泛型,但要求是該形態(tài)必須具有可比較的對象大小的方法,一個方法就是要求排序的對象實例化[java.lang.Comparable<T>]

class Sort<T extends Comparable<T>> {
    void quick(T[] array) {
        sort(array, 0, array.length-1);
    }
    
    private void sort(T[] array, int left, int right) {
        if(left < right) { 
            int q = partition(array, left, right); 
            sort(array, left, q-1); 
            sort(array, q+1, right); 
        } 

    }

    private int partition(T[] array, int left, int right) {  
        int i = left - 1; 
        for(int j = left; j < right; j++) { 
            if(array[j].compareTo(array[right]) <= 0) {
                i++; 
                swap(array, i, j); 
            } 
        } 
        swap(array, i+1, right); 
        return i + 1; 
    } 

    private void swap(T[] array, int i, int j) {
        T t = array[i]; 
        array[i] = array[j]; 
        array[j] = t;
    }
}

若extends可以指定多個類和接口,想再指定其它接口,可以使用&連接。例如:
public class Some<T extends Iterable<T> & Comparable<T>> {
...
}

共變性,逆變性

假設(shè)我們定義了下列這種類別:

class Node<T> {
    T value;
    Node<T> next;
    
    Node(T value, Node<T> next) {
        this.value = value;
        this.next = next;
    }
}

在下面的例子中:

class Fruit {}
class Apple extends Fruit {
    @Override
    public String toString() {
        return "Apple";
    }
}

class Banana extends Fruit {
    @Override
    public String toString() {
        return "Banana";
    }
}


public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<Fruit> fruit = apple;  // 編譯錯誤,incompatible types
    }
}

在上面的例子中,apple的形態(tài)是Node<Apple>,而fruit的類型是Node<fruit>,我們將apple所指向的對象給fruit,那么Node<Apple>是否應(yīng)該是一種Node<Fruit>呢?編譯器告訴我們不是的。

在泛型中,如果B是A的子類,而Node<B>被視為一種Node<A>類型,就稱Node具有共變形(Covariance),反過來,如果Node<A>被視為一種Node<B>形態(tài),則成為具有逆變性(Contravariance),如果不具有共變形或者逆變性,則稱其是不可變的。

Java中的泛型不支持共變形和逆變性,不過可以使用通配字符?與extends或者super
來宣告達到類似的共變形和逆變性。如下面的例子:

public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<? extends Fruit> fruit = apple; // 類似共變性效果
    }
}

一個實際應(yīng)用的例子是:

public class Main {
    public static void main(String[] args) {
        Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
        Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
        Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);
        
        Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
        Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);

        show(apple3);
        show(banana2);
    }
    
    static void show(Node<? extends Fruit> n) {
        Node<? extends Fruit> node = n;
        do {
            System.out.println(node.value);
            node = node.next;
        } while(node != null);
    }
}

你的目的是可以顯示所有的水果節(jié)點,由於show()方法使用型態(tài)通配字元宣告參數(shù),使得n具備類似共變性的效果,因此show()方法就可以顯示Node<Apple>也可以顯示Node<Banana>。

Java的泛型亦不支援逆變性,不過可以使用型態(tài)通配字元?與super來宣告變數(shù),使其達到類似逆變性,例如:

我們的目的是可以顯示所有水果的節(jié)點,由于show方法使用通配字符宣告共變形,所以show方法既可以顯示apple,也能顯示banana。
java的泛型不支持逆變性,不過可以使用通配字符super來宣告逆變性,如下面的例子:

class Fruit {
    int price;
    int weight;
    Fruit(int price, int weight) {
        this.price = price;
        this.weight = weight;
    }
}

class Apple extends Fruit {
     Apple(int price, int weight) {
         super(price, weight);
     }
}

class Banana extends Fruit {
     Banana(int price, int weight) {
         super(price, weight);
     }
}

interface Comparator<T> {
    int compare(T t1, T t2);
}

class Basket<T> {
    private T[] things;
    Basket(T... things) {
        this.things = things;
    }
    void sort(Comparator<? super T> comparator) {
        // 作一些排序
    }
}
public class Main {
    public static void main(String[] args) {
        Comparator<Fruit> comparator = new Comparator<Fruit>() {
            public int compare(Fruit f1, Fruit f2) {
                return f1.price - f2.price;
            }
        };
        Basket<Apple> b1 = new Basket<Apple>(
                                 new Apple(20, 100), new Apple(25, 150));
        Basket<Banana> b2 = new Basket<Banana>(
                                 new Banana(30, 200), new Banana(25, 250));
        b1.sort(comparator);
        b2.sort(comparator);
    }
}

泛型對象的比較

如果我們需要重寫泛型對象的equal方法,我們可能會這么寫:

import java.util.*;

class Basket<T> {
    T[] things;
    Basket(T... things) {
        this.things = things;
    }
    
    @Override
    public boolean equals(Object o) {
        if(o instanceof Basket<T>) {  // 編譯錯誤
            Basket that = (Basket) o;
            return Arrays.deepEquals(this.things, that.things);
        }
        return false;
    }
}

但如果我們編譯這個程序,我們會發(fā)現(xiàn)如下的錯誤:
illegal generic type for instanceof
if(o instanceof Basket<T>) {
在 程式中instanceof對Basket<T>的型態(tài)判斷是不合法的,因為Java的泛型所採用的是型態(tài)抹除,也就是說,程式中泛型語法的 型態(tài)指定,僅提供編譯器使用,執(zhí)行時期無法獲型態(tài)資訊,因而instanceof在執(zhí)行時期比對時,僅能針對Basket型態(tài)比對,無法針對當中的泛型實 際型態(tài)進行比對。

如果想要通過編譯,可以使用型態(tài)通配字元?:

在程序中對Basket<T>的類型的判斷是不合法的,因為java泛型采用的是類型擦除,也就是說,在程序中泛型語法的類型指定,僅給編譯器使用,執(zhí)行時無法獲取類型的信息,因而instanceOf在執(zhí)行器對比時,僅能根據(jù)basket類型進行對比,無法針對當眾的泛型實際的類型進行對比

如果想要通過編譯,就要使用通配符?:

import java.util.*;

class Basket<T> {
    T[] things;
    Basket(T... things) {
        this.things = things;
    }
    
    @Override
    public boolean equals(Object o) {
        if(o instanceof Basket<?>) {
            Basket that = (Basket) o;
            return Arrays.deepEquals(this.things, that.things);
        }
        return false;
    }
}

我們可以來測試一下,這樣寫的效果:

public class Main {
    public static void main(String[] args) {
        Basket<Integer> b1 = new Basket<Integer>(1, 2);
        Basket<Integer> b2 = new Basket<Integer>(1, 2);
        Basket<Integer> b3 = new Basket<Integer>(2, 2);
        Basket<String> b4 = new Basket<String>("1", "2");
        System.out.println(b1.equals(b2));       // true
        System.out.println(b1.equals(b3));       // false
        System.out.println(b1.equals(b4));       // false
    }
}

好像不錯,可以正確的比較,但我們看下面的例子:

public class Main {
    public static void main(String[] args) {
        Basket<String> b1 = new Basket<String>();
        Basket<Integer> b2 = new Basket<Integer>();
        System.out.println(b1.equals(b2));    // true
    }
}

Basket<Integer>與Basket<String>本來應(yīng)該是被看作為不同的類型的,顯然比較的結(jié)果應(yīng)該為不相等,但實際上,由于java采用類型擦除的方式,結(jié)果就是在這種情況下,空對象的相等的,因為還沒有塞值進去。

public class Main {
    public static void main(String[] args) {
        List<Integer> l1 = new ArrayList<Integer>();
        List<String> l2 = new ArrayList<String>();
        System.out.println(l1.equals(l2));       // true
    }
}

java中是這么理解的,l1,l2都是空串,那么他們不就是相等的么,這就是采取類型擦除的結(jié)果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,901評論 12 51
  • object 變量可指向任何類的實例,這讓你能夠創(chuàng)建可對任何數(shù)據(jù)類型進程處理的類。然而,這種方法存在幾個嚴重的問題...
    CarlDonitz閱讀 932評論 0 5
  • 前言 人生苦多,快來 Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,252評論 9 118
  • 泛型是Java 1.5引入的新特性。泛型的本質(zhì)是參數(shù)化類型,這種參數(shù)類型可以用在類、變量、接口和方法的創(chuàng)建中,分別...
    何時不晚閱讀 3,051評論 0 2
  • 定崗 崗位是組織架構(gòu)最小的單位。確定崗位,才能夠更好地分析得到更科學(xué)的組織架構(gòu)。 崗位分類:縱向和橫向 縱向是在崗...
    cooooky閱讀 370評論 0 1