Java 泛型類型基礎


為什么要使用泛型?

未使用泛型的情況:

// 創建列表類
List list = new ArrayList();
// 添加一個類型為 String 的列表元素
list.add("hello");
// 強制轉換為 String 類型,再賦值給類型為 s 的引用變量
String s = (String) list.get(0);

使用泛型的情況:

// 創建泛型類,<String> 為類型參數
List<String> list = new ArrayList<String>();
// 添加一個類型為 String 的列表元素
list.add("hello");
// 這里不需要強制類型轉換
String s = list.get(0);

好處:實現通用的泛型算法,處理不同類型的集合,可以自定義類型,類型安全,便於閱讀。


泛型類型

一個泛型類型是一個類型參數化(<類型參數>)的泛型類或接口。

一個簡單的 Box 類

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

Box 類中方法接受或返回一個對象,除了基本類型外你可以傳入任何對象。編譯時無法檢查類是如何使用的,如果傳入一個 Integer 並希望獲取 Integer,但卻錯誤傳入 String 對象,那么就會導致運行錯誤。

泛型版本 Box 類

泛型類型定義格式:

class name<T1, T2, ..., Tn> { /* ... */ } //T1, T2, ..., Tn為類型參數

類型參數(也成為類型變量)跟在類名稱后面,類型參數放在尖括號(<>)中(T1, T2, ..., and Tn),

修改后的泛型 Box 類:

/**
 * Box 類的泛型版本
 * @param <T> 類型的值被裝箱
 */
public class Box<T> {
    // T 表示 "類型"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

上面代碼中,Box 類中的所有 Object 類型都被替換為 T 類型,類型變量可以是指定的任何非基本類型:任何類類型、任何接口類型、任何數組類型,甚至是任何其他的類型變量。

泛型技術也可以實現通用的泛型接口。

類型參數的命名約定

按照慣例,類型參數名為單個大寫字母。這與已知的變量命名約定形成鮮明對比,這樣做有充分的理由:如果沒有這個約定,那么很難區分類型變量和普通類或接口名稱之間的區別。

最常用的類型參數名稱:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

調用和實例化泛型類型

用具體的類型替換類型變量 T 就可以實例化泛型類型,例如:

Box <Integer> integerBox = new Box <Integer>();

通用類型的調用一般稱為“參數化類型”。

像往常一樣使用new關鍵字實例化,但在類名和括號之間放置<Integer>:

在 Java SE 7 和更高的版本中,只要編譯器可以根據上下文確定或推斷類型參數,就可以用一組空類型參數(<>)替換調用泛型類的構造函數所需的類型參數。例如:

Box<Integer> integerBox = new Box<>();

多類型參數

泛型類可以擁有多個類型參數。例如,實現通用 Pair 接口的通用 OrderedPair 類:

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
    	this.key = key;
    	this.value = value;
    }

    public K getKey()	{ return key; }
    public V getValue() { return value; }
}

以下語句創建 OrderedPair 類的兩個實例:

Pair <String,Integer> p1 = new OrderedPair <String,Integer>(“Even”,8);
Pair <String,String> p2 = new OrderedPair <String,String>(“hello”,“world”);

新的OrderedPair <String,Integer>代碼將 K 實例化為一個字符串,將 V 實例化為一個整數。因此,OrderedPair 的構造函數的參數類型分別是 String 和 Integer。由於自動裝箱,將 String 和 int 傳遞給類是有效的。

正如前文所述,由於Java編譯器可以從OrderedPair <String,Integer>聲明中推斷 K 和 V 類型,因此可以使用以下縮寫:

OrderedPair <String,Integer> p1 = new OrderedPair <>(“Even”,8);
OrderedPair <String,String> p2 = new OrderedPair <>(“hello”,“world”);

參數化類型

您也可以用參數化類型(即List )替換類型參數(即 K 或 V)。例如,使用 OrderedPair <K,V>示例:

OrderedPair <String,Box <Integer>> p = new OrderedPair <>(“primes”,new Box <Integer>(...));

原始類型

原始類型是沒有任何類型參數的泛型類或接口的名稱。

例如,給定一個 Box 泛型類:

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

要創建參數化類型,為形式類型參數 T 提供實際類型參數:

Box<Integer> intBox = new Box<>();

如果省略實際類型參數,則會創建一個Box<T>的原始類型:

Box rawBox = new Box();

因此,Box 是泛型Box<T>的原始類型。但是,非泛型類或接口類型不是原始類型。

在 JDK5.0 之前很多 API 類(如 Collections 類)不是通用的,為了向后兼容,允許將參數化類型分配給其原始類型:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

但是,將原始類型分配給參數化類型,編譯器會發出一個警告:

Box rawBox = new Box();           // rawBox 是 Box<T> 的原始類型
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

如果使用原始類型來調用相應泛型類型中定義的泛型方法,則還會收到警告:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

該警告顯示原始類型繞過了泛型類型檢查,將不安全代碼的捕獲推遲到運行時。因此,你應該避免使用原始類型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM