一文帶你搞懂Java中的泛型和通配符
概述
泛型機(jī)制在項目中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不僅如此,很多源碼中都用到了泛型機(jī)制,所以深入學(xué)習(xí)了解泛型相關(guān)機(jī)制對于源碼閱讀以及自己代碼編寫有很大的幫助。但是里面很多的機(jī)制和特性一直沒有明白,特別是通配符這塊,對于通配符上界、下界每次用每次百度,經(jīng)常忘記,這次我就做一個總結(jié),加深自己的理解。
泛型介紹和使用
泛型在類定義時不會設(shè)置類中的屬性或方法參數(shù)的具體類型,而是在類使用時(創(chuàng)建對象)再進(jìn)行類型的定義。會在編譯期檢查類型是否錯誤, 保證程序的可讀性和安全性。
泛型定義根據(jù)實(shí)際情況可以分為泛型類和泛型方法:
泛型類
public class Point<T, U> { private T pointX; private U pintY; public Point(T pointX, U pintY) { this.pointX = pointX; this.pintY = pintY; } public void showPoint() { System.out.println(pointX); System.out.println(pintY); } }
- 類中引入類型變量,類型變量指的T, U這些,用尖括號<>括起來, 跟在類名后面。
- 多個類型變量可以用逗號分隔。
- 在類中的方法和返回值等地方可以使用類型變量。
- 類型變量采用大寫形式,要求簡短,一般E表示集合的元素類型,K和V表示key和value等。
- 泛型類使用:Point<Integer, Double>
泛型方法
public class FxMethod { public static <T> T getMiddleNumber(T ... numbers) { return null; } public <T, U> void showNumber(T t, U u) { System.out.println("t = " + t); System.out.println("u = " + u);; } }
- 方法中引入類型變量,在返回類型前添加<>, 中間放置類型變量,多個類型變量用逗號分隔。
- 在方法的參數(shù)和返回值等位置可以使用類型變量。
- 泛型方法使用:
Integer result = FxMethod.getMiddleNumber(2, 3)
或者Integer result = FxMethod.<Integer>getMiddleNumber(2, 3)
。
類型變量的限定
前面講解了泛型一般定義的兩種方式,其中的類型變量沒有任何限定, 這樣在導(dǎo)致一方面在定義泛型的時候無法使用一些API, 需要強(qiáng)轉(zhuǎn),另一方面在使用的時候也容易出錯,那么如何給類型變量添加限定呢?
- 只有通過extends關(guān)鍵字限定,不能通過super關(guān)鍵字。
- 加了限定以后,就可以直接使用限定類相關(guān)的API。
- 多個限定之間用&符號,比如
T extends Number & Comparable
。 - 使用泛型時,只能傳入相應(yīng)限定的類,比如傳入Point<String, String> 就會報編譯錯誤。
通配符使用
泛型的引入的確解決了很大問題,那它是完美的嗎?
class AnimalWrapper<T extends Animal> { private T animal; AnimalWrapper(T animal) { this.animal = animal; } public void eat() { animal.eat(); } } class Animal { private String name; public void eat() { System.out.println("animal eat -----"); } } class Cat extends Animal { @Override public void eat() { System.out.println(" cat eat -----"); } }
定義一個AnimalWrapper,泛型變量中限定為Animal,如果是下面的測試類,會怎么樣呢?
會編譯報錯,因?yàn)锳nimalWrapper并不是AnimalWrapper的子類,不能直接傳入。為了解決個問題,我們引入了通配符,通配符一般是在方法中或者泛型類使用中用到。
AnimalWrapper可以作為AnimalWrapper<?extends Animal>的子類型,這就是利用通配符帶來的好處。
- 統(tǒng)配符使用在集合或者方法的參數(shù)返回值中。
- 通配符可以分為無邊界通配符、上邊界通配符和下邊界通配符。
無邊界通配符
通配符無邊界,可以傳入任何類型,沒有限制,相當(dāng)于Object.
基本語法:
<?>
例子:
public static void printList1(List<?> list) { for (Object x:list) { System.out.println(x); } } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); printList1(list); // ok List<String> list2 = new ArrayList<>(); list2.add("1"); printList1(list2); // ok List<?> list3 = list; // get只能用Object接受, Object o = list3.get(0); list3.add(5); // compile error list3.add(new Object()); // compile error }
小結(jié):
- 無邊界通配符相當(dāng)于Object,任何類型都可以傳入,比如
List<Integer> list, List<String> list2
。 - 由于?無法確定是哪種類型,所以只能使用Object類型的變量接收, 比如例子中的:
Object o = list3.get(0);
- 如果是無邊界通配符對應(yīng)的集合類型,不能添加任何元素。因?yàn)闊o法確定集合存放數(shù)據(jù)的類型,鬼知道我們要放什么類型才合適啊。
通配符上界
通配符上界,可以限制傳入的類型必須是上界這個類或者是這個類的子類。
基本語法:
<? extends 上界> <? extends Number>//可以傳入的實(shí)參類型是Number或者Number的子類
例子:
public static void printList1(List<? extends Number> list) { for (Object x:list) { System.out.println(x); } } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); printList1(list); // ok List<Double> list1 = new ArrayList<>(); list1.add(1.0D); printList1(list1); // ok List<String> list2 = new ArrayList<>(); list2.add("1"); printList1(list2); // compile error List<? extends Number> list3 = list; // get能用上界 Number o = list3.get(0); // 不能add list3.add(5); // compile error list3.add(new Object()); // compile error }
小結(jié):
- 通配符上界? extends A, 表明所有的是A的類或者子類型可以傳入,比如本例中的Integer和Double都是Number的子類,String不是。
- 通配符上界? extends A,確定了類型是A或者是A的子類,那么向集合容器get獲取數(shù)據(jù),肯定是它的上界類A,因?yàn)槠渌诺念惗际茿的子類,比如例子中的
Number o = list3.get(0);
- 如果向通配符上界集合中添加元素時,會失敗。 List<? extends A>, 說明容器可以容納的是A或者A的子類,但A的子類有很多,不確定放哪個,為了安全性,就直接不讓你add,比如例子中的
list3.add(5);
,5雖然是Number的子類,依然不能add。
通配符下界
通配符下界,可以限制傳入的類型必須是這個類或者是這個類的父類。
基本語法:
<? super 下界> <? super Integer>//代表 可以傳入的實(shí)參的類型是Integer或者Integer的父類類型
例子:
public static void printList1(List<? super Integer> list) { for (Object x:list) { System.out.println(x); } } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); printList1(list); // ok List<Double> list1 = new ArrayList<>(); list1.add(1.0D); printList1(list1); // compile error List<String> list2 = new ArrayList<>(); list2.add("1"); printList1(list2); // compile error List<? super Integer> list3 = list; // 不能用下界接收 Integer o = list3.get(0); // compile error // 能add list3.add(5); // ok list3.add(new Number(5)); // compile error }
- 通配符上界? super A, 表明所有的是A的類或者A的父類可以傳入。
- 通配符上界? super A,確定了類型是A或者是A的父類,那么向集合容器get獲取數(shù)據(jù),無法確定是A還是A的某個父類,所以不能get,
Integer o = list3.get(0); // compile error
,比如例子中用Integer接收,萬一list3中放的是Object類型,就涼涼了。 - 如果向通配符下界集合中添加元素時,只能添加下屆類的子類。比如例子中的:
list3.add(5)
, list3的通配符是<? super Integer>
,說明該集合存放的是Integer或者Integer的父類,我只要向容器中放Integer和它的子類都是成立的。
到此這篇關(guān)于一文帶你搞懂Java中的泛型和通配符的文章就介紹到這了,更多相關(guān)Java泛型 通配符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Maven多模塊及version修改的實(shí)現(xiàn)方法
這篇文章主要介紹了Maven多模塊及version修改的實(shí)現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-06-06詳解Java的MyBatis框架與Spring框架整合中的映射器注入
映射器注入方式可以將MyBatis與Spring映射好的XML文件實(shí)現(xiàn)配置共用,這里我們就來詳解Java的MyBatis框架與Spring框架整合中的映射器注入:2016-06-06Java9 Stream Collectors新增功能(小結(jié))
這篇文章主要介紹了Java9 Stream Collectors新增功能(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Java 實(shí)戰(zhàn)項目錘煉之網(wǎng)上商城系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java+jsp+servlet+mysql+ajax實(shí)現(xiàn)一個網(wǎng)上商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11SpringBoot整合SpringSecurity認(rèn)證與授權(quán)
在項目開發(fā)中,權(quán)限認(rèn)證是很重要的,尤其是一些管理類的系統(tǒng),對于權(quán)限要求更為嚴(yán)格,本文主要介紹了SpringBoot整合SpringSecurity認(rèn)證與授權(quán),感興趣的可以了解一下2023-11-11