Java?中泛型?T?和???的區(qū)別詳解
泛型中 T 類型變量 和 ? 通配符 區(qū)別
定義不同 :T 是類型變量,? 是通配符
使用范圍不同:
- ? 通配符用作 參數(shù)類型、字段類型、局部變量類型,有時作為返回類型(但請避免這樣做)
- T 用作 聲明類的類型參數(shù)、通用方法的類型參數(shù) (這里注意 類型參數(shù) 和 參數(shù)類型 是兩個概念)
通常我們使用 ? 的時候并并不知道也不關(guān)心這個時候的類型,這里只想使用其通用的方法,而且 ? 通配符是無法作用于聲明類的類型參數(shù),一般作用于方法和參數(shù)上。而 類型變量 T 在類定義時具有更廣泛的應(yīng)用。
在某些程度的使用上 ? 通配符與 T 參數(shù)類型是可以等效的,但是 T 參數(shù)類型并不支持下界限制 即 T super SomeTing 而 通配符支持 ? super SomeThing
如果你想寫一個通用的方法且該方法的邏輯不關(guān)心類型那么就大膽的用 ? 通配符來進(jìn)行適配和限制吧,如果你需要作用域類型(這可能在操作通用數(shù)組類型時更明顯)或者聲明類的類型參數(shù)時請使用 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ù)組類型,甚至是另一個類型變量
按照慣例,類型參數(shù)名稱是單個大寫字母。
最常用的類型參數(shù)名稱是:
- E - 元素(被 Java 集合框架廣泛使用)
- K - 鍵
- N - 數(shù)字
- T - 類型
- V - 值
- S、U、V 等 - 第 2、3、4 種類型
用法
1.聲明通用的類型 – 泛型類:
當(dāng)我們想對通用的對象類型進(jìn)行操作時我們可能想到使用 Object ,但是使用 Object 在編譯時無法進(jìn)行檢查,因為 Object 是所有類的父類,這可能導(dǎo)致我們意圖在傳入 Integer 并可以取出 Inerger 時,在另一部分代碼錯誤的傳入了 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; } }
你也可以使用多個類型參數(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ù)。
泛型方法的語法包括一個類型參數(shù)列表,在尖括號內(nèi),它出現(xiàn)在方法的返回類型之前。對于靜態(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(); }
一個完整的調(diào)用是
JestTestMain.<String>printListT(names);
但是通??梢允÷灶愋?,這里使用到的功能是 類型推斷
JestTestMain.printListT(names);
有界類型參數(shù)
同時我們可以對類型參數(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(); }
同時 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ù)、字段或局部變量的類型,有時作為返回類型(但請避免這樣做)。
通配符從不用作泛型方法調(diào)用、泛型類實例創(chuàng)建或超類型的類型參數(shù)。
用法
通配符分為 3 種:
1.上界通配符:? extend 上界類型
如List public static void process(List list) { /* ... */ }
我們可以使用上界通配符來放寬對變量的限制
2.無界通配符:?
如 List<?> 這表示未知類型的列表,一般有兩種情況下無界通配符是有用的:
- 你正在編寫可以使用 Object類中提供的功能實現(xiàn)的方法
- 當(dāng)代碼使用不依賴于類型參數(shù)的泛型類中的方法時。例如,List.size或 List.clear。事實上,Class<?> 之所以如此常用,是因為 Class<T>中的大多數(shù)方法都不依賴于 T。
如何理解這句話的意思呢?來看一個例子:
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }
printList 的意圖是想打印任何類型的列表,但是它沒有達(dá)到目標(biāo),其只打印了 Object 實例的列表。它不能打印 List<Integer>、List<String>、List<Double>等,因為它們不是 List<Object> 的子類型。
編譯時將會報錯。
這里我們換成通配符將正確運(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
這里需要明白的一點是,List<Object> 和 List<?> 并不相同,你可以向 List<Object> 中插入 Object 對象,或者任何其子類對象,但是你只能向 List<?> 中插入 null 值。
3.下界通配符:? super 子類
如:<? super Integer>
假設(shè)你要編寫一個將 Integer 對象放入列表的方法。為了最大限度地提高靈活性,希望該方法適用于 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ù)進(jìn)行實際的替換。
就算如此我們?nèi)杂惺褂梅盒偷睦碛?/p>
- Java 編譯器在編譯時對泛型代碼執(zhí)行更嚴(yán)格的類型檢查。
- 泛型支持編程類型作為參數(shù)。
- 泛型能夠?qū)崿F(xiàn)泛型算法。
Java 語言中引入了泛型以在編譯時提供更嚴(yán)格的類型檢查并支持泛型編程。為了實現(xiàn)泛型,Java 編譯器將類型擦除應(yīng)用于:
- 如果類型參數(shù)是無界的,則將泛型類型中的所有類型參數(shù)替換為其邊界或 Object。因此,生成的字節(jié)碼僅包含普通的類、接口和方法。
- 必要時插入類型轉(zhuǎn)換以保持類型安全。
- 生成橋接方法以保留擴(kuò)展泛型類型中的多態(tài)性。
類型擦除確保不會為參數(shù)化類型創(chuàng)建新類;因此,泛型不會產(chǎn)生運(yùn)行時開銷。
下面舉兩個例子
// 類型擦除前 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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于maven搭建一個ssm的web項目的詳細(xì)圖文教程
這篇文章主要介紹了基于maven搭建一個ssm的web項目的詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09SpringMVC記錄我遇到的坑_AOP注解無效,切面不執(zhí)行的解決
這篇文章主要介紹了SpringMVC記錄我遇到的坑_AOP注解無效,切面不執(zhí)行的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java List的remove()方法陷阱以及性能優(yōu)化
Java List在進(jìn)行remove()方法是通常容易踩坑,本文就詳細(xì)的介紹一下陷阱以及性能優(yōu)化,感興趣的可以了解一下2021-10-10mybatis同一張表多次連接查詢相同列賦值問題小結(jié)
這篇文章主要介紹了mybatis同一張表多次連接查詢相同列賦值問題,非常不錯,具有參考借鑒價值,需要的的朋友參考下2017-01-01