国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java 泛型總結(一):基本用法與類型擦除

Java 泛型總結(一):基本用法與類型擦除

2020-08-31 14:21然則 Java教程

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

簡介

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

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

基本用法

泛型類

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

java" id="highlighter_19183">
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 的泛型版本:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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<Integer> list = ArrayList<>();。

泛型方法

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 是泛型方法。在返回類型之前是它的參數標識 <K,V>,注意這里有兩個泛型參數,所以泛型參數可以有多個。

調用泛型方法時可以不顯式傳入泛型參數,上面的調用就沒有。這是因為編譯器會使用參數類型推斷,根據傳入的實參的類型 (這里是 integer 和 String) 推斷出 K 和 V 的類型。

類型擦除

什么是類型擦除

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

?
1
2
3
4
5
6
7
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<Integer> 和 ArrayList<String>。在我們看來它們的參數化類型不同,一個保存整性,一個保存字符串。但是通過比較它們的 Class 對象,上面的代碼輸出是 true。這說明在 JVM 看來它們是同一個類。而在 C++、C# 這些支持真泛型的語言中,它們就是不同的類。

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

擦除帶來的問題

擦除會出現一些問題,下面是一個例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 一個邊界:

?
1
2
3
4
5
class Manipulator2<T extends HasF> {
 private T obj;
 public Manipulator2(T x) { obj = x; }
 public void manipulate() { obj.f(); }
}

現在 T 的類型是 <T extends HasF>,這表示 T 必須是 HasF 或者 HasF 的導出類型。這樣,調用 f() 方法才安全。HasF 就是 T 的邊界,因此通過類型擦除后,所有出現 T 的

地方都用 HasF 替換。這樣編譯器就知道 obj 是有方法 f() 的。

但是這樣就抵消了泛型帶來的好處,上面的類完全可以改成這樣:

?
1
2
3
4
5
class Manipulator3 {
 private HasF obj;
 public Manipulator3(HasF x) { obj = x; }
 public void manipulate() { obj.f(); }
}

所以泛型只有在比較復雜的類中才體現出作用。但是像 <T extends HasF> 這種形式的東西不是完全沒有意義的。如果類中有一個返回 T 類型的方法,泛型就有用了,因為這樣會返回準確類型。比如下面的例子:

?
1
2
3
4
5
class ReturnGenericType<T extends HasF> {
 private T obj;
 public ReturnGenericType(T x) { obj = x; }
 public T get() { return obj; }
}

這里的 get() 方法返回的是泛型參數的準確類型,而不是 HasF。

類型擦除的補償

類型擦除導致泛型喪失了一些功能,任何在運行期需要知道確切類型的代碼都無法工作。比如下面的例子:

?
1
2
3
4
5
6
7
8
9
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 是否有默認的構造器。一種解決的辦法是傳遞一個工廠對象并且通過它創建新的實例。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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());
 }
}

另一種解決的方法是利用模板設計模式:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 泛型的使用,以及類型擦除相關的問題。一般情況下泛型的使用比較簡單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時要注意類型擦除的問題。接下來會介紹數組與泛型的關系以及通配符的使用。

以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持服務器之家!

原文鏈接:https://segmentfault.com/a/1190000005179142

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: av官网在线 | 色视频在线免费观看 | 欧美午夜精品一区二区三区电影 | 成人一区二区三区 | 亚洲一区二区在线播放 | 毛片网站在线 | 99久久亚洲一区二区三区青草 | 欧美天堂一区二区三区 | 亚洲欧美国产日韩综合 | 日韩成人一区二区 | 欧美日韩亚洲一区 | 日本黄色免费网站 | 精品一区二区三区中文字幕老牛 | 久久精品 | 中国大陆高清aⅴ毛片 | 在线中文字幕视频 | 欧美性一区二区三区 | 中文字幕 亚洲一区 | 日韩av中文在线 | 久久精品99视频 | 亚洲一区二区在线 | www久草 | 国产成人在线电影 | 亚洲精品综合 | 啊啊啊网站| 久久久久久亚洲精品 | 欧美视频在线观看不卡 | 久久久久综合狠狠综合日本高清 | 午夜视频在线播放 | 欧美国产精品一区二区三区 | 欧美成人精品 | av一区二区三区 | 亚洲a在线播放 | 亚洲永久免费视频 | 亚洲国产精品一区二区www | 国产一区二区三区在线 | 成人区精品一区二区婷婷 | 97久久精品人人澡人人爽 | 国产精品久久久久无码av | 国产91精品久久久久 | 一级特黄bbbbb免费观看 |