Java中的泛型
1. 什么是泛型
泛型不只是 Java 語(yǔ)言所特有的特性,泛型是程序設(shè)計(jì)語(yǔ)言的一種特性。
允許程序員在強(qiáng)類型的程序設(shè)計(jì)語(yǔ)言中編寫(xiě)代碼時(shí)定義一些可變部分,那些部分在使用前必須做出聲明。
Java 中的集合類是支持泛型的,它在代碼中是這個(gè)樣子的
代碼中的<Integer>
就是泛型,我們把類型像參數(shù)一樣傳遞,尖括號(hào)中間就是數(shù)據(jù)類型,我們可以稱之為實(shí)際類型參數(shù),這里實(shí)際類型參數(shù)的數(shù)據(jù)類型只能為引用數(shù)據(jù)類型。
那么為什么需要泛型呢?
2. 為什么需要泛型
我們?cè)谑褂?code>ArrayList實(shí)現(xiàn)類的時(shí)候,如果沒(méi)有指定泛型,IDEA
會(huì)給出警告,代碼似乎也是可以順利運(yùn)行的。請(qǐng)看如下實(shí)例:
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); } }
運(yùn)行結(jié)果:
str1=Hello
雖然運(yùn)行時(shí)沒(méi)有發(fā)生任何異常,但這樣做有兩個(gè)缺點(diǎn):
- 需要強(qiáng)制類型轉(zhuǎn)換: 由于
ArrayList
內(nèi)部就是一個(gè)Object[]
數(shù)組,在get()
元素的時(shí)候,返回的是Object
類型,所以在ArrayList
外獲取該對(duì)象,需要強(qiáng)制類型轉(zhuǎn)換。其它的Collection
、Map
如果不使用泛型,也存在這個(gè)問(wèn)題; - 可向集合中添加任意類型的對(duì)象,存在類型不安全風(fēng)險(xiǎn)。例如如下代碼中,我們向列表中既添加了
Integer
類型,又添加了String
類型
package com.caq.oop.demo08; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { //實(shí)例化一個(gè)空列表 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 個(gè)元素實(shí)際上是整型,但被我們強(qiáng)制轉(zhuǎn)換為字符串類型,這是行不通的,因此會(huì)拋出ClassCastException
異常。
使用泛型可以解決這些問(wèn)題。泛型有如下優(yōu)點(diǎn):
- 可以減少類型轉(zhuǎn)換的次數(shù),代碼更加簡(jiǎn)潔;
- 程序更加健壯:只要編譯期沒(méi)有警告,運(yùn)行期就不會(huì)拋出
ClassCastException
異常; - 提高了代碼的可讀性:編寫(xiě)集合的時(shí)候,就限定了集合中能存放的類型。
3. 如何使用泛型
3.1 泛型使用
在代碼中,這樣使用泛型:
List<String> list = new ArrayList<String>(); // Java 7 及以后的版本中,構(gòu)造方法中可以省略泛型類型: List<String> list = new ArrayList<>(); 外幣巴伯
要注意的是,變量聲明的類型必須與傳遞給實(shí)際對(duì)象的類型保持一致,下面是錯(cuò)誤的例子:
List<Object> list = new ArrayList<String>(); List<Number> numbers = new ArrayList(Integer);
3.2 自定義泛型類
3.2.1 Java 源碼中泛型的定義
在自定義泛型類之前,我們來(lái)看下java.util.ArrayList是如何定義的:
類名后面的<E>
就是泛型的定義,E
不是 Java 中的一個(gè)具體的類型,它是 Java 泛型的通配符(注意是大寫(xiě)的,實(shí)際上就是Element
的含義),可將其理解為一個(gè)占位符,將其定義在類上,使用時(shí)才確定類型。
此處的命名不受限制,但最好有一定含義,例如java.lang.HashMap
的泛型定義為HashMap<K,V>,K
表示Key
,V
表示Value
。
3.2.2 自定義泛型類實(shí)例1
下面我們來(lái)自定義一個(gè)泛型類,自定義泛型按照約定俗成可以叫<T>,具有Type的含義,實(shí)例如下:
實(shí)例演示
package com.caq.List; public class Generic01<T> { private T abc;//定義在類上的泛型,在類內(nèi)部可以使用 public T getAbc() { return abc; } public void setAbc(T abc) { this.abc = abc; } public static void main(String[] args) { //實(shí)例化對(duì)象,指定元素類型為整型 Generic01<Integer> integerGeneric01 = new Generic01<>(); //調(diào)用方法 integerGeneric01.setAbc(100); System.out.println("integerGeneric01="+ integerGeneric01.getAbc()); //實(shí)例化對(duì)象,指定元素類型為長(zhǎng)類型 Generic01<Long> longGeneric01 = new Generic01<>(); longGeneric01.setAbc(200L); System.out.println("longGeneric01="+ longGeneric01.getAbc()); // 實(shí)例化對(duì)象,指定元素類型為雙精度浮點(diǎn)型 Generic01<Double> doubleGeneric01 = new Generic01<>(); doubleGeneric01.setAbc(300.0); System.out.println("doubleGeneric01="+ doubleGeneric01.getAbc()); } }
運(yùn)行結(jié)果:
integerGeneric01=100 longGeneric01=200 doubleGeneric01=300.0
我們?cè)陬惖亩x處也定義了泛型:Generic01<T>;
在類內(nèi)部定義了一個(gè)T類型的abc變量,并且為其添加了setter
和getter
方法。
解釋:對(duì)于泛型類的使用也很簡(jiǎn)單,在主方法中,創(chuàng)建對(duì)象的時(shí)候指定T的類型分別為Integer
、Long
、Double
,類就可以自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的類型了。
3.2.3 自定義泛型類實(shí)例2
上面我們知道了如何定義含有單個(gè)泛型的類,那么對(duì)于含有多個(gè)泛型的類,如何定義呢?
我們可以看一下HashMap
類是如何定義的。如下是 Java 源碼的截圖:
參照HashMap<K,V>
類的定義,下面我們來(lái)看看如何定義含有兩個(gè)泛型的類
package com.caq.List; public class Generic02<K, V> {//這次是定義兩個(gè)泛型在類上 //定義類型為K的key屬型 private K key; //定義類型為V的value屬型 private V value; //封裝里的知識(shí),通過(guò)Getter和Setter方法來(lái)設(shè)置和獲取私有屬型的值 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) { //實(shí)例化對(duì)象,分別指定類型為整型,長(zhǎng)整型 Generic02<Integer, Long> integerLongGeneric02 = new Generic02<>(); //實(shí)例化對(duì)象,分別指定類型為浮點(diǎn)型、字符串類型 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()); } }
運(yùn)行結(jié)果:
key=100value=200key=0.9value=巴啦啦能量
3.3 自定義泛型方法
前面我們知道了如何定義泛型類,在類上定義的泛型,在方法中也可以使用。下面我們來(lái)看一下如何自定義泛型方法。
泛型方法不一定寫(xiě)在泛型類當(dāng)中。當(dāng)類的調(diào)用者總是關(guān)心類中的某個(gè)泛型方法,不關(guān)心其他屬性,這個(gè)時(shí)候就沒(méi)必要再整個(gè)類上定義泛型了。
直接在方法上設(shè)置泛型(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); }}
運(yùn)行結(jié)果:
Monkey11.01
實(shí)例中,使用<T>
來(lái)定義test
方法的泛型,它接收一個(gè)泛型的參數(shù)變量并在方法體打??;調(diào)用泛型方法也很簡(jiǎn)單,在主方法中實(shí)例化對(duì)象,調(diào)用對(duì)象下的泛型方法,可傳入不同類型的參數(shù)。
4. 泛型類的子類
泛型類也是一個(gè) Java 類,它也具有繼承的特性。
泛型類的繼承可分為兩種情況:
- 子類明確泛型類的類型參數(shù)變量;
- 子類不明確泛型類的類型參數(shù)變量。
4.1 明確類型參數(shù)變量
例如,有一個(gè)泛型接口:
package com.caq.List;public interface GenericInterface01<T> { default void show(T t) { }}
泛型接口的實(shí)現(xiàn)類如下:
package com.caq.List;public class GenericInterfaceImple implements GenericInterface01<String> { @Override public void show(String s) { System.out.println(s); }}
子類實(shí)現(xiàn)明確了泛型的參數(shù)變量為String類型。因此方法show()的重寫(xiě)也將T替換為了String類型。
4.2 不明確類型參數(shù)變量
當(dāng)實(shí)現(xiàn)類不確定泛型類的參數(shù)變量時(shí),實(shí)現(xiàn)類需要定義類型參數(shù)變量,調(diào)用者使用子類時(shí),也需要傳遞類型參數(shù)變量。
如下是GenericInterface
接口的另一個(gè)實(shí)現(xiàn)類:
package com.caq.List; package com.caq.List; public class GenericInterfaceImple<T> implements GenericInterface01<T> { @Override public void show(T t) { System.out.println(t); } }
在主方法中調(diào)用實(shí)現(xiàn)類的show()
方法:
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. 類型通配符
我們先來(lái)看一個(gè)泛型作為方法參數(shù)的實(shí)例:
/** * 遍歷并打印集合中的每一個(gè)元素 * 遍歷是二叉樹(shù)上最重要的運(yùn)算之一,是二叉樹(shù)上進(jìn)行其它運(yùn)算之基礎(chǔ)。 樹(shù)的遍歷是樹(shù)的一種重要的運(yùn)算。 * 所謂遍歷是指對(duì)樹(shù)中所有結(jié)點(diǎn)的信息的訪問(wèn),即依次對(duì)樹(shù)中每個(gè)結(jié)點(diǎn)訪問(wèn)一次且僅訪問(wèn)一次。 * @param list 要接收的集合 */ public class Generic04 { public void printListElement(List<object> list){ for (Object o : list) { System.out.println(o); } } }
觀察上面的代碼,參數(shù)list的限定的泛型類型為Object, 也就是說(shuō),這個(gè)方法只能接收元素為Object類型的集合,如果我們想傳遞其他元素類型的集合,是行不通的。例如,如果傳遞裝載Integer元素的集合,程序在編譯階段就會(huì)報(bào)錯(cuò):
Tips: 泛型中的List<Object
>并不是List<Integer>
的父類,它們不滿足繼承關(guān)系。
5.1 無(wú)限定通配符
想要解決這個(gè)問(wèn)題,使用類型通配符即可,修改方法參數(shù)處的代碼,將<>中間的Object改為?即可:
public void printListElement(List<?> list){
此處的?就是類型通配符,表示可以匹配任意類型,因此調(diào)用方可以傳遞任意泛型類型的列表。
實(shí)例演示
package com.caq.List; import java.util.ArrayList; import java.util.List; public class Generic04 { //List<?>可以理解為列表的類型,可以是整數(shù)型列表,也可以是字符串類型列表,list代表的是遍歷的元素代表 public void printListElement(List<?> list){ for (Object o : list) { System.out.println(o); } } public static void main(String[] args) { //實(shí)例化一個(gè)整型列表 List<Integer> interger = new ArrayList<>(); //加元素 interger.add(1); interger.add(2); interger.add(2222); //實(shí)例化對(duì)象 Generic04 generic04 = new Generic04(); generic04.printListElement(interger); //實(shí)例化一個(gè)字符串類型列表 ArrayList<String> strings = new ArrayList<>(); strings.add("element1"); strings.add("element2"); strings.add("element3"); generic04.printListElement(strings); } }
運(yùn)行結(jié)果:
2222 element1 element2 element3
5.2 extends 通配符
extends
通配符用來(lái)限定泛型的上限。什么意思呢?依舊以上面的實(shí)例為例,我們來(lái)看一個(gè)新的需求,我們希望方法接收的List
集合限定在數(shù)值類型內(nèi)(float、integer、double、byte 等),不希望其他類型可以傳入(比如字符串)。此時(shí),可以改寫(xiě)上面的方法定義,設(shè)定上界通配符:
public void printListElement(List<? extends Number> list) {
這樣的寫(xiě)法的含義為:List
集合裝載的元素只能是Number
自身或其子類(Number
類型是所有數(shù)值類型的父類),完整實(shí)例如下:
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) { // 實(shí)例化一個(gè)整型的列表 List<Integer> integers = new ArrayList<>(); // 添加元素 integers.add(1); integers.add(2); integers.add(3); GenericDemo4 generic04 = new Generic04(); // 調(diào)用printListElement()方法 generic04.printListElement(integers); } }
運(yùn)行結(jié)果:
1 2 3
5.3 super 通配符
既然已經(jīng)了解了如何設(shè)定通配符上界,也就不難理解通配符的下界了,可以限定傳遞的參數(shù)只能是某個(gè)類型的父類。
語(yǔ)法如下:
<? super Type>
6. 小結(jié)
- 使用泛型可以避免強(qiáng)制類型轉(zhuǎn)換,也可以避免運(yùn)行期就拋出的
ClassCastException
異常 - 在使用泛型時(shí),要注意變量聲明的泛型類型要匹配傳遞給實(shí)際對(duì)象的類型, Java 7 及以后的版本中,構(gòu)造方法中可以省略泛型類型,推薦直接省略
- 如何自定義泛型類和泛型方法,在實(shí)際的開(kāi)發(fā)中,我們想要編寫(xiě)比較通用的代碼就避免不了使用泛型,慢慢體會(huì)星弟們~~~
- 另外,泛型也是可以繼承的
- 類型通配符的概念和使用場(chǎng)景
到此這篇關(guān)于多方面介紹Java的泛型的文章就介紹到這了,更多相關(guān)Java 泛型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
記一次線程爆滿導(dǎo)致服務(wù)器崩潰的問(wèn)題排查及解決
這篇文章主要介紹了記一次線程爆滿導(dǎo)致服務(wù)器崩潰的問(wèn)題排查及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10SpringBoot如何使用MyBatisPlus逆向工程自動(dòng)生成代碼
本文介紹如何使用SpringBoot、MyBatis-Plus進(jìn)行逆向工程自動(dòng)生成代碼,并結(jié)合Swagger3.0實(shí)現(xiàn)API文檔的自動(dòng)生成和訪問(wèn),通過(guò)詳細(xì)步驟和配置,確保Swagger與SpringBoot版本兼容,并通過(guò)配置文件和測(cè)試類實(shí)現(xiàn)代碼生成和Swagger文檔的訪問(wèn)2024-12-12SpringBoot2.x 參數(shù)校驗(yàn)問(wèn)題小結(jié)
這篇文章主要介紹了SpringBoot2.x 參數(shù)校驗(yàn)一些問(wèn)題總結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08詳解Java編程中包package的內(nèi)容與包對(duì)象的規(guī)范
這篇文章主要介紹了Java編程中包package的內(nèi)容與包對(duì)象的規(guī)范,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-12-12Spring Boot實(shí)現(xiàn)異步請(qǐng)求(Servlet 3.0)
在spring 3.2 及以后版本中增加了對(duì)請(qǐng)求的異步處理,這篇文章主要介紹了Spring Boot實(shí)現(xiàn)異步請(qǐng)求(Servlet 3.0),感興趣的小伙伴們可以參考一下。2017-04-04詳解Java使用super和this來(lái)重載構(gòu)造方法
這篇文章主要介紹了詳解Java使用super和this來(lái)重載構(gòu)造方法的相關(guān)資料,這里提供實(shí)例來(lái)幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08