Java泛型使用一些常見報錯總結(jié)
一、什么時候使用泛型會報錯?
報錯幾乎都發(fā)生在編譯時期,這正是泛型保護(hù)我們的地方。它們源于對泛型規(guī)則(特別是繼承規(guī)則)的誤解。
錯誤1:誤認(rèn)為泛型類型存在繼承關(guān)系(最常見?。?/h3>
核心規(guī)則( invariance - 不可變性):
Box<String> 和 Box<Object> 沒有任何繼承關(guān)系,即使 String 是 Object 的子類。
List<String> strList = new ArrayList<>(); List<Object> objList = strList; // 編譯錯誤! incompatible types(不兼容的類型)
為什么會報錯?
如果編譯器允許這樣做,就會破壞類型安全。因?yàn)?nbsp;objList 的類型是 List<Object>,意味著你可以向里面添加任何 Object 的子類,比如 Integer。
// 假設(shè)上面的代碼不報錯,會發(fā)生什么: objList.add(new Integer(100)); // 這看起來是合法的,因?yàn)镮nteger是Object的子類 String firstElement = strList.get(0); // ?。?!運(yùn)行時錯誤:ClassCastException // 你試圖從一個聲稱只裝String的列表里,取出一個Integer并當(dāng)成String
編譯器通過報錯阻止了這種可能引發(fā)運(yùn)行時災(zāi)難的代碼。這就是泛型提供的編譯期類型安全。
錯誤2:試圖創(chuàng)建泛型數(shù)組
T[] array = new T[10]; // 編譯錯誤! generic array creation(無法創(chuàng)建泛型數(shù)組) List<String>[] listArray = new List<String>[10]; // 編譯錯誤!
為什么會報錯?
因?yàn)轭愋筒脸?。在運(yùn)行時,T 和 String 都被擦除了,變成了 Object。JVM 無法在創(chuàng)建數(shù)組時確認(rèn) array 和 listArray 的具體類型(數(shù)組需要知道其確切的組件類型以強(qiáng)制保證類型安全)。如果允許創(chuàng)建,可能會導(dǎo)致上述的類型安全問題。
繞過方法(但不安全):
你可以創(chuàng)建一個 Object[] 然后強(qiáng)制轉(zhuǎn)型,但這會收到編譯器警告,且需要自己保證類型安全。
List<String>[] listArray = (List<String>[]) new List<?>[10]; // 有警告,但不報錯
錯誤3:誤用通配符集合的寫入操作
List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(new Integer(100)); // 編譯錯誤! numbers.add(new Float(3.14f)); // 編譯錯誤! numbers.add(null); // 這是唯一可以的,因?yàn)閚ull沒有類型
為什么會報錯?
List<? extends Number> 的意思是“一個只讀的列表,其元素是某種未知的、繼承自 Number 的類型”。它可能是 ArrayList<Integer>,也可能是 ArrayList<Double>。編譯器無法確定到底是哪一種,所以為了絕對的類型安全,它禁止你加入任何元素(除了 null),因?yàn)槟憧赡軙?nbsp;Double 加到 ArrayList<Integer> 里。
錯誤4:誤用通配符集合的讀取操作
List<? super Integer> list = new ArrayList<Number>(); Integer num = list.get(0); // 編譯錯誤! Number num2 = list.get(0); // 編譯錯誤! Object obj = list.get(0); // 這是唯一可以的
為什么會報錯?
List<? super Integer> 的意思是“一個只寫的列表,其元素是某種未知的、Integer 的父類型”。你從里面取出的元素,編譯器只能確定它是 Integer 的某個父類,但無法確定具體是 Number 還是 Object。因此,為了保證安全,它只允許你賦值給最頂層的 Object 引用。
二、泛型“繼承”關(guān)系的正確認(rèn)知

如何理解這張圖:
不變性 (Invariance - 紅色部分):這是最根本的規(guī)則。
List<String>和List<Object>在類型系統(tǒng)上是完全不同的類型,沒有繼承關(guān)系。你不能將它們互相賦值。試圖這么做是絕大多數(shù)編譯錯誤的根源。協(xié)變 (Covariance - 通過 ? extends 實(shí)現(xiàn),綠色部分):雖然
List<Integer>不是List<Number>的子類,但你可以通過上界通配符List<? extends Number>來獲得一個“只讀”的、安全的“繼承”視圖。一個ArrayList<Integer>可以被當(dāng)作一個List<? extends Number>來使用。這模擬了協(xié)變(子類容器可以被視為父類容器)。逆變 (Contravariance - 通過 ? super 實(shí)現(xiàn),綠色部分):同樣,你可以通過下界通配符
List<? super Integer>來獲得一個“只寫”的、安全的“繼承”視圖。一個ArrayList<Number>甚至ArrayList<Object>都可以被當(dāng)作一個List<? super Integer>來使用。這模擬了逆變(父類容器可以被視為子類容器,但用途相反)。
記?。?/strong>
<T>: 用于當(dāng)你既需要“讀”也需要“寫”操作時。這是最常用、最直接的方式。<? extends T>: 只讀。當(dāng)你只需要從集合中獲取元素時使用(Producer)。<? super T>: 只寫。當(dāng)你只需要向集合中添加元素時使用(Consumer)。
三、常見問題總結(jié)
Q:“談?wù)凧ava泛型中使用不當(dāng)會導(dǎo)致的問題和泛型的繼承關(guān)系。”
A:
“使用泛型最容易出錯的地方是對泛型容器繼承關(guān)系的誤解。核心規(guī)則是泛型具有不變性:Container<SubClass> 和 Container<SuperClass> 沒有繼承關(guān)系,即使 SubClass 繼承自 SuperClass。試圖將它們相互賦值是主要的編譯錯誤來源。
之所以這樣設(shè)計,是為了保證絕對的編譯期類型安全。如果允許這種賦值,就可以把 SuperClass 對象放入一個聲明為只裝 SubClass 的容器中,從而在后續(xù)讀取時引發(fā)運(yùn)行時 ClassCastException。
為了在需要時實(shí)現(xiàn)類似繼承的靈活性,Java引入了通配符:
<? extends T>實(shí)現(xiàn)了協(xié)變,讓我們能安全地讀取元素,將容器視為元素的生產(chǎn)者。<? super T>實(shí)現(xiàn)了逆變,讓我們能安全地寫入元素,將容器視為元素的消費(fèi)者。
其他常見錯誤還包括試圖創(chuàng)建泛型數(shù)組或?qū)嵗愋蛥?shù),這些都源于泛型在運(yùn)行時類型信息被擦除的實(shí)現(xiàn)機(jī)制。
PECS(Producer-Extends, Consumer-Super) 原則是指導(dǎo)我們正確使用通配符、避免編譯錯誤的最佳實(shí)踐。理解了不變性是基礎(chǔ),協(xié)變和逆變是工具,就能絕大多數(shù)泛型相關(guān)的編譯錯誤。
到此這篇關(guān)于Java泛型使用一些常見報錯總結(jié)的文章就介紹到這了,更多相關(guān)Java泛型使用報錯內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA 15款超級牛逼插件推薦(自用,超級牛逼)
這篇文章主要給大家推薦介紹了IntelliJ IDEA 15款超級牛逼插件,這15款插件都是自用的,真的非常推薦,需要的朋友可以參考下2020-11-11
springmvc項(xiàng)目使用@Valid+BindingResult遇到的問題
這篇文章主要介紹了springmvc項(xiàng)目使用@Valid+BindingResult遇到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
使用maven一步一步構(gòu)建spring mvc項(xiàng)目(圖文詳解)
這篇文章主要介紹了詳解使用maven一步一步構(gòu)建spring mvc項(xiàng)目,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
Mybatis中typeAliases標(biāo)簽和package標(biāo)簽使用
這篇文章主要介紹了Mybatis中typeAliases標(biāo)簽和package標(biāo)簽使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
druid監(jiān)控?zé)o法關(guān)閉的坑以及處理方式
這篇文章主要介紹了druid監(jiān)控?zé)o法關(guān)閉的坑以及處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
淺析SpringBoot2底層注解@Conditional@ImportResource
這篇文章主要為大家介紹了SpringBoot2底層注解@Conditional@ImportResource的分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05

