Java泛型的約束與局限性

2021-01-11 全棧技術資源社區

不能用基本類型實例化類型參數

不能用類型參數代替基本類型:例如,沒有Pair,只有Pair,其原因是類型擦除。擦除之後,Pair類含有Object類型的域,而Object不能存儲double值。這體現了Java語言中基本類型的獨立狀態。

運行時類型查詢只適用於原始類型(raw type)

運行時:通常指在Classloader裝載之後,JVM執行之時

類型查詢:instanceof、getClass、強制類型轉換

原始類型:即(raw type),泛型類型經編譯器類型擦除後是Object或泛型參數的限定類型(例如Pair,Comparable就是T的限定類型,轉化後泛型的原始類型就是Comparable,所以Pair類不帶泛型是Pair),即Pair類含有Comparable類型的域

JVM中沒有泛型

if(a instanceof Pair<String>) //ERROR,僅測試了a是否是任意類型的一個Pair,會看到編譯器ERROR警告

if(a instanceof Pair<T>) //ERROR

Pair<String> p = (Pair<String>) a;//WARNING,僅測試a是否是一個Pair

Pair<String> stringPair = ...;

Pair<Employee> employeePair = ...;

if(stringPair.getClass() == employeePair.getClass())

//會得到true,因為兩次調用getClass都將返回Pair.class

//加入Java開發交流君樣:756584822一起吹水聊天

不能創建參數化類型的數組(泛型數組)

參數化類型的數組:指類型帶有泛型參數的數組,也即泛型數組,如Pair[] 、 T[]

不能實例化參數化類型的數組,例如:

Pair<String> table = new Pair<String>[10]; //ERROR

在這裡我們假設可以實例化,那麼經編譯器類型擦除後,table的類型是Pair[],我們再讓它協變為Object[]:

Object[] objArray = table;

而一般來說,數組會記住他的元素類型Pair,我們如果試圖存儲其他類型的元素,就會拋出異常(數組存儲檢查),例如:

objArray[0] = "Hello"; //ERROR--component type is Pair

但是,對於泛型類型Pair,類型擦除會使這種不同類檢查機制無效,這就是不能實例化泛型數組的原因!

objArray[0] = new Pair<Employee>();

//如果泛型機制允許我們實例化數組,那麼這一步就沒理由出錯了!

//而這違背了我們的初衷(限定類型)

數組存儲只會檢查擦除後的類型,又因為Java語言設計數組可以協變,所以可以通過編譯

能夠通過數組存儲檢查,不過仍會導致一個類型錯誤,故不允許創建參數化類型的數組

注意,聲明類型為Pair[]的變量是合法的,只是不能創建這些實例(我們應該直接用new Pair[10]{…}來初始化這個變量)

泛型數組的間接實現:

通過泛型數組包裝器,如ArrayList類,維護一個Object數組,然後通過進出口方法set、get來限定類型和強制轉換數組類型,從而間接實現泛型數組,

例如:ArrayList: ArrayList<Pair<T>>、ArrayList<T>

不能實例化類型變量T

即不能使用

new T(..) , new T[..] 或 T.class

這樣的表達式中的類型變量

例如:

public Pair() { first = new T(); } //ERROR!

類型擦除將T改變成Object,調用非本意的new Object()

不能使用

new T(..)

但是,可通過反射調用

Class.newInstance

方法來構造泛型對象(要注意表達式T.class是非法的)

public static <T> Pair<T> makePair(Class<T> cl){

try{ return new Pair<>(cl.newInstance() , cl.newInstance()); }

catch(Exception ex) { return null; }

}

//這個方法可以按照下列方式調用:

Pair<String> p = Pair.makePair(String.class);

注意:Class類本身是泛型。String.class是一個Class的實例,因此makePair方法能夠推斷出pair的類型

不能使用new T[…]

解決方案:使用泛型數組包裝器,例如

ArrayList

然而,當在設計一個泛型數組包裝器時,例如方法minmax返回一個T[]數組,則泛型數組包裝器無法施展,因為類型擦除,

return (T [])new Object

是沒有意義的強轉不了。此時只好利用反射,調用

Array.newInstance

import java.lang.reflect.*;

...

public static <T extends Comparable> T[] minmax(T... a){

T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType() , 2);

【API文檔描述】public Class<?> getComponentType() 返回表示數組組件類型的 Class。如果此類不表示數組類,則此方法返回 null。

而ArrayList類中的toArray方法的實現就麻煩了

public Object[] toArray() 無參,返回Object[]數組即可 

public Object[] toArray() {

return Arrays.copyOf(elementData, size);

【API文檔描述】public static T[] copyOf(T[] original,int newLength)

複製指定的數組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對於在原數組和副本中都有效的所有索引,這兩個數組將包含相同的值。對於在副本中有效而在原數組無效的所有索引,副本將包含 null。若且唯若指定長度大於原數組的長度時,這些索引存在。所得數組和原數組屬於完全相同的類。

public T[] toArray(T[] a) a - 要存儲列表元素的T[]數組(如果它足夠大)否則分配一個具有相同運行時類型的新數組,返回該T[]數組

@SuppressWarnings("unchecked")

public <T> T[] toArray(T[] a) {

if (a.length < size)

// Make a new array of a's runtime type, but my contents:

return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //a.getClass()得運行時目的數組的運行時類型//加入Java開發交流君樣:756584822一起吹水聊天

System.arraycopy(elementData, 0, a, 0, size);

if (a.length > size)

a[size] = null;

return a;

【API文檔描述】

public static <T,U> T[] copyOf(U[] original,int newLength, Class<? extends T[]> newType)

複製指定的數組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對於在原數組和副本中都有效的所有索引,這兩個數組將包含相同的值。對於在副本中有效而在原數組無效的所有索引,副本將包含 null。若且唯若指定長度大於原數組的長度時,這些索引存在。所得數組屬於 newType 類。

泛型類的靜態上下文中類型變量無效

泛型類不能在靜態域或靜態方法中引用類型變量

public class Singleton<T>{

private static T singleInstance; //ERROR

public static T getSingleInstance(){...} //ERROR

類型擦除後只剩下Singleton類,因為靜態所以他只包含一個singleInstance域,如果能運行則以Singleton類為模板生成不同類型的域,因此產生了衝突

不能throws或catch泛型類的實例(有關異常)

泛型類繼承Throwable類不合法,如

public class Problem<T> extends Exception {...}

//ERROR 不能通過編譯

catch

子句不能使用類型變量

public static <T extends Throwable> void doWork(Class<T> t){

try{

do work

}catch (T e){ // ERROR

Logger.global.info(...)

不過,在異常規範中使用類型變量是允許的:

public static <T extends Throwable> void doWork(T t) throws T { //此時可以throws T

try{//加入Java開發交流君樣:756584822一起吹水聊天

}catch (Throwable realCause){ //捕獲到具體實例

t.initCause(realCause);

throw t; //這時候拋具體實例,所以throw t 和 throws T 是可以的!

此特性作用:可以利用泛型類、類型擦除、SuppressWarnings標註,來消除對已檢查(checked)異常的檢查,

unchecked和checked異常: Java語言規範將派生於Error類或RuntimeException的所有異常稱為未檢查(unchecked)異常,其他的是已檢查(checked)異常

Java異常處理原則:必須為所有已檢查(checked)異常提供一個處理器,即一對一個,多對多個

//SuppressWarning標註很關鍵,使得編譯器認為T是unchecked異常從而不強迫為每一個異常提供處理器

public static <T extends Throwable> void throwAs(Throwable e) throws

T{ //因為泛型和類型擦除,可以傳遞任意checked異常,例如RuntimeException類異常

throw (T) e;

假設該方法放在類Block中,如果調用 Block.throwAs(t); 編譯器就會認為t是一個未檢查的異常

public abstract class Block{

public abstract void body() throws Exception;

public Thread toThread(){

return new Thread(){

public void run(){

body();

}catch(Throwable t){

Block.<RuntimeException>throwAs(t);

}//加入Java開發交流君樣:756584822一起吹水聊天

};

public static <T extends Throwable> void throwAs(Throwable e) throws T{

再寫個測試類

public class Test{

public static void main(String[] args){

new Block(){

public void body() throws Exception{

//不存在ixenos文件將產生IOException,checked異常!

Scanner in = new Scanner(new File("ixenos"));

while(in.hasNext())

System.out.println(in.next());

}.toThread().start();

啟動線程後,throwAs方法將捕獲線程run方法所有checked異常,「處理」成uncheckedException(其實只是騙了編譯器)後拋出;有什麼意義?正常情況下,因為run()方法聲明為不拋出任何checked異常,所以必須捕獲所有checked異常並「包裝」到未檢查的異常中;意義:而我們這樣處理後,就不必去捕獲所有並包裝到

unchecked

異常中,我們只是拋出異常並「哄騙」了編譯器而已

注意擦除後的衝突

Java泛型規範有個原則:「要想支持擦除的轉換,就需要強行限制一個泛型類或類型變量T不能同時成為兩個接口類型的子類,而這兩個接口是統一接口的不同參數化」

注意:非泛型類可以同時實現同一接口,畢竟沒有泛型,很好處理

class Calender implements Comparable<Calender>{...}

class GGCalender extends Calender implements Comparable<GGCalender>{...} //ERROR

123在這裡GGCalender類會同時實現Comparable 和 Comparable,這是同一接口的不同參數化

相關焦點

  • 阿里內部學習指南《Effective Java中文 第3版》程式設計師進階必備
    經典Jolt獲獎作品《Effective Java》的第3版這本書,對上一版內容進行了徹底的更新,介紹了如何充分利用從泛型到枚舉、從註解到自動裝箱的各種特性,幫助讀者更加有效地使用Java程式語言及其基本類庫:java.lang. java.util和java.io,以及子包,如java.util. concurrent和java.util.function等。
  • 這些 Java 8 官方挖的坑,你踩過幾個?
    Caused by: java.lang.IllegalArgumentException: Illegal base64 character 3f    at java.util.Base64$Decoder.decode0(Base64.java:714)    at java.util.Base64$Decoder.decode
  • 知乎神回復:如果一定要在C+和Java中選擇,應該選擇哪一種?
    結論: (1)如果你是 計算機科班,大一學生,不需要立即找工作,想提高自己,那我的建議是: 把手上有關java 但是學習C++ 同樣會給你帶來很多收益(前提是學好的情況下): (1)你會變得自信,在有C++ 基礎上,學習go 1天,java的學習也就
  • Java 9正式發布,新特性解讀
    這不禁讓人反思 Java 傳統的研發模式的局限性。針對這些情況,Java 首席架構師 Mark Reinhold 已經發出倡議,建議從傳統的以特性驅動的發布周期,轉變為以時間驅動的(6 個月為周期)發布模式,並逐步的將 Oracle JDK 原有商業特性進行開源,Java Flight Recorder 等殺手級工具和特性,一定會大受開發者的歡迎。
  • 如何優雅的設計 Java 異常
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫導語異常處理是程序開發中必不可少操作之一,但如何正確優雅的對異常進行處理確是一門學問
  • java集合詳解合集
    它返回一個代表當前集合對象的泛型<T>迭代器,用於之後的遍歷操作1.1 CollectionCollection是最基本的集合接口,一個Collection代表一組Object的集合,這些Object被稱作Collection的元素。
  • 「原創」Java並發編程系列36|FutureTask
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫線程池源碼中出現了很多Callable、Future、FutureTask等以前沒介紹過的接口,尤其是線程池提交任務時總是把任務封裝成FutureTask,今天就來為大家解惑:
  • 拋棄 Java 改用 Kotlin 的六個月後,我後悔了!
    : Class<LocalDate> = LocalDate::class.java因此在 Kotlin 中,你必須寫成如下形式:val gson = GsonBuilder().registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()).create()這看起來非常醜陋
  • Java:如何更優雅地處理空值?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫導語在筆者幾年的開發經驗中,經常看到項目中存在到處空值判斷的情況,這些判斷,會讓人覺得摸不著頭緒,它的出現很有可能和當前的業務邏輯並沒有關係。
  • Java中設置classpath、path、JAVA_HOME的作用?
    path:Windows系統執行命令時要搜尋的路徑在dos命令行中執行命令的時候,會先從當前路徑去找,如果找不到,就會到path路徑下去找比如我的jdk裝在D盤下,當我在C盤執行java -version的時候會出現找不到java命令的情況,設置path的目的就是當在
  • java練習本(2019-06-12)
    題目java語言使用的字符集是?2.答案解析A.這是一種常用於會計系統中的編碼方式,與java關係不大,錯誤B.ASCII碼1961年提出,是目前使用最廣泛的西文字符集,但不是java使用的編碼C.GBK為一種漢子編碼格式,包含了大量的漢子編碼,java也並未使用它最為編碼格式D.Unicode編碼是一種將世界上所有符號都納入其中的編碼格式來避免編碼不匹配的亂碼問題
  • java集合【6】——— Iterable接口
    iterable接口整個接口框架關係如下(來自百度百科):iterable接口其實是java集合大家庭的最頂級的接口之一了,實現這個接口,可以視為擁有了獲取迭代器的能力。內部定義的方法 java集合最源頭的接口,實現這個接口的作用主要是集合對象可以通過迭代器去遍歷每一個元素。
  • Java | 第一個 SpringBoot 工程詳解
    version>0.0.1-SNAPSHOT</version>    <name>helloworld</name>    <description>Demo project for Spring Boot</description>    <properties>        <java.version
  • [博客更新]Java 中 final 關鍵詞的使用
    上一篇文章我們講了 java 中 static 關鍵字的使用,這裡再將一下 final 關鍵字的使用。final 在 java 中可以看做一個「終結者」,它可以定義類、定義方法和定義變量。method is final */System.out.println("我叫"+name+" 來自"+city+" 學號為"+id);}}publicclassFinalClassDemo{publicstaticvoid main(String[] args){}}就這樣吧博客文章地址:https://blog.sunriseydy.top/technology/code/java
  • Java 最困擾你的那些事
    有沒有搞錯缺乏對泛型的支持。C++ 中的模板要強大的多。事實上,在Java 中你根本不能在泛型中實例化一個類,除非你把這個類作為參數來聲明一個泛型。你很難給一個類加上結構函數並讓它銷毀這個類。RAII(一種資源管理模式,見 C++)卻一直非常有用。沒有操作符重載。
  • Java基礎面試題簡單總結
    中的保留字,現在沒有在java中使用11、數組有沒有length()這個方法?Int是java的原始數據類型,Integer是java為int提供的封裝類。Java為每個原始類型提供了封裝類。java編譯器要求方法必須聲明拋出可能發生的非運行時異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。
  • 剖析Java 集合框架(七)-HashMap為什麼線程不安全
    : java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode at java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1819) at java.util.HashMap$TreeNode.treeify(HashMap.java:1936)
  • 20個非常有用的Java程序片段(上)
    = null) {          out.close();      }  }3.得到當前方法的名字String methodName = Thread.currentThread().getStackTrace()[1].getMethodName()4.轉字符串到日期java.util.Date = java.text.DateFormat.getDateInstance
  • Java程式設計師必讀書,豆瓣評分9.8!等了10年終於更新了
    下面是一些來自Effective Java (第3版)裡面值得一提的新項目:函數接口、lambda表達式、方法引用和streamstry-with-resources語句接口和靜態成員類類型推斷,包括泛型類型的菱形運算符@SafeVarargs注釋新的庫特性第15項的Java 9模塊在第3版中,條目的數量從70多個增加到90個,如果你讀過以前的版本,而且時間沒那麼充足的情況下,建議你直接看新增加的那些條目
  • 普通大學生0基礎自學java怎樣才能進大廠?
    今天小築就來和朋友們聊一聊普通大學生0基礎自學java怎樣才能進入IT行業內。自學離不開好的java教程、互相幫助的小夥伴,和良好的自制力。真的想要自學好java,zui好不要有一口氣吃成個胖子的速成方式,好的辦法是循序漸進,有章有法的學習。