半小時通透Java的泛型

Java 泛型

前言

編程不能停止,每天發一篇~

不要捉急往後學,前面基礎打的越牢固後面進階越好進階

基礎不牢,地動山搖

學習目標

什麼是泛型

為什麼需要泛型

如何使用泛型

如何自定義泛型

類型通配符等知識

1. 什麼是泛型

泛型不隻是 Java 語言所特有的特性,泛型是程序設計語言的一種特性。

允許程序員在強類型的程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須做出聲明。

Java 中的集合類是支持泛型的,它在代碼中是這個樣子的

請添加圖片描述

代碼中的<Integer>就是泛型,我們把類型像參數一樣傳遞,尖括號中間就是數據類型,我們可以稱之為實際類型參數,這裡實際類型參數的數據類型隻能為引用數據類型。

那麼為什麼需要泛型呢?

2. 為什麼需要泛型

我們在使用ArrayList實現類的時候,如果沒有指定泛型,IDEA會給出警告,代碼似乎也是可以順利運行的。請看如下實例:

import java.util.ArrayList;

public class testDemo1 {

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("Hello");
        String str1 = (String) arrayList.get(0);
        System.out.println("str=" + str1);
    }

}

運行結果:

str1=Hello

雖然運行時沒有發生任何異常,但這樣做有兩個缺點:

  1. 需要強制類型轉換: 由於ArrayList內部就是一個Object[]數組,在get()元素的時候,返回的是Object類型,所以在ArrayList外獲取該對象,需要強制類型轉換。其它的CollectionMap如果不使用泛型,也存在這個問題;
  2. 可向集合中添加任意類型的對象,存在類型不安全風險。例如如下代碼中,我們向列表中既添加瞭Integer類型,又添加瞭String類型:
package com.caq.oop.demo08;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        //實例化一個空列表
        List arrayList = new ArrayList<>();
        arrayList.add(123);
        arrayList.add("sad");
        String str = (String) arrayList.get(0);

    }
}

Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.caq.oop.demo08.Test.main(Test.java:12)

由於我們的“疏忽”,列表第 1 個元素實際上是整型,但被我們強制轉換為字符串類型,這是行不通的,因此會拋出ClassCastException異常。

使用泛型可以解決這些問題。泛型有如下優點:

  1. 可以減少類型轉換的次數,代碼更加簡潔;
  2. 程序更加健壯:隻要編譯期沒有警告,運行期就不會拋出ClassCastException異常;
  3. 提高瞭代碼的可讀性:編寫集合的時候,就限定瞭集合中能存放的類型。

3. 如何使用泛型

3.1 泛型使用

在代碼中,這樣使用泛型:

List<String> list = new ArrayList<String>();
// Java 7 及以後的版本中,構造方法中可以省略泛型類型:
List<String> list = new ArrayList<>();
外幣巴伯
    

要註意的是,變量聲明的類型必須與傳遞給實際對象的類型保持一致,下面是錯誤的例子:

List<Object> list = new ArrayList<String>();
List<Number> numbers = new ArrayList(Integer);

3.2 自定義泛型類

3.2.1 Java 源碼中泛型的定義

在自定義泛型類之前,我們來看下java.util.ArrayList是如何定義的:

img

類名後面的<E>就是泛型的定義,E不是 Java 中的一個具體的類型,它是 Java 泛型的通配符(註意是大寫的,實際上就是Element的含義),可將其理解為一個占位符,將其定義在類上,使用時才確定類型。

此處的命名不受限制,但最好有一定含義,例如java.lang.HashMap的泛型定義為HashMap<K,V>K表示KeyV表示Value

3.2.2 自定義泛型類實例1

下面我們來自定義一個泛型類,自定義泛型按照約定俗成可以叫<T>,具有Type的含義,實例如下:

實例演示

package com.caq.List;

public class Generic01<T> {

    private T abc;//定義在類上的泛型,在類內部可以使用

    public T getAbc() {
        return abc;
    }

    public void setAbc(T abc) {
        this.abc = abc;
    }

    public static void main(String[] args) {
        //實例化對象,指定元素類型為整型
        Generic01<Integer> integerGeneric01 = new Generic01<>();
        //調用方法
        integerGeneric01.setAbc(100);
        System.out.println("integerGeneric01="+ integerGeneric01.getAbc());

        //實例化對象,指定元素類型為長類型
        Generic01<Long> longGeneric01 = new Generic01<>();
        longGeneric01.setAbc(200L);
        System.out.println("longGeneric01="+ longGeneric01.getAbc());

        // 實例化對象,指定元素類型為雙精度浮點型
        Generic01<Double> doubleGeneric01 = new Generic01<>();
        doubleGeneric01.setAbc(300.0);
        System.out.println("doubleGeneric01="+ doubleGeneric01.getAbc());
        
    }
}

運行結果:

integerGeneric01=100
longGeneric01=200
doubleGeneric01=300.0

我們在類的定義處也定義瞭泛型:Generic01<T>;在類內部定義瞭一個T類型的abc變量,並且為其添加瞭settergetter方法。

解釋:對於泛型類的使用也很簡單,在主方法中,創建對象的時候指定T的類型分別為IntegerLongDouble,類就可以自動轉換成對應的類型瞭。

3.2.3 自定義泛型類實例2

上面我們知道瞭如何定義含有單個泛型的類,那麼對於含有多個泛型的類,如何定義呢?

我們可以看一下HashMap類是如何定義的。如下是 Java 源碼的截圖:

img

參照HashMap<K,V>類的定義,下面我們來看看如何定義含有兩個泛型的類

package com.caq.List;

public class Generic02<K, V> {//這次是定義兩個泛型在類上

    //定義類型為K的key屬型
    private K key;

    //定義類型為V的value屬型
    private V value;

    //封裝裡的知識,通過Getter和Setter方法來設置和獲取私有屬型的值
    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    public static void main(String[] args) {
        //實例化對象,分別指定類型為整型,長整型
        Generic02<Integer, Long> integerLongGeneric02 = new Generic02<>();
        //實例化對象,分別指定類型為浮點型、字符串類型
        Generic02<Float, String> floatStringGeneric02 = new Generic02<>();

        integerLongGeneric02.setKey(100);
        integerLongGeneric02.setValue(200L);
        System.out.println("key=" + integerLongGeneric02.getKey());
        System.out.println("value=" + integerLongGeneric02.getValue());

        floatStringGeneric02.setKey(0.9f);
        floatStringGeneric02.setValue("巴啦啦能量");
        System.out.println("key=" + floatStringGeneric02.getKey());
        System.out.println("value=" + floatStringGeneric02.getValue());
    }
}

運行結果:

key=100value=200key=0.9value=巴啦啦能量

3.3 自定義泛型方法

前面我們知道瞭如何定義泛型類,在類上定義的泛型,在方法中也可以使用。下面我們來看一下如何自定義泛型方法。

泛型方法不一定寫在泛型類當中。當類的調用者總是關心類中的某個泛型方法,不關心其他屬性,這個時候就沒必要再整個類上定義泛型瞭。

直接在方法上設置泛型(generic)

package com.caq.List;public class Generic03 {        public <T> void test(T t){        System.out.println(t);    }    public static void main(String[] args) {        Generic03 generic03 = new Generic03();                generic03.test("Monkey");        generic03.test(1);        generic03.test(1.00000);        generic03.test(1L);    }}

運行結果:

Monkey11.01

實例中,使用<T>來定義test方法的泛型,它接收一個泛型的參數變量並在方法體打印;調用泛型方法也很簡單,在主方法中實例化對象,調用對象下的泛型方法,可傳入不同類型的參數。

4. 泛型類的子類

泛型類也是一個 Java 類,它也具有繼承的特性。

泛型類的繼承可分為兩種情況:

  1. 子類明確泛型類的類型參數變量;
  2. 子類不明確泛型類的類型參數變量。

4.1 明確類型參數變量

例如,有一個泛型接口:

package com.caq.List;public interface GenericInterface01<T> {    default void show(T t) {            }}

泛型接口的實現類如下:

package com.caq.List;public class GenericInterfaceImple implements GenericInterface01<String> {    @Override    public void show(String s) {        System.out.println(s);    }}

子類實現明確瞭泛型的參數變量為String類型。因此方法show()的重寫也將T替換為瞭String類型。

4.2 不明確類型參數變量

當實現類不確定泛型類的參數變量時,實現類需要定義類型參數變量,調用者使用子類時,也需要傳遞類型參數變量。

如下是GenericInterface接口的另一個實現類:

package com.caq.List;

public class GenericInterfaceImple<T> implements GenericInterface01<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

在主方法中調用實現類的show()方法:

package com.caq.List;

public class GenericInterfaceImple<T> implements GenericInterface01<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }

    public static void main(String[] args) {
        GenericInterfaceImple<Integer> integerGenericInterfaceImple = new GenericInterfaceImple<>();
        integerGenericInterfaceImple.show(100);
    }
}


100

5. 類型通配符

我們先來看一個泛型作為方法參數的實例:

package com.caq.List;
/**
 * 遍歷並打印集合中的每一個元素
 * 遍歷是二叉樹上最重要的運算之一,是二叉樹上進行其它運算之基礎。 樹的遍歷是樹的一種重要的運算。 
 * 所謂遍歷是指對樹中所有結點的信息的訪問,即依次對樹中每個結點訪問一次且僅訪問一次。

 * @param list 要接收的集合
 */
public class Generic04 {
    public void printListElement(List<object> list){
        for (Object o :
                list) {
            System.out.println(o);
        }
    }
}

觀察上面的代碼,參數list的限定的泛型類型為Object, 也就是說,這個方法隻能接收元素為Object類型的集合,如果我們想傳遞其他元素類型的集合,是行不通的。例如,如果傳遞裝載Integer元素的集合,程序在編譯階段就會報錯:

請添加圖片描述

Tips: 泛型中的List<Object>並不是List<Integer>的父類,它們不滿足繼承關系。

5.1 無限定通配符

想要解決這個問題,使用類型通配符即可,修改方法參數處的代碼,將<>中間的Object改為?即可:

    public void printListElement(List<?> list){

此處的?就是類型通配符,表示可以匹配任意類型,因此調用方可以傳遞任意泛型類型的列表。

實例演示

package com.caq.List;

import java.util.ArrayList;
import java.util.List;


public class Generic04 {
//List<?>可以理解為列表的類型,可以是整數型列表,也可以是字符串類型列表,list代表的是遍歷的元素代表
    public void printListElement(List<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        //實例化一個整型列表
        List<Integer> interger = new ArrayList<>();
        //加元素
        interger.add(1);
        interger.add(2);
        interger.add(2222);

        //實例化對象
        Generic04 generic04 = new Generic04();
        generic04.printListElement(interger);

        //實例化一個字符串類型列表
        ArrayList<String> strings = new ArrayList<>();
        strings.add("element1");
        strings.add("element2");
        strings.add("element3");

        generic04.printListElement(strings);
    }
}

運行結果:

1
2
2222
element1
element2
element3

5.2 extends 通配符

extends通配符用來限定泛型的上限。什麼意思呢?依舊以上面的實例為例,我們來看一個新的需求,我們希望方法接收的List 集合限定在數值類型內(float、integer、double、byte 等),不希望其他類型可以傳入(比如字符串)。此時,可以改寫上面的方法定義,設定上界通配符:

public void printListElement(List<? extends Number> list) {

這樣的寫法的含義為:List集合裝載的元素隻能是Number自身或其子類(Number類型是所有數值類型的父類),完整實例如下:

import java.util.ArrayList;
import java.util.List;

public class Generic04 {

    public void printListElement(List<? extends Number> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        // 實例化一個整型的列表
        List<Integer> integers = new ArrayList<>();
        // 添加元素
        integers.add(1);
        integers.add(2);
        integers.add(3);
        GenericDemo4 generic04 = new Generic04();
        // 調用printListElement()方法
        generic04.printListElement(integers);

    }
}

運行結果:

1
2
3

5.3 super 通配符

既然已經瞭解瞭如何設定通配符上界,也就不難理解通配符的下界瞭,可以限定傳遞的參數隻能是某個類型的父類。

語法如下:

<? super Type>

6. 小結

  • 使用泛型可以避免強制類型轉換,也可以避免運行期就拋出的ClassCastException異常
  • 在使用泛型時,要註意變量聲明的泛型類型要匹配傳遞給實際對象的類型, Java 7 及以後的版本中,構造方法中可以省略泛型類型,推薦直接省略
  • 如何自定義泛型類和泛型方法,在實際的開發中,我們想要編寫比較通用的代碼就避免不瞭使用泛型,慢慢體會星弟們~~~
  • 另外,泛型也是可以繼承的
  • 類型通配符的概念和使用場景

到此這篇關於半小時通透Java的泛型的文章就介紹到這瞭,更多相關Java泛型內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: