Java?中泛型?T?和???的區(qū)別詳解
泛型中 T 類型變量 和 ? 通配符 區(qū)別
定義不同 :T 是類型變量,? 是通配符
使用范圍不同:
- ? 通配符用作 參數(shù)類型、字段類型、局部變量類型,有時(shí)作為返回類型(但請(qǐng)避免這樣做)
- T 用作 聲明類的類型參數(shù)、通用方法的類型參數(shù) (這里注意 類型參數(shù) 和 參數(shù)類型 是兩個(gè)概念)
通常我們使用 ? 的時(shí)候并并不知道也不關(guān)心這個(gè)時(shí)候的類型,這里只想使用其通用的方法,而且 ? 通配符是無法作用于聲明類的類型參數(shù),一般作用于方法和參數(shù)上。而 類型變量 T 在類定義時(shí)具有更廣泛的應(yīng)用。
在某些程度的使用上 ? 通配符與 T 參數(shù)類型是可以等效的,但是 T 參數(shù)類型并不支持下界限制 即 T super SomeTing 而 通配符支持 ? super SomeThing
如果你想寫一個(gè)通用的方法且該方法的邏輯不關(guān)心類型那么就大膽的用 ? 通配符來進(jìn)行適配和限制吧,如果你需要作用域類型(這可能在操作通用數(shù)組類型時(shí)更明顯)或者聲明類的類型參數(shù)時(shí)請(qǐng)使用 T 類型變量
類型參數(shù)定義了一種代表作用域類型的變量(例如,T),通配符只是定義了一組可用于泛型類型的允許類型。通配符的意思是“在這里使用任何類型”
在泛型的使用中我們經(jīng)??梢钥吹竭@樣的用法:
public class Box<T> { ? ? // T stands for "Type" ? ? private T t; ? ? public void set(T t) { this.t = t; } ? ? public T get() { return t; } }
List<? extends Integer> intList = new ArrayList<>(); List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number> public interface GenericProgressiveFutureListener<F extends ProgressiveFuture<?>> extends GenericFutureListener<F> { void operationProgressed(F future, long progress, long total) throws Exception; }
如果你其用法和概念仍有疑問,那不妨繼續(xù)閱讀本文
了解他們的概念:Generic Types 和 Wildcards ,以及使用。
Generic Types 類型變量
通用類型即 T、F、K、V 這樣的寫法,它是一種是通過類型參數(shù)化的通用類或接口,也可以稱之為 類型變量
類型變量可以是任何非原始類型:任何類類型、任何接口類型、任何數(shù)組類型,甚至是另一個(gè)類型變量
按照慣例,類型參數(shù)名稱是單個(gè)大寫字母。
最常用的類型參數(shù)名稱是:
- E - 元素(被 Java 集合框架廣泛使用)
- K - 鍵
- N - 數(shù)字
- T - 類型
- V - 值
- S、U、V 等 - 第 2、3、4 種類型
用法
1.聲明通用的類型 – 泛型類:
當(dāng)我們想對(duì)通用的對(duì)象類型進(jìn)行操作時(shí)我們可能想到使用 Object ,但是使用 Object 在編譯時(shí)無法進(jìn)行檢查,因?yàn)?Object 是所有類的父類,這可能導(dǎo)致我們意圖在傳入 Integer 并可以取出 Inerger 時(shí),在另一部分代碼錯(cuò)誤的傳入了 String
public class Box { ? ? private Object object; ? ? public void set(Object object) { this.object = object; } ? ? public Object get() { return object; } }
為了避免上述的問題,我們可以選擇使用 類型變量
public class Box<T> { ? ? // T stands for "Type" ? ? private T t; ? ? public void set(T t) { this.t = t; } ? ? public T get() { return t; } }
你也可以使用多個(gè)類型參數(shù)
public interface Pair<K, V> { ? ? public K getKey(); ? ? public V getValue(); } public class OrderedPair<K, V> implements Pair<K, V> { ? ? private K key; ? ? private V value; ? ? public OrderedPair(K key, V value) { ?? ?this.key = key; ?? ?this.value = value; ? ? } ? ? public K getKey()?? ?{ return key; } ? ? public V getValue() { return value; } }
2.聲明通用的方法 – 泛型方法:
泛型方法 是引入自己的類型參數(shù)的方法。這類似于聲明泛型類型,但類型參數(shù)的范圍僅限于聲明它的方法。允許靜態(tài)和非靜態(tài)泛型方法,以及泛型類構(gòu)造函數(shù)。
泛型方法的語法包括一個(gè)類型參數(shù)列表,在尖括號(hào)內(nèi),它出現(xiàn)在方法的返回類型之前。對(duì)于靜態(tài)泛型方法,類型參數(shù)部分必須出現(xiàn)在方法的返回類型之前。
public class Util { ? ? public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { ? ? ? ? return p1.getKey().equals(p2.getKey()) && ? ? ? ? ? ? ? ?p1.getValue().equals(p2.getValue()); ? ? } } public static <T> void printListT(List<T> list) { ? ? for (Object elem : list) ? ? ? ? System.out.println(elem + " "); ? ? System.out.println(); }
一個(gè)完整的調(diào)用是
JestTestMain.<String>printListT(names);
但是通常可以省略類型,這里使用到的功能是 類型推斷
JestTestMain.printListT(names);
有界類型參數(shù)
同時(shí)我們可以對(duì)類型參數(shù)進(jìn)行限制通過 extends 關(guān)鍵字
如 <T extends Number> 這里的泛型參數(shù)就限制了必須繼承于 Number 類。
public static <T extends Number> void printListT(List<T> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }
同時(shí) Java 也支持多重限定,如 <T extends CharSequence & Comparable<T> & Serializable> 但是如果其中限定包含 類 需要寫在最前面
public static <T extends CharSequence & Comparable<T> & Serializable> void printListT(List<T> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }
Wildcards 通配符
通配符即指 ?
在泛型代碼中,? 表示未知類型。通配符可用于多種情況:
作為參數(shù)、字段或局部變量的類型,有時(shí)作為返回類型(但請(qǐng)避免這樣做)。
通配符從不用作泛型方法調(diào)用、泛型類實(shí)例創(chuàng)建或超類型的類型參數(shù)。
用法
通配符分為 3 種:
1.上界通配符:? extend 上界類型
如List public static void process(List list) { /* ... */ }
我們可以使用上界通配符來放寬對(duì)變量的限制
2.無界通配符:?
如 List<?> 這表示未知類型的列表,一般有兩種情況下無界通配符是有用的:
- 你正在編寫可以使用 Object類中提供的功能實(shí)現(xiàn)的方法
- 當(dāng)代碼使用不依賴于類型參數(shù)的泛型類中的方法時(shí)。例如,List.size或 List.clear。事實(shí)上,Class<?> 之所以如此常用,是因?yàn)?Class<T>中的大多數(shù)方法都不依賴于 T。
如何理解這句話的意思呢?來看一個(gè)例子:
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }
printList 的意圖是想打印任何類型的列表,但是它沒有達(dá)到目標(biāo),其只打印了 Object 實(shí)例的列表。它不能打印 List<Integer>、List<String>、List<Double>等,因?yàn)樗鼈儾皇?List<Object> 的子類型。
編譯時(shí)將會(huì)報(bào)錯(cuò)。
這里我們換成通配符將正確運(yùn)行
public class JestTestMain { ? ? public static void main(String[] args) { ? ? ? ? List<String> names= Lists.newArrayList(); ? ? ? ? names.add("張三"); ? ? ? ? names.add("張三1"); ? ? ? ? names.add("張三2"); ? ? ? ? printList(names); ? ? } ? ? public static void printList(List<?> list) { ? ? ? ? for (Object elem : list) ? ? ? ? ? ? System.out.println(elem + " "); ? ? ? ? System.out.println(); ? ? } }
打?。?/p>
張三
張三1
張三2
這里需要明白的一點(diǎn)是,List<Object> 和 List<?> 并不相同,你可以向 List<Object> 中插入 Object 對(duì)象,或者任何其子類對(duì)象,但是你只能向 List<?> 中插入 null 值。
3.下界通配符:? super 子類
如:<? super Integer>
假設(shè)你要編寫一個(gè)將 Integer 對(duì)象放入列表的方法。為了最大限度地提高靈活性,希望該方法適用于 List<Integer>、List<Number>和 List<Object> 任何可以保存 Integer 值的東西。
public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }
類型擦除
我們要知道的一件事兒,編譯器在編譯時(shí)清除了所有類型參數(shù),也就是說會(huì)將我們的類型參數(shù)進(jìn)行實(shí)際的替換。
就算如此我們?nèi)杂惺褂梅盒偷睦碛?/p>
- Java 編譯器在編譯時(shí)對(duì)泛型代碼執(zhí)行更嚴(yán)格的類型檢查。
- 泛型支持編程類型作為參數(shù)。
- 泛型能夠?qū)崿F(xiàn)泛型算法。
Java 語言中引入了泛型以在編譯時(shí)提供更嚴(yán)格的類型檢查并支持泛型編程。為了實(shí)現(xiàn)泛型,Java 編譯器將類型擦除應(yīng)用于:
- 如果類型參數(shù)是無界的,則將泛型類型中的所有類型參數(shù)替換為其邊界或 Object。因此,生成的字節(jié)碼僅包含普通的類、接口和方法。
- 必要時(shí)插入類型轉(zhuǎn)換以保持類型安全。
- 生成橋接方法以保留擴(kuò)展泛型類型中的多態(tài)性。
類型擦除確保不會(huì)為參數(shù)化類型創(chuàng)建新類;因此,泛型不會(huì)產(chǎn)生運(yùn)行時(shí)開銷。
下面舉兩個(gè)例子
// 類型擦除前 public class Pair<K, V> { ? ? public Pair(K key, V value) { ? ? ? ? this.key = key; ? ? ? ? this.value = value; ? ? } ? ? public K getKey(); { return key; } ? ? public V getValue(); { return value; } ? ? public void setKey(K key) ? ? { this.key = key; } ? ? public void setValue(V value) { this.value = value; } ? ? private K key; ? ? private V value; } // 類型擦除后 public class Pair { ? ? public Pair(Object key, Object value) { ? ? ? ? this.key = key; ? ? ? ? this.value = value; ? ? } ? ? public Object getKey() ? { return key; } ? ? public Object getValue() { return value; } ? ? public void setKey(Object key) ? ? { this.key = key; } ? ? public void setValue(Object value) { this.value = value; } ? ? private Object key; ? ? private Object value; } // 類型擦除前 public static <T extends Comparable<T>> int findFirstGreaterThan(T[] at, T elem) { ? ? // ... } // 類型擦除后 public static int findFirstGreaterThan(Comparable[] at, Comparable elem) { ? ? // ... }
最后感興趣的同學(xué)可以參考:
Java Tutorial on Generics
Generics in the Java programming language
到此這篇關(guān)于Java 中泛型 T 和 ? 的區(qū)別詳解的文章就介紹到這了,更多相關(guān)Java 泛型T和? 區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于maven搭建一個(gè)ssm的web項(xiàng)目的詳細(xì)圖文教程
這篇文章主要介紹了基于maven搭建一個(gè)ssm的web項(xiàng)目的詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09前后端項(xiàng)目分離解決cors錯(cuò)誤的方法詳解
隨著前后端分離技術(shù)的越來越盛行,跨域問題也逐漸凸顯了出來,下面這篇文章主要給大家介紹了關(guān)于前后端項(xiàng)目分離解決cors錯(cuò)誤的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02淺談Java中對(duì)類的主動(dòng)引用和被動(dòng)引用
這篇文章主要介紹了淺談Java中對(duì)類的主動(dòng)引用和被動(dòng)引用,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02SpringMVC記錄我遇到的坑_AOP注解無效,切面不執(zhí)行的解決
這篇文章主要介紹了SpringMVC記錄我遇到的坑_AOP注解無效,切面不執(zhí)行的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java List的remove()方法陷阱以及性能優(yōu)化
Java List在進(jìn)行remove()方法是通常容易踩坑,本文就詳細(xì)的介紹一下陷阱以及性能優(yōu)化,感興趣的可以了解一下2021-10-10spring @Lazy延遲注入的邏輯實(shí)現(xiàn)
有時(shí)候我們會(huì)在屬性注入的時(shí)候添加@Lazy注解實(shí)現(xiàn)延遲注入,今天咱們通過閱讀源碼來分析下原因,感興趣的可以了解一下2021-08-08mybatis同一張表多次連接查詢相同列賦值問題小結(jié)
這篇文章主要介紹了mybatis同一張表多次連接查詢相同列賦值問題,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下2017-01-01