摘要:唯一的例外是,它是所有引用類型的實(shí)例另一方面,程序可以調(diào)用方法來返回集合指定索引處的元素,其返回值是一個(gè)未知類型,但可以肯定的是,它總是一個(gè)。
泛型入門 編譯時(shí)不檢查類型的異常
public class ListErr { public static void main(String[] args) { // 創(chuàng)建一個(gè)只想保存字符串的List集合 List strList = new ArrayList(); strList.add("布達(dá)佩斯"); strList.add("布拉格"); // "不小心"把一個(gè)Integer對(duì)象"丟進(jìn)"了集合 strList.add(34); // ① strList.forEach(str -> System.out.println(((String)str).length())); // ② } }
上述程序創(chuàng)建了一個(gè)List集合,且該List集合保存字符串對(duì)象——但程序不能進(jìn)行任何限制,如果程序在①處“不小心”把一個(gè)Integer對(duì)象"丟進(jìn)"了List集合,這將導(dǎo)致程序在②處引發(fā)ClassCastException異常,因?yàn)槌绦蛟噲D把一個(gè)Integer對(duì)象轉(zhuǎn)換為String類型
使用泛型參數(shù)化類型,允許程序在創(chuàng)建集合時(shí)指定集合元素的類型。Java的參數(shù)化類型被稱為泛型(Generic)
class GenericList { public static void main(String[] args) { // 創(chuàng)建一個(gè)只想保存字符串的List集合 ListstrList = new ArrayList<>(); // ① strList.add("布達(dá)佩斯"); strList.add("布拉格"); // 下面代碼將引起編譯錯(cuò)誤 strList.add(34); // ② strList.forEach(str -> System.out.println(((String)str).length())); // ③ } }
strList集合只能保存字符串對(duì)象,不能保存其他類型的對(duì)象。創(chuàng)建特殊集合的方法是:在集合接口、類后增加尖括號(hào),尖括號(hào)里放一個(gè)數(shù)據(jù)類型,即表明這個(gè)集合接口、集合類只能保存特定類型的對(duì)象
①類型聲明,在創(chuàng)建這個(gè)ArrayList對(duì)象時(shí)也指定了一個(gè)類型參數(shù);②引發(fā)編譯異常;③不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換
泛型使程序更加簡(jiǎn)潔,集合自動(dòng)記住所有集合元素的數(shù)據(jù)類型,從而無須對(duì)集合元素進(jìn)行強(qiáng)制類型轉(zhuǎn)換
Java7泛型的“菱形”語法Java允許在構(gòu)造器后不需要帶完整的泛型信息,只要給出一對(duì)尖括號(hào)(<>)即可,Java可以推斷尖括號(hào)里應(yīng)該是什么泛型信息
public class DiamondTest { public static void main(String[] args) { // Java自動(dòng)推斷出ArrayList的<>里應(yīng)該是String List深入泛型countries = new ArrayList<>(); countries.add("法蘭西第五共和國(guó)"); countries.add("西班牙王國(guó)"); // 遍歷countries集合,集合元素就是String類型 countries.forEach(ele -> System.out.println(ele.length())); // Java自動(dòng)推斷出HashMap的<>里應(yīng)該是String , List Map > citiesInfo = new HashMap<>(); // Java自動(dòng)推斷出ArrayList的<>里應(yīng)該是String List cities = new ArrayList<>(); cities.add("巴黎"); cities.add("巴塞羅那"); citiesInfo.put("Bienvenue" , cities); // 遍歷Map時(shí),Map的key是String類型,value是List 類型 citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value)); } }
所謂泛型:就是允許在定義類、接口、方法時(shí)指定類型形參,這個(gè)類型形參將在聲明變量、創(chuàng)建對(duì)象、調(diào)用方法時(shí)動(dòng)態(tài)地指定(即傳入實(shí)際的類型參數(shù),也可稱為類型實(shí)參)
定義泛型接口、類List接口、Iterator接口、Map的代碼片段
// 定義接口時(shí)指定了一個(gè)類型形參,該形參名為E public interface List{ // 在該接口里,E可作為類型使用 // 下面方法可以使用E作為參數(shù)類型 void add(E x); Iterator iterator(); // ① } // 定義接口時(shí)指定了一個(gè)類型形參,該形參為E public interface Iterator { // 在該接口里E完全可以作為類型使用 E next(); boolean hasNext(); } // 定義該接口時(shí)指定了兩個(gè)類型形參,其形參名為K、V public interface Map { // 在該接口里K、V完全可以作為類型使用 Set keySet(); // ② V put(K key,V value); }
上面代碼說明泛型實(shí)質(zhì):允許在定義接口、類時(shí)聲明類型形參,類型形參在整個(gè)接口、類體內(nèi)可當(dāng)成類型使用
包含泛型聲明的類型可以在定義變量、創(chuàng)建對(duì)象時(shí)傳入一個(gè)類型實(shí)參,從而可以動(dòng)態(tài)地生成無數(shù)個(gè)邏輯上的子類,但這種子類在物理上并不存在
可以為任何類、接口增加泛型聲明(并不是只有集合類才可以使用泛型聲明,雖然集合類是泛型的重要使用場(chǎng)所)
// 定義Onmyoji類時(shí)使用了泛型聲明 public class Onmyoji{ // 使用T類型形參定義實(shí)例變量 private T info; public Onmyoji(){} // 下面方法使用T類型形參來定義構(gòu)造器 public Onmyoji(T info) { this.info = info; } public T getInfo() { return info; } public void setInfo(T info) { this.info = info; } public static void main(String[] args) { //由于傳給T形參的是String,所以構(gòu)造器參數(shù)只能是String Onmyoji a1 = new Onmyoji<>("安倍晴明"); System.out.println(a1.getInfo()); // 由于傳給T形參的是Double,所以構(gòu)造器參數(shù)只能是Double或double Onmyoji a2 = new Onmyoji<>(520.1314); System.out.println(a2.getInfo()); } }
當(dāng)創(chuàng)建帶泛型聲明的自定義類,為該類定義構(gòu)造器時(shí),構(gòu)造器名還是原來的類名,不要增加泛型聲明
從泛型類派生子類當(dāng)創(chuàng)建了帶泛型聲明的接口、父類之后,可以為該接口創(chuàng)建實(shí)現(xiàn)類,或從父類派生子類。當(dāng)使用這些接口、父類時(shí)不能再包含類型形參
// 定義類Shikigami繼承Onmyoji類,Onmyoji類不能跟類型形參 public class Shikigami extends Onmyoji{ } // 錯(cuò)誤
方法中的形參(或數(shù)據(jù)形參)代表變量、常量、表達(dá)式等數(shù)據(jù)。定義方法時(shí),可以聲明數(shù)據(jù)形參;調(diào)用方法(使用方法)時(shí),必須為這些數(shù)據(jù)形參傳入實(shí)際的數(shù)據(jù);與此類似的是,定義類、接口、方法時(shí)可以聲明類型形參,使用類、接口、方法時(shí)應(yīng)為類型形參傳入實(shí)際的類型
// 使用Onmyoji類時(shí)為T形參傳入String類型 public class Shikigami extends Onmyoji{ } // 正確
調(diào)用方法時(shí)必須為所有的數(shù)據(jù)參數(shù)傳入?yún)?shù)值,而使用類、接口時(shí)也可以不為類型形參傳入實(shí)際的類型參數(shù)
// 使用Onmyoji類時(shí),沒有為T形參傳入實(shí)際的類型參數(shù) public class Shikigami extends Onmyoji{ } // 正確
子類需要重寫父類的Getters和Setters方法
private String info; public String getInfo() { return "子類"+ super.getInfo(); } public void setInfo(String info) { this.info = info; }并不存在泛型類
ArrayList
// 分別創(chuàng)建List對(duì)象和List 對(duì)象 List l1 = new ArrayList<>(); List l2 = new ArrayList<>(); // 調(diào)用getClass()方法來比較l1和l2的類是否相等 System.out.println(l1.getClass() == l2.getClass()); // 輸出true
不管為泛型的類型形參傳入哪一種類型實(shí)參,對(duì)于Java來說,它們依然被當(dāng)成同一個(gè)類處理,在內(nèi)存中也只占用一塊內(nèi)存空間,因此在靜態(tài)方法、靜態(tài)初始化塊或者靜態(tài)變量的聲明和初始化中不允許使用類型形參
public class R{ static T info; // 代碼錯(cuò)誤,不能在靜態(tài)變量聲明中使用類型形參 T age; public void foo(T msg){} public static void bar(T msg){} // 代碼錯(cuò)誤,不能在靜態(tài)方法聲明中使用類型形參 }
由于系統(tǒng)中并不會(huì)真正生成泛型類,所以instanceof運(yùn)算符后不能使用泛型類。
if(cs instanceof List類型通配符) { ... }
如果Foo是Bar的一個(gè)子類型(子類或者子接口),而G是具有泛型聲明的類或接口,G
數(shù)組和泛型有所不同,假設(shè)Foo是Bar的一個(gè)子類型(子類或者子接口),那么Foo[]依然是Bar[]的子類型;但G
Java泛型的設(shè)計(jì)原則是,只要代碼在編譯時(shí)沒有出現(xiàn)警告,就不會(huì)遇到運(yùn)行時(shí)ClassCastException異常
使用類型通配符為了表示各種泛型List的父類,可以使用類型通配符,類型通配符是一個(gè)問號(hào)(?),將一個(gè)問號(hào)作為類型實(shí)參傳給List集合,寫作:List>(意思是未知類型元素的List)。這個(gè)問號(hào)(?)被稱為通配符,它的元素類型可以匹配任何類型
public void test(List> c) { for(int i = 0; i < c.size(); i++) { System.out.println(c.get(i)); } }
現(xiàn)在使用任何類型的List來調(diào)用它,程序依然可以訪問集合c中的元素,其類型是Object,這永遠(yuǎn)是安全的,因?yàn)椴还躄ist的真實(shí)類型是什么,它包含的都是 Object
這種帶通配符的List僅表示它是各種泛型List的父類,并不能把元素加入到其中
List> c = new ArrayList(); // 下面程序引起編譯錯(cuò)誤 c.add(new Object());
因?yàn)槌绦驘o法確認(rèn)c集合里元素的類型,所以不能向其中添加對(duì)象。根據(jù)前面的List
另一方面,程序可以調(diào)用get()方法來返回List>集合指定索引處的元素,其返回值是一個(gè)未知類型,但可以肯定的是,它總是一個(gè)Object。因此,把get()的返回值賦值給一個(gè)Object類型的變量,或者放在任何希望是Object類型的地方都可以
設(shè)定類型通配符的上限List
List extends Shape>是受限通配符的例子,此處的問號(hào)(?)代表一個(gè)未知的類型,此處的未知類型一定是Shape的子類也可以是Shape,因此可以把shape稱為這個(gè)通配符的上限(upper bound)
設(shè)定類型形參的上限Java泛型不僅允許在使用通配符形參時(shí)設(shè)定上限,而且可以在定義類型形參時(shí)設(shè)定上限,用于表示傳給該類型形參的實(shí)際類型要么是該上限類型,要門是該上限類型的子類
在一種更極端的情況下,程序需要為類型形參設(shè)定多個(gè)上限(至少有一個(gè)父類上限,可以有多個(gè)接口上限),表明該類型形參必須是其父類的子類(其父類本事也行),并且實(shí)現(xiàn)多個(gè)上限接口
//表明T類型必須是Number類或其子類,并必須實(shí)現(xiàn)java.io.Serializablepublic class Apple{ ... }
與類同時(shí)繼承父類、實(shí)現(xiàn)接口類似的是:為類型形參指定多個(gè)上限,所有的接口上限必須位于類上限之后。也就是說,如果需要為類型形參指定類上限,類上限必須位于第一位
泛型方法 定義泛型方法Java不允許把對(duì)象放進(jìn)一個(gè)未知類型的集合中
所謂泛型方法,就是在聲明方法時(shí)定義一個(gè)或多個(gè)類型形參
修飾符返回值類型 方法名(形參列表) { // 方法體... }
把上面方法的格式和普通方法的格式進(jìn)行對(duì)比,不難發(fā)現(xiàn)泛型方法的方法簽名比普通方法的方法簽名多了類型形參聲明,類型形參聲明以尖括號(hào)括起來,多個(gè)類型形參之間以逗號(hào)隔開,所有的類型形參聲明放在方法修飾符和方法返回值類型之間
public class GenericMethodTest { // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T類型形參, staticvoid fromArrayToCollection(T[] a, Collection c) { for (T o : a) { c.add(o); } } public static void main(String[] args) { Object[] oa = new Object[100]; Collection
為了讓編譯器能準(zhǔn)確地推斷出泛型方法中類型形參的類型,不要制造迷惑!系統(tǒng)一旦迷惑了,就是你錯(cuò)了!看如下程序
public class ErrorTest { // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T類型形參 staticvoid test(Collection from, Collection to) { for (T ele : from) { to.add(ele); } } public static void main(String[] args) { List
上面程序中定義了test方法,該方法用于將前一個(gè)集合里的元素復(fù)制到下一個(gè)集合中,該方法中的兩個(gè)形參 from、to 的類型都是 Collection
上面程序中調(diào)用test方法傳入了兩個(gè)實(shí)際參數(shù),其中as的數(shù)據(jù)類型是List
public class RightTest { // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T形參 staticvoid test(Collection extends T> from , Collection to) { for (T ele : from) { to.add(ele); } } public static void main(String[] args) { List
上面代碼改變了test方法簽名,將該方法的前一個(gè)形參類型改為 Collection extends T>,這種采用類型通配符的表示方式,只要test方法的前一個(gè)Collection集合里的元素類型是后一個(gè)Collection集合里元素類型的子類即可
泛型方法和類型通配符的區(qū)別大多數(shù)時(shí)候都可以使用泛型方法來代替類型通配符。例如,對(duì)于 Java 的 Collection 接口中兩個(gè)方法定義:
public interface Collection{ boolean containAll(Collection> c); boolean addAll(Collection extends E> c); ... }
上面集合中兩個(gè)方法的形參都采用了類型通配符的形式,也可以采用泛型方法的形式,如下所示
public interface Collection{ boolean containAll(Collection c); boolean addAll(Collection c); ... }
上面方法使用了
泛型方法允許類型形參被用來表示方法的一個(gè)或多個(gè)參數(shù)之間的類型依賴關(guān)系,或者方法返回值與參數(shù)之間的類型依賴關(guān)系。如果沒有這樣的類型依賴關(guān)系,就不應(yīng)該使用泛型方法
如果某個(gè)方法中一個(gè)形參(a)的類型或返回值的類型依賴于另一個(gè)形參(b)的類型,則形參(b)的類型聲明不應(yīng)該使用通配符----因?yàn)樾螀ⅲ╝)或返回值的類型依賴于該形參(b)的類型,如果形參(b)的類型無法確定,程序就無法定義形參(a)的類型。在這種情況下,只能考慮使用在方法簽名中聲明類型形參 ——也就是泛型方法
也可以同時(shí)使用泛型方法和通配符,如Java的Collections.copy方法
public class Collections { public staticvoid copy(List dest, List extends T> src) { ... } ... }
上面copy方法中的dest和src存在明顯的依賴關(guān)系,從源List中復(fù)制出來的元素,必須可以“丟進(jìn)”目標(biāo)List中,所以源List集合元素的類型只能是目標(biāo)集合元素的類型的子類型或者它本身。但JDK定義src形參類型時(shí)使用的是類型通配符,而不是泛型方法。這是因?yàn)椋涸摲椒o須向src集合中添加元素,也無須修改src集合里的元素,所以可以使用類型通配符,不使用泛型方法
類型通配符與泛型方法(在方法簽名中顯式聲明類型形參)還有一個(gè)顯著的區(qū)別:類型通配符既可以在方法簽名中定義形參的類型,也可以用于定義變量的類型;但泛型方法中的類型形參必須在對(duì)應(yīng)方法中顯式聲明
Java7的“菱形”語法與泛型構(gòu)造器Java允許在構(gòu)造器簽名中聲明類型形參,這樣就產(chǎn)生了所渭的泛型構(gòu)造器。一旦定義了泛型構(gòu)造器,接下來在調(diào)用構(gòu)造器時(shí),就不僅可以讓Java根據(jù)數(shù)據(jù)參數(shù)的類型來“推斷”類型形參的類型,而且程序員也可以顯式地為構(gòu)造器中的類型形參指定實(shí)際的類型
class Foo { publicFoo(T t) { System.out.println(t); } } public class GenericConstructor { public static void main(String[] args) { // 泛型構(gòu)造器中的T參數(shù)為String。 new Foo("瘋狂Java講義"); // 泛型構(gòu)造器中的T參數(shù)為Integer。 new Foo(200); // 顯式指定泛型構(gòu)造器中的T參數(shù)為String, // 傳給Foo構(gòu)造器的實(shí)參也是String對(duì)象,完全正確。 new Foo("瘋狂Android講義"); // 顯式指定泛型構(gòu)造器中的T參數(shù)為String, // 但傳給Foo構(gòu)造器的實(shí)參是Double對(duì)象,下面代碼出錯(cuò) new Foo(12.3); } }
前面介紹過 Java 7 新增的“菱形”語法,它允許調(diào)用構(gòu)造器時(shí)在構(gòu)造器后使用一對(duì)尖括號(hào)來代表泛型信息。但如果程序顯式指定了泛型構(gòu)造器中聲明的類型形參的實(shí)際類型,則不可以使用“菱形”語法
class MyClass{ public MyClass(T t) { System.out.println("t參數(shù)的值為:" + t); } } public class GenericDiamondTest { public static void main(String[] args) { // MyClass類聲明中的E形參是String類型。 // 泛型構(gòu)造器中聲明的T形參是Integer類型 MyClass mc1 = new MyClass<>(5); // 顯式指定泛型構(gòu)造器中聲明的T形參是Integer類型, MyClass mc2 = new MyClass (5); // MyClass類聲明中的E形參是String類型。 // 如果顯式指定泛型構(gòu)造器中聲明的T形參是Integer類型 // 此時(shí)就不能使用"菱形"語法,下面代碼是錯(cuò)的。 // MyClass mc3 = new MyClass<>(5); } }
上面程序中最后一行代碼既指定了泛型構(gòu)造器中的類型形參是 Integer 類型,又想使用“菱形”語法,所以這行代碼無法通過編譯
設(shè)定通配符下限實(shí)現(xiàn)將src集合里的元素復(fù)制到dest集合里的功能,因?yàn)閐est集合可以保存src集合里的所有元素,所以dest集合元素的類型應(yīng)該是src集合元素類型的父類,假設(shè)該方法需要一個(gè)返回值,返回最后一個(gè)被復(fù)制的元素。為了表示兩個(gè)參數(shù)之間的類型依賴,考慮同時(shí)使用通配符、泛型參數(shù)來實(shí)現(xiàn)該方法
public staticT copy(Collection dest, Collection extends T> src) { T last = null; for (T ele : src) { last = ele; dest.add(ele); } return last; }
實(shí)際上有一個(gè)問題:當(dāng)遍歷src集合的元素時(shí),src元素的類型是不確定的(但可以肯定它是T的子類),程序只能用T來籠統(tǒng)地表示各種src集合的元素類型
Listln = new ArrayList<>; List li = new ArrayList<>; // 下面代碼引起編譯錯(cuò)誤 Integer last = copy(ln, li);
ln的類型是List
對(duì)于上面的copy方法,可以這樣理解兩個(gè)集合參數(shù)之間的依賴關(guān)系:不管src集合元素的類型是什么,只要dest集合元素的類型與前者相同或是前者的父類即可。Java允許設(shè)定通配符的下限: super Type>,這個(gè)通配符表示它必須是Type本身,或是Type的父類
public class MyUtils { // 下面dest集合元素類型必須與src集合元素類型相同,或是其父類 public staticT copy(Collection super T> dest , Collection src) { T last = null; for (T ele : src) { last = ele; dest.add(ele); } return last; } public static void main(String[] args) { List ln = new ArrayList<>(); List li = new ArrayList<>(); li.add(5); // 此處可準(zhǔn)確的知道最后一個(gè)被復(fù)制的元素是Integer類型 // 與src集合元素的類型相同 Integer last = copy(ln , li); // ① System.out.println(ln); } }
使用這種語句,就可以保證程序的①處調(diào)用后推斷出晟后一個(gè)被復(fù)制的元素類型是 Integer,而不是籠統(tǒng)的 Number 類型
泛型方法與方法重載因?yàn)榉盒图仍试S設(shè)定通配符的上限,也允許設(shè)定通配符的下限,從而允許在一個(gè)類里包含如下兩個(gè)方法定義
public class MyUtils { public staticvoid copy(Collection dest , Collection extends T> src) {...} // ① public static copy(Collection super T> dest, Collection src) {...} // ② }
MyUtils類中包含兩個(gè)copy方法,這兩個(gè)方法的參數(shù)列表存在一定的區(qū)別,但這種區(qū)別不是很明確:這兩個(gè)方法的兩個(gè)參數(shù)都是Collection對(duì)象,前一個(gè)集合里的集合元素類型是后一個(gè)集合里集合元素類型的父類。如果這個(gè)類僅包含這兩個(gè)方法不會(huì)有任何錯(cuò)誤,但只要調(diào)用這個(gè)方法就會(huì)引起編譯錯(cuò)誤
Listln = new ArrayList<>; List li = new ArrayList<>; copy(ln , li);
上面程序調(diào)用copy方法,但這個(gè)copy()方法既可以匹配①號(hào)copy方法,此時(shí)T類型參數(shù)的類型是 Number;也可以匹配②號(hào)copy()方法,此時(shí)T參數(shù)的類型是Integer。編譯器無法確定這行代碼想調(diào)用哪個(gè)copy()方法,所以這行代碼將引起編譯錯(cuò)誤
Java8改進(jìn)的類型推斷Java8改進(jìn)了泛型方法的類型推斷能力,類型推斷主要有如下兩方面
可通過調(diào)用方法的上下文來推斷類型參數(shù)的目標(biāo)類型
可在方法調(diào)用鏈中,將推斷得到的類型參數(shù)傳遞到最后一個(gè)方法
class MyUtil擦除和轉(zhuǎn)換{ public static MyUtil nil() { return null; } public static MyUtil cons(Z head, MyUtil tail) { return null; } E head() { return null; } } public class InferenceTest { public static void main(String[] args) { // 可以通過方法賦值的目標(biāo)參數(shù)來推斷類型參數(shù)為String MyUtil ls = MyUtil.nil(); // 無需使用下面語句在調(diào)用nil()方法時(shí)指定類型參數(shù)的類型 MyUtil mu = MyUtil. nil(); // 可調(diào)用cons方法所需的參數(shù)類型來推斷類型參數(shù)為Integer MyUtil.cons(42, MyUtil.nil()); // 無需使用下面語句在調(diào)用nil()方法時(shí)指定類型參數(shù)的類型 MyUtil.cons(42, MyUtil. nil()); // 希望系統(tǒng)能推斷出調(diào)用nil()方法類型參數(shù)為String類型, // 但實(shí)際上Java 8依然推斷不出來,所以下面代碼報(bào)錯(cuò) // String s = MyUtil.nil().head(); String s = MyUtil. nil().head(); } }
在嚴(yán)格的泛型代碼里,帶泛型聲明的類總應(yīng)該帶著泛型參數(shù)。但是為了和古老的java代碼保持一致,也就是說為了向下兼容,也允許在使用帶泛型聲明的類時(shí)不指定實(shí)際的類型參數(shù)。如果沒有為這個(gè)泛型指定實(shí)際的類型參數(shù),則該類型參數(shù)被稱作raw type(原始類型),默認(rèn)是聲明該類型參數(shù)時(shí)指定的第一個(gè)上限類型
當(dāng)把一個(gè)具體泛型信息的對(duì)象賦值給另外一個(gè)沒有泛型信息的變量時(shí),所有尖括號(hào)之間的類型信息都將被扔掉。比如說將一個(gè)List
class Apple{ T size; public Apple() { } public Apple(T size) { this.size = size; } public void setSize(T size) { this.size = size; } public T getSize() { return this.size; } } public class ErasureTest { public static void main(String[] args) { Apple a = new Apple<>(6); // ① // a的getSize()方法返回Integer對(duì)象 Integer as = a.getSize(); // 把a(bǔ)對(duì)象賦給Apple變量,丟失尖括號(hào)里的類型信息 Apple b = a; // ② // b只知道size的類型是Number Number size1 = b.getSize(); // 下面代碼引起編譯錯(cuò)誤 Integer size2 = b.getSize(); // ③ } }
上面程序定義了一個(gè)帶泛型聲明的Apple類,其類型形參的上限是Number,這個(gè)類型形參用來定義Apple類的size變量。程序在①處創(chuàng)建了一個(gè)Apple對(duì)象,該Apple對(duì)象傳入了Integer作為類型形參的值,所以調(diào)用a的getSize()方法時(shí)返回Integer類型的值。當(dāng)把a(bǔ)賦給一個(gè)不帶泛型信息的b變量時(shí),編譯器就會(huì)丟失a對(duì)象的泛型信息,即所有尖括號(hào)里的信息都會(huì)丟失——因?yàn)锳pple的類型形參的上限是Number類,所以編譯器依然知道b的getSize()方法返回Number類型,但具體是Number的哪個(gè)子類就不清楚了
從邏輯上來看,List
public class ErasureTest2 { public static void main(String[] args) { Listli = new ArrayList<>(); li.add(34); li.add(59); List list = li; // 下面代碼引起“未經(jīng)檢查的轉(zhuǎn)換”的警告,編譯、運(yùn)行時(shí)完全正常 List ls = list; // ① // 但只要訪問ls里的元素,如下面代碼將引起運(yùn)行時(shí)異常 // ClassCastException System.out.println(ls.get(0)); } }
上面程序中定義了一個(gè)List
下面代碼與上面代碼的行為完全相似
public class ErasureTest2 { public static void main(String[] args) { Listli = new ArrayList<>(); li.add(34); li.add(59); System.out.println((String)li.get(0)); } }
程序從li中獲取一個(gè)元素,并試圖通過強(qiáng)制類型轉(zhuǎn)換把它轉(zhuǎn)換成一個(gè)String,將引發(fā)運(yùn)行時(shí)異常。前面使用泛型代碼時(shí),系統(tǒng)與之存在完全相似的行為,所以引發(fā)相同的ClassCastException異常
泛型與數(shù)組Java泛型與一個(gè)很重要的設(shè)計(jì)原則——如果一段代碼在編譯時(shí)沒有提出“[unchecked] 未經(jīng)檢查的轉(zhuǎn)換”警告,則程序在運(yùn)行時(shí)不會(huì)引發(fā)ClassCastException異常。正是基于這個(gè)原因,所以數(shù)組元素的類型不能包含類型變量或類型形參,除非是無上限的類型通配符。但可以聲明元素類型包含類型變量或類型形參的數(shù)組。也就是說,只能聲明List
Java不支持創(chuàng)建泛型數(shù)組
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66419.html
摘要:引用泛型除了方法因不能使用外部實(shí)例參數(shù)外,其他繼承實(shí)現(xiàn)成員變量,成員方法,方法返回值等都可使用。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設(shè)計(jì)? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對(duì)應(yīng)類型;如果要編寫可以應(yīng)用于多種類型的代碼,這種刻板的限制對(duì)代碼的束縛就會(huì)很大。----摘自原書Ordinary classes and meth...
摘要:知識(shí)點(diǎn)總結(jié)泛型知識(shí)點(diǎn)總結(jié)泛型泛型泛型就是參數(shù)化類型適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼泛型中的類型在使用時(shí)指定泛型歸根到底就是模版優(yōu)點(diǎn)使用泛型時(shí),在實(shí)際使用之前類型就已經(jīng)確定了,不需要強(qiáng)制類型轉(zhuǎn)換。 Java知識(shí)點(diǎn)總結(jié)(Java泛型) @(Java知識(shí)點(diǎn)總結(jié))[Java, Java泛型] [toc] 泛型 泛型就是參數(shù)化類型 適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼 泛型中的類型在使用時(shí)指定 泛...
摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。 前言 和C++以模板來實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...
摘要:泛型類在類的申明時(shí)指定參數(shù),即構(gòu)成了泛型類。換句話說,泛型類可以看成普通類的工廠。的作用就是指明泛型的具體類型,而類型的變量,可以用來創(chuàng)建泛型類的對(duì)象。只有聲明了的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),...
摘要:可以看到,如果我們給泛型類制定了上限,泛型擦除之后就會(huì)被替換成類型的上限。相應(yīng)的,泛型類中定義的方法的類型也是如此。參考語言類型擦除下界通配符和的區(qū)別 本篇博客主要介紹了Java類型擦除的定義,詳細(xì)的介紹了類型擦除在Java中所出現(xiàn)的場(chǎng)景。 1. 什么是類型擦除 為了讓你們快速的對(duì)類型擦除有一個(gè)印象,首先舉一個(gè)很簡(jiǎn)單也很經(jīng)典的例子。 // 指定泛型為String List list1 ...
閱讀 3377·2021-11-22 09:34
閱讀 2881·2021-10-09 09:43
閱讀 1462·2021-09-24 09:47
閱讀 2210·2019-08-30 12:53
閱讀 1009·2019-08-29 14:00
閱讀 3370·2019-08-29 13:17
閱讀 2277·2019-08-28 18:00
閱讀 1295·2019-08-26 12:00