java進(jìn)階-泛型

1、什么是泛型

所謂泛型,就是允許在定義類、接口、方法時(shí)使用類型形參,這個(gè)類型形參將在聲明變量、創(chuàng)建對(duì)象、調(diào)用方法時(shí)動(dòng)態(tài)的指定。

2、為什么要使用泛型

先看下這個(gè)例子:

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("字符串");
        list.add(122);

        for (int i = 0; i <list.size() ; i++) {
            String str = (String) list.get(i);
            System.out.println(str);
        }
    }

我們運(yùn)行上面的代碼,就會(huì)拋出如下的異常。

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

我們知道ArrayList可以存放任意對(duì)象類型,我們先向list中存放了一個(gè)String類型的數(shù)據(jù),接著又存放了一個(gè)Integer類型的數(shù)據(jù),在使用的時(shí)候就拋出了異常。為了解決類似這樣的問(wèn)題,泛型應(yīng)運(yùn)而出。

對(duì)于上面的例子的例子做如下改造:

List<String> list = new ArrayList();
list.add("字符串");
list.add(122);  // add(java.long.String) in list connot be applied to (int)

當(dāng)我們使用泛型后,再存放Integer類型的數(shù)據(jù),就會(huì)直接報(bào)錯(cuò),這就是再編譯時(shí)檢測(cè),而上面的是在運(yùn)行時(shí)才去檢測(cè)的,這樣就能很大程度的解決程序員犯錯(cuò)了。

與非泛型代碼相比,使用泛型的代碼具有許多優(yōu)點(diǎn):

  • 在編譯時(shí)進(jìn)行更強(qiáng)的類型檢查。Java編譯器將強(qiáng)類型檢查應(yīng)用于泛型代碼,并在代碼違反類型安全時(shí)發(fā)出錯(cuò)誤。修復(fù)編譯時(shí)錯(cuò)誤比修復(fù)運(yùn)行時(shí)錯(cuò)誤更容易,運(yùn)行時(shí)錯(cuò)誤很難找到。
  • 消除類型轉(zhuǎn)換。 當(dāng)使用的時(shí)候不需要強(qiáng)制轉(zhuǎn)換類型。
  • 使程序員能夠?qū)崿F(xiàn)泛型算法。 通過(guò)使用泛型,程序員可以實(shí)現(xiàn)泛型算法,這些算法可以處理不同類型的集合,可以自定義,并且類型安全且易于閱讀。

3、泛型的使用

泛型有三種使用方式,分別為:泛型類、泛型接口、泛型方法。

3.1、泛型類

泛型類型用于類的定義中,就稱之為泛型類,泛型類的基本寫(xiě)法如下:

public class Generic<T> {}
public class Generic<T1,T2,...,Tn> {}

在類名之后,類型參數(shù)部分由尖括號(hào)<>分隔。它指定了類型參數(shù)(也稱為類型變量)T,當(dāng)然了也可以定義多個(gè)參數(shù)。
下面看一個(gè)例子:

/**
 * 在實(shí)例化泛型類時(shí),必須指定T的具體類型
 * @param <T> 此處T可以隨便寫(xiě)為任意標(biāo)識(shí),常見(jiàn)的如T、E、K、V等形式的參數(shù)常用于表示泛型
 */
public class Generic<T> {
    //key這個(gè)成員變量的類型為T(mén),T的類型由外部指定
    private T key;

    //泛型構(gòu)造方法形參key的類型也為T(mén),T的類型由外部指定
    //這里需要注意的是構(gòu)造器名是Generic ,而不是Generic<T>。
    public Generic(T key) {
        this.key = key;
    }
    
    //泛型方法getKey的返回值類型為T(mén),T的類型由外部指定
    public T getKey() {
        return key;
    }
}

運(yùn)行下面代碼:

Generic<String> genericStr = new Generic<>("測(cè)試泛型類");
Generic<Integer> genericInt = new Generic<>(100);
System.out.println("泛型測(cè)試---》"+genericStr.getKey());
System.out.println("泛型測(cè)試---》"+genericInt.getKey());

// 運(yùn)行結(jié)果如下:
// 泛型測(cè)試---》測(cè)試泛型類
// 泛型測(cè)試---》100

對(duì)于多種類型的參數(shù)其實(shí)是一樣的做法,比如我們熟悉的HashMap就是多種類型參數(shù):

//HashMap的源碼
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {...}
//HashMap的使用
Map<String,String> map = new HashMap<>();

3.2、泛型接口

其實(shí)泛型接口和泛型類的定義使用基本上是一樣的。模擬我們常用的list接口來(lái)演示下泛型接口

//定義一個(gè)泛型接口
public interface MyList<T> {
    public T add(T t);
}

當(dāng)實(shí)現(xiàn)泛型接口的類,未傳入泛型實(shí)參時(shí),與泛型類的定義相同,在聲明類的時(shí)候,需將泛型的聲明也一起加到類中

public class MyArrayList<T> implements MyList<T> {
    @Override
    public T add(T t) {
        return t;
    }

    public static void main(String[] args) {
        MyList<String> list = new MyArrayList<>();
        list.add("aaaa");
        MyList<Integer> list1 = new MyArrayList<>();
        list1.add(0);
    }
}

在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時(shí),如已將泛型類型傳入實(shí)參類型,則所有使用泛型的地方都要替換成傳入的實(shí)參類型。

public class MyArrayList implements MyList<String> {
    @Override
    public String add(String s) {
        return s;
    }
}

3.3、泛型方法

泛型方法,是在調(diào)用方法的時(shí)候指明泛型的具體類型。基本寫(xiě)法如下:

/**
 * @param <T>  這個(gè)<T>非常重要,可以理解為聲明此方法為泛型方法。
 *           只有聲明了<T>的方法才是泛型方法。泛型類中的使用了泛型的成員方法并不是泛型方法。
 */
public <T> void Generic(T t) {}

下面看下具體例子看下泛型方法的實(shí)現(xiàn):

public class TestGeneric {

    public <T> void getKey(T t) {
        System.out.println("測(cè)試泛型方法---》" +t);
    }

    public static void main(String[] args) {
        TestGeneric testGeneric = new TestGeneric();
        testGeneric.getKey("字符串");
        testGeneric.getKey(0);
    }
}

//輸出結(jié)果
//測(cè)試泛型方法---》字符串
//測(cè)試泛型方法---》0

3.4、泛型通配符

稱為通配符的問(wèn)號(hào)(?)表示未知類型。通配符可以在多種情況下使用:作為參數(shù),字段或局部變量的類型;有時(shí)作為返回類。通配符從不用作泛型方法調(diào)用,泛型類實(shí)例創(chuàng)建或超類型的類型參數(shù)。下面詳細(xì)地討論通配符,包括上界通配符,下界通配符和通配符捕獲。

下面我們看一個(gè)例子:

    List<?> list = new ArrayList<String>();
    //我們可以正常獲取list中的元素,不會(huì)引起編譯錯(cuò)誤
    Object object = list.get(0);
    //調(diào)用add方法會(huì)引起編譯錯(cuò)誤
    list.add("字符串");

這種帶通配符的 List僅表示它是各種泛型List的父類,并不能把元素添加到其中,否則會(huì)引起編譯錯(cuò)誤。因?yàn)槌绦驘o(wú)法確定list集合中的元素類型,所以不能向其中添加對(duì)象,而程序第哦啊用get()方法來(lái)返回List<?>集合指定索引處的元素,其返回值是一個(gè)未知類型,但肯定是一個(gè)Object,因此返回值賦給一個(gè)Object類型的變量是可以的。

3.4.1、上界通配符

為泛型添加上邊界,即傳入的類型實(shí)參必須是指定類型的子類型,要聲明上界通配符,請(qǐng)使用通配符(?),然后使用extends關(guān)鍵字

public static void main(String[] args) {
    List<Integer> integers = new ArrayList<>();
    integers.add(1);
    List<Double> doubles = new ArrayList<>();
    doubles.add(2.0d);
    List<String> strings = new ArrayList<>();
    strings.add("通配符上界");

    print(integers);
    print(doubles);
    //編譯直接報(bào)錯(cuò)
    //print(strings);
}
public static void print(List<? extends Number> list) {
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
}
3.4.2、下界通配符

通配符將未知類型限制為特定類型,下界通配符表示為通配符(?),后跟`super關(guān)鍵字

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(0);
        List<Number> numbers = new ArrayList<>();
        numbers.add(123);
        numbers.add(10.0d);
        List<Double> doubles = new ArrayList<>();
        doubles.add(0.2d);
        print(integers);
        print(numbers);
        //編譯報(bào)錯(cuò)
        //print(doubles);
    }

    public static void print(List<? super Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

上面我們可以看到定義了下界Integer后,我們就無(wú)法在使用Double類型了,只能使用Integer類型、Number類型、Object類型了。

4、泛型總結(jié)

使用泛型的時(shí)候,要考慮泛型的限制:

  • 無(wú)法實(shí)例化具有基本類型的泛型類型
  • 無(wú)法創(chuàng)建類型參數(shù)的實(shí)例
  • 無(wú)法聲明類型為類型參數(shù)的靜態(tài)字段
  • 無(wú)法創(chuàng)建參數(shù)化類型的數(shù)組
  • 無(wú)法創(chuàng)建捕獲或拋出參數(shù)化類型的對(duì)象
  • 無(wú)法重載每個(gè)重載的形式參數(shù)類型都擦除為相同原始(raw)類型的方法。
  • 無(wú)法將Castsinstanceof與參數(shù)化類型一起使用

泛型,我們用到最多的可能就是集合了。在實(shí)際開(kāi)發(fā)中,我們合理的使用泛型可以簡(jiǎn)化我們的開(kāi)發(fā),提高為我們的開(kāi)發(fā)效率。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容