搜尋
首頁Javajava教程Java泛型總結(一)-基本用法與型別擦除的詳解

本文主要介紹了Java泛型的使用以及與類型擦除相關的問題。具有很好的參考價值。下面跟著小編一起來看下吧

簡介

Java 在1.5 引進了泛型機制,泛型本質是參數化類型,也就是說變數的類型是一個參數,在使用時再指定為具體類型。泛型可以用於類別、介面、方法,透過使用泛型可以使程式碼更簡單、安全。然而 Java 中的泛型使用了型別擦除,所以只是偽泛型。這篇文章對泛型的使用以及存在的問題做個總結,主要參考自 《Java 程式設計思想》。

這個系列的另外兩篇文章:

  • Java 泛型總結(二):泛型與陣列

  • Java 泛型總結(三):通配符的使用

基本用法

##泛型類別

如果有一個類別Holder 用來包裝一個變量,這個變數的型別可能是任意的,要怎麼寫Holder 呢?在沒有泛型之前可以這樣:

public class Holder1 {
 private Object a;
 public Holder1(Object a) {
 this.a = a;
 }
 public void set(Object a) {
 this.a = a;
 }
 public Object get(){
 return a;
 }
 public static void main(String[] args) {
 Holder1 holder1 = new Holder1("not Generic");
 String s = (String) holder1.get();
 holder1.set(1);
 Integer x = (Integer) holder1.get();
 }
}

在 Holder1 中,有一個用 Object 引用的變數。因為任何類型都可以向上轉型為 Object,所以這個 Holder 可以接受任何類型。取出的時候 Holder 只知道它保存的是一個 Object

物件,所以要強制轉換為對應的型別。在 main 方法中, holder1 先是保存了一個字串,也就是 String 對象,接著又變成保存一個 Integer 對象(參數 1 會自動裝箱)。從 Holder 取出變數時強制轉換已經比較麻煩,這裡還要記住不同的類型,要是轉錯了就會出現運行時異常。

下面看看 Holder 的泛型版本:

public class Holder2<T> {
 private T a;
 public Holder2(T a) {
 this.a = a;
 }
 public T get() {
 return a;
 }
 public void set(T a) {
 this.a = a;
 }
 public static void main(String[] args) {
 Holder2<String> holder2 = new Holder2<>("Generic");
 String s = holder2.get();
 holder2.set("test");
 holder2.set(1);//无法编译 参数 1 不是 String 类型
 }
}

在 Holder2 中, 變數 a 是一個參數化型別 T,T 只是一個標識,用其它字母也是可以的。建立 Holder2 物件的時候,在尖括號中傳入了參數 T 的類型,那麼在這個物件中,所有出現 T 的地方都相當於用 String 取代了。現在的 get 的取出的不是 Object ,而是 String 對象,因此不需要

類型轉換。另外,當呼叫 set 時,只能傳入 String 類型,否則編譯無法通過。這就保證了 holder2 的型別安全,避免因為不小心傳入錯誤的型別。

透過上面的例子可以看出泛使得程式碼更簡單、更安全。引入泛型之後,Java 函式庫的一些類別,例如常用的容器類別也被改寫為支援泛型,我們使用的時候都會傳入參數類型,如:ArrayList list = ArrayList();。

泛型方法

泛型不僅可以針對類,還可以單獨使某個方法是泛型的,舉個例子:

public class GenericMethod {
 public <K,V> void f(K k,V v) {
 System.out.println(k.getClass().getSimpleName());
 System.out.println(v.getClass().getSimpleName());
 }
 public static void main(String[] args) {
 GenericMethod gm = new GenericMethod();
 gm.f(new Integer(0),new String("generic"));
 }
}

代码输出:
 Integer
 String

GenericMethod 類別本身不是泛型的,創建它的物件的時候不需要傳入泛型參數,但是它的方法f 是泛型方法。在回傳型別之前是它的參數標識 ,注意這裡有兩個泛型參數,所以泛型參數可以有多個。

呼叫泛型方法時可以不明確傳入泛型參數,上面的呼叫就沒有。這是因為編譯器會使用參數型別推斷,根據傳入的實參的型別 (這裡是 integer 和 String) 推斷 K 和 V 的型別。

類型擦除

什麼是類型擦除

Java 的泛型使用了類型擦除機制,這個引來了很大的爭議,以至於Java 的泛型功能受到限制,只能說是」偽泛型「。什麼叫類型擦除呢?簡單的說法就是,型別參數只存在於編譯期,在執行時,Java 的虛擬機器 ( JVM ) 並不知道泛型的存在。先看個例子:

public class ErasedTypeEquivalence {
 public static void main(String[] args) {
 Class c1 = new ArrayList<String>().getClass();
 Class c2 = new ArrayList<Integer>().getClass();
 System.out.println(c1 == c2);
 }
}

上面的程式碼有兩個不同的 ArrayList:ArrayList 和 ArrayList。在我們看來它們的參數化類型不同,一個保存整性,一個保存字串。但透過比較它們的 Class 對象,上面的程式碼輸出是 true。這說明在 JVM 看來它們是同一個類別。而在 C++、

C# 這些支援真泛型的語言中,它們就是不同的類別。

泛型參數會擦除到它的第一個邊界,比如說上面的Holder2 類,參數類型是一個單獨的T,那麼就擦除到Object,相當於所有出現T 的地方都用Object 取代。所以在 JVM 看來,保存的變數 a 還是 Object 類型。之所以取出來自動就是我們傳入的參數類型,這是因為編譯器在編譯產生的字節碼檔案中插入了類型轉換的程式碼,不需要我們手動轉型了。如果參數類型有邊界那麼就擦除到它的第一個邊界,這個下一節再說。

擦除带来的问题

擦除会出现一些问题,下面是一个例子:

class HasF {
 public void f() {
 System.out.println("HasF.f()");
 }
}
public class Manipulator<T> {
 private T obj;
 public Manipulator(T obj) {
 this.obj = obj;
 }
 public void manipulate() {
 obj.f(); //无法编译 找不到符号 f()
 }
 public static void main(String[] args) {
 HasF hasF = new HasF();
 Manipulator<HasF> manipulator = new Manipulator<>(hasF);
 manipulator.manipulate();
 }
}

上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:

class Manipulator2<T extends HasF> {
 private T obj;
 public Manipulator2(T x) { obj = x; }
 public void manipulate() { obj.f(); }
}

现在 T 的类型是 ,这表示 T 必须是 HasF 或者 HasF 的导出类型。这样,调用 f() 方法才安全。HasF 就是 T 的边界,因此通过类型擦除后,所有出现 T 的

地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。

但是这样就抵消了泛型带来的好处,上面的类完全可以改成这样:

class Manipulator3 {
 private HasF obj;
 public Manipulator3(HasF x) { obj = x; }
 public void manipulate() { obj.f(); }
}

所以泛型只有在比较复杂的类中才体现出作用。但是像 这种形式的东西不是完全没有意义的。如果类中有一个返回 T 类型的方法,泛型就有用了,因为这样会返回准确类型。比如下面的例子:

class ReturnGenericType<T extends HasF> {
 private T obj;
 public ReturnGenericType(T x) { obj = x; }
 public T get() { return obj; }
}

这里的 get() 方法返回的是泛型参数的准确类型,而不是 HasF。

类型擦除的补偿

类型擦除导致泛型丧失了一些功能,任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子:

public class Erased<T> {
 private final int SIZE = 100;
 public static void f(Object arg) {
 if(arg instanceof T) {} // Error
 T var = new T(); // Error
 T[] array = new T[SIZE]; // Error
 T[] array = (T)new Object[SIZE]; // Unchecked warning
 }
}

通过 new T() 创建对象是不行的,一是由于类型擦除,二是由于编译器不知道 T 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。

interface FactoryI<T> {
 T create();
}
class Foo2<T> {
 private T x;
 public <F extends FactoryI<T>> Foo2(F factory) {
 x = factory.create();
 }
 // ...
}
class IntegerFactory implements FactoryI<Integer> {
 public Integer create() {
 return new Integer(0);
 }
}
class Widget {
 public static class Factory implements FactoryI<Widget> {
 public Widget create() {
 return new Widget();
 }
 }
}
public class FactoryConstraint {
 public static void main(String[] args) {
 new Foo2<Integer>(new IntegerFactory());
 new Foo2<Widget>(new Widget.Factory());
 }
}

另一种解决的方法是利用模板设计模式

abstract class GenericWithCreate<T> {
 final T element;
 GenericWithCreate() { element = create(); }
 abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
 X create() { return new X(); }
 void f() {
 System.out.println(element.getClass().getSimpleName());
 }
}
public class CreatorGeneric {
 public static void main(String[] args) {
 Creator c = new Creator();
 c.f();
 }
}

具体类型的创建放到了子类继承父类时,在 create 方法中创建实际的类型并返回。

总结

本文介绍了 Java 泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。接下来会介绍数组与泛型的关系以及通配符的使用。

以上是Java泛型總結(一)-基本用法與型別擦除的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能