日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

大白话说Java泛型:入门、使用、原理

發(fā)布時(shí)間:2025/3/8 java 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大白话说Java泛型:入门、使用、原理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章首發(fā)于【博客園-陳樹義】,點(diǎn)擊跳轉(zhuǎn)到原文《大白話說Java泛型:入門、使用、原理》

遠(yuǎn)在 JDK 1.4 版本的時(shí)候,那時(shí)候是沒有泛型的概念的。當(dāng)時(shí) Java 程序員們寫集合類的代碼都是類似于下面這樣:

List list = new ArrayList(); list.add("www.cnblogs.com"); list.add(23); String name = (String)list.get(0); Integer number = (Integer)list.get(1);

在代碼中聲明一個(gè)集合,我們可以往集合中放入各種各樣的數(shù)據(jù),而在取出來的時(shí)候就進(jìn)行強(qiáng)制類型轉(zhuǎn)換。但其實(shí)這樣的代碼存在一定隱患,因?yàn)榭赡苓^了不久我們就會(huì)忘記到底我們存放的 list 里面到底第幾個(gè)是 String,第幾個(gè)是 Integer 了。這樣就會(huì)出現(xiàn)下面這樣的情況:

List list = new ArrayList(); list.add("www.cnblogs.com"); list.add(23); String name = (String)list.get(0); String number = (String)list.get(1); //ClassCastException

上面的代碼在運(yùn)行時(shí)會(huì)發(fā)生強(qiáng)制類型轉(zhuǎn)換異常。這是因?yàn)槲覀冊(cè)诖嫒氲臅r(shí)候,第二個(gè)是一個(gè) Integer 類型,但是取出來的時(shí)候卻將其強(qiáng)制轉(zhuǎn)換為 String 類型了。Sun 公司為了使 Java 語言更加安全,減少運(yùn)行時(shí)異常的發(fā)生。于是在 JDK 1.5 之后推出了泛型的概念。

于是在 JDK 1.5 之后,我們?nèi)绻褂眉蟻頃鴮懘a,可以使用下面這種形式:

List<String> list = new ArrayList(); list.add("www.cnblogs.com"); list.add("www.cnblogs.com/chanshuyi"); String cnBlogs = list.get(0); String myWebSite = list.get(1);

泛型就是將類型參數(shù)化,其在編譯時(shí)才確定具體的參數(shù)。在上面這個(gè)例子中,這個(gè)具體的類型就是 String。可以看到我們?cè)趧?chuàng)建 List 集合的時(shí)候指定了 String 類型,這就意味著我們只能往 List 集合中存放 String 類型的數(shù)據(jù)。而當(dāng)我們指定泛型之后,我們?nèi)ト〕鰯?shù)據(jù)后就不再需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換了,這樣就減少了發(fā)生強(qiáng)制類型轉(zhuǎn)換的風(fēng)險(xiǎn)。

泛型的原理

上面我們通過兩個(gè)很簡(jiǎn)單的例子知道了為什么要有泛型,以及泛型最簡(jiǎn)單的使用。下面我們通過一個(gè)面試中常見的例子來看一下泛型的本質(zhì)是什么。

ArrayList<String> a = new ArrayList<String>(); ArrayList<Integer> b = new ArrayList<Integer>(); Class c1 = a.getClass(); Class c2 = b.getClass(); System.out.println(c1 == c2);

在繼續(xù)往下看之前,先想一想,這道題輸出的結(jié)果是什么?

是 true 還是 false ?

這道題輸出的結(jié)果是 true。因?yàn)闊o論對(duì)于 ArrayList 還是 ArrayList,它們的 Class 類型都是一直的,都是 ArrayList.class。

那它們聲明時(shí)指定的 String 和 Integer 到底體現(xiàn)在哪里呢?

答案是體現(xiàn)在類編譯的時(shí)候。當(dāng) JVM 進(jìn)行類編譯時(shí),會(huì)進(jìn)行泛型檢查,如果一個(gè)集合被聲明為 String 類型,那么它往該集合存取數(shù)據(jù)的時(shí)候就會(huì)對(duì)數(shù)據(jù)進(jìn)行判斷,從而避免存入或取出錯(cuò)誤的數(shù)據(jù)。

也就是說:泛型只存在于編譯階段,而不存在于運(yùn)行階段。在編譯后的 class 文件中,是沒有泛型這個(gè)概念的。

上面我們只是說了泛型在集合中的使用方式,但其實(shí)泛型的應(yīng)用范圍不僅僅只是集合,還包括類、方法、Map 接口等等。

泛型的使用情景

泛型的應(yīng)用還廣泛存在于下面幾種情形:泛型類、泛型方法、泛型集合。

泛型類

泛型類一般使用字母 T 作為泛型的標(biāo)志。

public class GenericClass<T> {private T object;public T getObject() {return object;}public void setObject(T object) {this.object = object;} }

使用:

public static void main(String[] args) {GenericClass<Integer> integerGenericClass = new GenericClass<>(100);integerGenericClass.showType();GenericClass<String> stringGenericClass = new GenericClass<>("www.cnblogs.com/chanshuyi");stringGenericClass.showType(); }

除了使用 T 作為泛型類的標(biāo)志之外,在需要使用 Map 的類中,通常使用 K V 兩個(gè)字母表示 Key Value 對(duì)應(yīng)的類型。

public class GenericMap<K, V> {private K key;private V value;public void put(K key, V value) {this.key = key;this.value = value;} }

使用:

public static void main(String[] args) {GenericMap<Integer, String> team = new GenericMap<>();team.put(1, "YaoMin");team.put(2, "Me");GenericMap<String, Integer> score = new GenericMap<>();score.put("YaoMin", 88);score.put("Me", 80);}

泛型方法

泛型方法一般使用字母 T 作為泛型的標(biāo)志。

public class GenericMethod {public static <T> T getObject(Class<T> clz) throws InstantiationException, IllegalAccessException{T t = clz.newInstance();return t;} }

使用:

public static void main(String[] args) throws Exception{GenericMethod genericMethod = getObject(GenericMethod.class);System.out.println("Class:" + genericMethod.getClass().getName()); }

泛型通配符

除了泛型類、泛型方法之外,泛型還有更加復(fù)雜的應(yīng)用,如:

List<? extends Number> list = new ArrayList(); List<? super Number> list = new ArrayList();

上面的 extends 和 super 關(guān)鍵字其實(shí)就是泛型的高級(jí)應(yīng)用:泛型通配符。

但在講泛型通配符之前,我們必須對(duì)編譯時(shí)類型和運(yùn)行時(shí)類型有一個(gè)基本的了解,才能更好地理解通配符的使用。

編譯時(shí)類型和運(yùn)行時(shí)類型

我們先來看看一個(gè)簡(jiǎn)單的例子。

Class Fruit{} Class Apple extends Fruit{}

上面聲明一個(gè) Fruit 類,Apple 類是 Fruit 類的子類。

接著下面我們聲明一個(gè)蘋果對(duì)象:

Apple apple = new Apple();

這樣的聲明,我相信大家都沒有什么異議,聲明一個(gè) Apple 類型的變量指向一個(gè) Apple 對(duì)象。在上面這段代碼中,apple 屬性指向的對(duì)象,其編譯時(shí)類型和運(yùn)行時(shí)類型都是 Apple 類型。

但其實(shí)很多時(shí)候我們也使用下面這種寫法:

Fruit apple = new Apple();

我們使用 Fruit 類型的變量指向了一個(gè) Apple 對(duì)象,這在 Java 的語法體系中也是沒有問題的。因?yàn)?Java 允許把一個(gè)子類對(duì)象(Apple對(duì)象)直接賦值給一個(gè)父類引用變量(Fruit類變量),一般我們稱之為「向上轉(zhuǎn)型」。

那問題來了,此時(shí) apple 屬性所指向的對(duì)象,其編譯時(shí)類型和運(yùn)行時(shí)類型是什么呢?

很多人會(huì)說:apple 屬性指向的對(duì)象,其編譯時(shí)類型和運(yùn)行時(shí)類型不都是 Apple 類型嗎?

正確答案是:apple 屬性所指向的對(duì)象,其在編譯時(shí)的類型就是 Fruit 類型,而在運(yùn)行時(shí)的類型就是 Apple 類型。

這是為什么呢?

因?yàn)樵诰幾g的時(shí)候,JVM 只知道 Fruit 類變量指向了一個(gè)對(duì)象,并且這個(gè)對(duì)象是 Fruit 的子類對(duì)象或自身對(duì)象,其具體的類型并不確定,有可能是 Apple 類型,也有可能是 Orange 類型。而為了安全方面的考慮,JVM 此時(shí)將 apple 屬性指向的對(duì)象定義為 Fruit 類型。因?yàn)闊o論其是 Apple 類型還是 Orange 類型,它們都可以安全轉(zhuǎn)為 Fruit 類型。

而在運(yùn)行時(shí)階段,JVM 通過初始化知道了它指向了一個(gè) Apple 對(duì)象,所以其在運(yùn)行時(shí)的類型就是 Apple 類型。

泛型中的向上轉(zhuǎn)型

當(dāng)我們明白了編譯時(shí)類型和運(yùn)行時(shí)類型之后,我們?cè)賮砝斫馔ㄅ浞恼Q生就相對(duì)容易一些了。

還是上面的場(chǎng)景,我們有一個(gè) Fruit 類,Apple 類是 Fruit 的子類。這時(shí)候,我們?cè)黾右粋€(gè)簡(jiǎn)單的容器:Plate 類。Plate 類定義了盤子一些最基本的動(dòng)作:

public class Plate<T> {private List<T> list;public Plate(){} public void add(T item){list.add(item);}public T get(){return list.get(0);} }

按我們之前對(duì)泛型的學(xué)習(xí),我們可以知道上面的代碼定義了一個(gè) Plate 類。Plate 類定義了一個(gè) T 泛型類型,可以接收任何類型。說人話就是:我們定義了一個(gè)盤子類,這個(gè)盤子可以裝任何類型的東西,比如裝水果、裝蔬菜。

如果我們想要一個(gè)裝水果的盤子,那定義的代碼就是這樣的:

Plate<Fruit> plate = new Plate<Fruit>();

我們直接定義了一個(gè) Plate 對(duì)象,并且指定其泛型類型為 Fruit 類。這樣我們就可以往里面加水果了:

plate.add(new Fruit()); plate.add(new Apple());

按照 Java 向上轉(zhuǎn)型的原則,Java 泛型可以向上轉(zhuǎn)型,即我們上面關(guān)于水果盤子的定義可以變?yōu)檫@樣:

Plate<Fruit> plate = new Plate<Apple>(); //Error

但事實(shí)上,上面的代碼在編譯的時(shí)候會(huì)出現(xiàn)編譯錯(cuò)誤。

按理說,這種寫法應(yīng)該是沒有問題的,因?yàn)?Java 支持向上轉(zhuǎn)型嘛。

錯(cuò)誤的原因就是:Java并不支持支持泛型的向上轉(zhuǎn)型,所以不能夠使用上面的寫法,這樣的寫法在Java中是不被支持的。

那有沒有解決的辦法呢?

肯定是有的,這個(gè)解決方案就是:泛型通配符。

上面這行代碼如果要正常編譯,只需要修改一下 Plate 類的聲明即可:

Plate<? extends Fruit> plate = new Plate<Apple>();

上面的這行代碼表示:plate 可以指向任何 Fruit 類對(duì)象,或者任何 Fruit 的子類對(duì)象。

Apple 是 Fruit 的子類,自然就可以正常編譯了。

extends 通配符的缺陷

雖然通過這種方式,Java 支持了 Java 泛型的向上轉(zhuǎn)型,但是這種方式是有缺陷的,那就是:其無法向 Plate 中添加任何對(duì)象,只能從中讀取對(duì)象。

Plate<? extends Fruit> plate = new Plate<Apple>(); plate.add(new Apple()); //Compile Error plate.get(); // Compile Success

可以看到,當(dāng)我們嘗試往盤子中加入一個(gè)蘋果時(shí),會(huì)發(fā)現(xiàn)編譯錯(cuò)誤。但是我們可以從中取出東西。那為什么我們會(huì)無法往盤子中加?xùn)|西呢?

這還得從我們對(duì)盤子的定義說起。

Plate<? extends Fruit> plate = new Plate<XXX>();

上面我們對(duì)盤子的定義中,plate 可以指向任何 Fruit 類對(duì)象,或者任何 Fruit 的子類對(duì)象。也就是說,plate 屬性指向的對(duì)象其在運(yùn)行時(shí)可以是 Apple 類型,也可以是 Orange 類型,也可以是 Banana 類型,只要它是 Fruit 類,或任何 Fruit 的子類即可。即我們下面幾種定義都是正確的:

Plate<? extends Fruit> plate = new Plate<Apple>(); Plate<? extends Fruit> plate = new Plate<Orange>(); Plate<? extends Fruit> plate = new Plate<Banana>();

這樣子的話,在我們還未具體運(yùn)行時(shí),JVM 并不知道我們要往盤子里放的是什么水果,到底是蘋果,還是橙子,還是香蕉,完全不知道。既然我們不能確定要往里面放的類型,那 JVM 就干脆什么都不給放,避免出錯(cuò)。

正是出于這種原因,所以當(dāng)使用 extends 通配符時(shí),我們無法向其中添加任何東西。

那為什么又可以取出數(shù)據(jù)呢?因?yàn)闊o論是取出蘋果,還是橙子,還是香蕉,我們都可以通過向上轉(zhuǎn)型用 Fruit 類型的變量指向它,這在 Java 中都是允許的。

Fruit apple = plate.get(); Apple apple = plate.get(); //Error

可以從上面的代碼看到,當(dāng)你嘗試用一個(gè) Apple 類型的變量指向一個(gè)從盤子里取出的水果時(shí),是會(huì)提示錯(cuò)誤的。

所以當(dāng)使用 extends 通配符時(shí),我們可以取出所有東西。

總結(jié)一下,我們通過 extends 關(guān)鍵字可以實(shí)現(xiàn)向上轉(zhuǎn)型。但是我們卻失去了部分的靈活性,即我們不能往其中添加任何東西,只能取出東西。

super 通配符的缺陷

與 extends 通配符相似的另一個(gè)通配符是 super 通配符,其特性與 extends 完全相反。super通配符可以存入對(duì)象,但是取出對(duì)象的時(shí)候受到限制。

Plate<? super Apple> plate = new Plate<Fruit>();

上面這行代碼表示 plate 屬性可以指向一個(gè)特定類型的 Plate 對(duì)象,只要這個(gè)特定類型是 Apple 或 Apple 的父類。上面的 Fruit 類就是 Apple 類的父級(jí),所以上面的語法是對(duì)的。

也就是說,如果 EatThing 類是 Fruit 的父級(jí),那么下面的聲明也是正確的:

Plate<? super Apple> plate = new Plate<EatThing>();

當(dāng)然了,下面的聲明肯定也是對(duì)的,因?yàn)?Object 是任何一個(gè)類的父級(jí)。

Plate<? super Apple> plate = new Plate<Object>();

既然這樣,也就是說 plate 指向的具體類型可以是任何 Apple 的父級(jí),JVM 在編譯的時(shí)候肯定無法判斷具體是哪個(gè)類型。但 JVM 能確定的是,任何 Apple 的子類都可以轉(zhuǎn)為 Apple 類型,但任何 Apple 的父類都無法轉(zhuǎn)為 Apple 類型。

所以對(duì)于使用了 super 通配符的情況,我們只能存入 T 類型及 T 類型的子類對(duì)象。

Plate<? super Apple> plate = new Plate<Fruit>(); plate.add(new Apple()); plate.add(new Fruit()); //Error

當(dāng)我們向 plate 存入 Apple 對(duì)象時(shí),編譯正常。但是存入 Fruit 對(duì)象,就會(huì)報(bào)編譯錯(cuò)誤。

而當(dāng)我們?nèi)〕鰯?shù)據(jù)的時(shí)候,也是類似的道理。JVM 在編譯的時(shí)候知道,我們具體的運(yùn)行時(shí)類型可以是任何 Apple 的父級(jí),那么為了安全起見,我們就用一個(gè)最頂層的父級(jí)來指向取出的數(shù)據(jù),這樣就可以避免發(fā)生強(qiáng)制類型轉(zhuǎn)換異常了。

Object object = plate.get(); Apple apple = plate.get(); //Error Fruit fruit = plate.get(); //Error

從上面的代碼可以知道,當(dāng)使用 Apple 類型或 Fruit 類型的變量指向 plate 取出的對(duì)象,會(huì)出現(xiàn)編譯錯(cuò)誤。而使用 Object 類型的額變量指向 plate 取出的對(duì)象,則可以正常通過。

也就是說對(duì)于使用了 super 通配符的情況,我們?nèi)〕龅臅r(shí)候只能用 Object 類型的屬性指向取出的對(duì)象。

PECS 原則

說到這里,我相信大家已經(jīng)明白了 extends 和 super 通配符的使用和限制了。我們知道:

  • 對(duì)于 extends 通配符,我們無法向其中加入任何對(duì)象,但是我們可以進(jìn)行正常的取出。
  • 對(duì)于 super 通配符,我們可以存入 T 類型對(duì)象或 T 類型的子類對(duì)象,但是我們?nèi)〕龅臅r(shí)候只能用 Object 類變量指向取出的對(duì)象。

從上面的總結(jié)可以看出,extends 通配符偏向于內(nèi)容的獲取,而 super 通配符更偏向于內(nèi)容的存入。我們有一個(gè) PECS 原則(Producer Extends Consumer Super)很好的解釋了這兩個(gè)通配符的使用場(chǎng)景。

Producer Extends 說的是當(dāng)你的情景是生產(chǎn)者類型,需要獲取資源以供生產(chǎn)時(shí),我們建議使用 extends 通配符,因?yàn)槭褂昧?extends 通配符的類型更適合獲取資源。

Consumer Super 說的是當(dāng)你的場(chǎng)景是消費(fèi)者類型,需要存入資源以供消費(fèi)時(shí),我們建議使用 super 通配符,因?yàn)槭褂?super 通配符的類型更適合存入資源。

但如果你既想存入,又想取出,那么你最好還是不要使用 extends 或 super 通配符。

總結(jié)

Java 泛型通配符的出現(xiàn)是為了使 Java 泛型也支持向上轉(zhuǎn)型,從而保持 Java 語言向上轉(zhuǎn)型概念的統(tǒng)一。但與此同時(shí),也導(dǎo)致 Java 通配符出現(xiàn)了一些缺陷,使得其有特定的使用場(chǎng)景。

文章首發(fā)于【博客園-陳樹義】,點(diǎn)擊跳轉(zhuǎn)到原文《大白話說Java泛型:入門、使用、原理》

轉(zhuǎn)載于:https://www.cnblogs.com/chanshuyi/p/deep_insight_java_generic.html

總結(jié)

以上是生活随笔為你收集整理的大白话说Java泛型:入门、使用、原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。