摘要:引用泛型除了方法因不能使用外部實(shí)例參數(shù)外,其他繼承實(shí)現(xiàn)成員變量,成員方法,方法返回值等都可使用。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。
為什么要使用泛型程序設(shè)計(jì)?
一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對(duì)應(yīng)類型;如果要編寫可以應(yīng)用于多種類型的代碼,這種刻板的限制對(duì)代碼的束縛就會(huì)很大。
----摘自原書Ordinary classes and methods work with specific types: either
primitives or class types. If you are writing code that might be used
across more types, this rigidity can be overconstraining.
----摘自英文版
從原書的第一段話引出,其實(shí)泛型的出現(xiàn)是為了讓編寫的代碼可以應(yīng)用于多種類型,解除只能使用具體類型的限制,這也就是參數(shù)化類型的概念。
泛型出現(xiàn)的契機(jī)泛型是在Java SE5出現(xiàn)的,也就是說(shuō)java5版本之前的java是不存在泛型的概念的。而Java5這個(gè)版本增加了泛型設(shè)計(jì)其中重要的一個(gè)原因就是:優(yōu)雅的安全的讓容器類解除只能使用具體類型的束縛,從而適用于多種類型。
下面以ArrayList為例比較前后差異,證明泛型的優(yōu)雅和安全:
java1.4版本
public class ArrayList // 省略繼承和實(shí)現(xiàn) { transient Object[] elementData; // 用于存儲(chǔ)ArrayList對(duì)象的數(shù)組 public Object get(int index) { . . . } // 獲取數(shù)組對(duì)象 public void add(Object o) { . . . } // 添加數(shù)組對(duì)象 }
java5版本
public class ArrayList{ transient Object[] elementData; public E get(int index) {...} public boolean add(E e) {...}
從兩者對(duì)比可以看出,java1.4版本是使用Object類作為對(duì)象存取的的出參入?yún)?,這樣的好處自然是可以讓ArrayList類滿足編寫一次適用于多種類型的代碼設(shè)計(jì),可是這樣就暴露以下幾個(gè)問(wèn)題了:
當(dāng)獲取一個(gè)值時(shí)必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換
ArrayList strs = new ArrayList(); strs.add("hello"); String str = (String) strs.get(0);
這里如果不加(String)強(qiáng)制轉(zhuǎn)換,那么代碼在編譯期就會(huì)報(bào)錯(cuò):Incompatible types,并提示files.get(0)返回的是一個(gè)Object對(duì)象可是接收的是String類型對(duì)象,需要做類型強(qiáng)制轉(zhuǎn)換。
當(dāng)添加一個(gè)值時(shí)沒(méi)有在編譯器做類型錯(cuò)誤檢査
ArrayList files = new ArrayList(); files.add(new File("./hello.text")); File file = (File)files.get(0); // 正常 String file = (String)files.get(0); // 編譯器正常,運(yùn)行期報(bào)錯(cuò)
代碼在編譯期不會(huì)出錯(cuò),可是在運(yùn)行期的時(shí)候就會(huì)報(bào)強(qiáng)轉(zhuǎn)錯(cuò)誤:java.lang.ClassCastException,這個(gè)問(wèn)題其實(shí)也就是使用Object類的弊端了。
既然java1.4出現(xiàn)了上述問(wèn)題,那么現(xiàn)在就是用java5版本的泛型來(lái)解決上述的問(wèn)題,如下:
當(dāng)獲取一個(gè)值時(shí)必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換(解決)
// 這里第二個(gè)<>省略了String,是因?yàn)轭愋屯茖?dǎo) ArrayListstrs = new ArrayList<>(); strs.add("hello"); String str = strs.get(0);
可以看出java5之后之后就不需要做類型強(qiáng)轉(zhuǎn)了,這是因?yàn)?strong>get方法的返回類型已經(jīng)在實(shí)例化的時(shí)候被
當(dāng)添加一個(gè)值時(shí)沒(méi)有在編譯器做類型錯(cuò)誤檢査(解決)
ArrayListfiles = new ArrayList<>(); files.add(new File("./../Fibonacci.java")); File file = (File)files.get(0); // 正常 String file = (String)files.get(0); // 編譯期就報(bào)錯(cuò)了
可以看出1.5之后之后就不需要可能到線上才會(huì)發(fā)現(xiàn)的bug,在編寫代碼的時(shí)候編輯器就給提前給提示:Inconvertible types; cannot cast "java.io.File" to "java.lang.String"(禁止File類型轉(zhuǎn)換成String類型了)
雖然泛型解決的問(wèn)題還有很多,但是總的來(lái)說(shuō)都是為了:更優(yōu)雅的更安全的讓容器類解除只能使用具體類型的束縛,從而適用于多種類型。
泛型的語(yǔ)法&使用范圍下面我們就來(lái)正式講一下泛型的語(yǔ)法,以及使用范圍:
泛型接口
語(yǔ)法定義:
定義泛型:接口名之后定義該類會(huì)使用到的所有泛型。
引用泛型:除了static方法因不能使用外部實(shí)例參數(shù)外,其他繼承、實(shí)現(xiàn)、成員變量,成員方法等都可使用。
泛型實(shí)參:通過(guò)繼承類的實(shí)例化時(shí)傳入,不傳默認(rèn)是Object
語(yǔ)法結(jié)構(gòu):
interface 接口名 <定義泛型標(biāo)識(shí)> extends 父接口名 <引用泛型標(biāo)識(shí)> { public 引用泛型標(biāo)識(shí) var; ... }
案例 —— 生成器接口:
/** * 生成器是一種專門負(fù)責(zé)創(chuàng)建對(duì)象的類,是類似工廠模式,不同的是工廠模式一般需要入?yún)?,而生成器不需要? * 即生成器是:無(wú)需額外的信息就可以知道如何創(chuàng)建對(duì)象,一般來(lái)說(shuō)生成器只會(huì)定義一個(gè)創(chuàng)建對(duì)象的方法。 * 本例子中的創(chuàng)建對(duì)象方法就是next * @param泛型類*/ public interface Generator { T next(); }
語(yǔ)法定義:
定義泛型:類名之后定義該類會(huì)使用到的所有泛型。
引用泛型:除了static方法因不能使用外部實(shí)例參數(shù)外,其他繼承、實(shí)現(xiàn)、成員變量,成員方法,方法返回值等都可使用。
泛型實(shí)參:類的實(shí)例化時(shí)鉆石符放在類名之后,如:new ArrayList
語(yǔ)法結(jié)構(gòu):
class 類名 <定義泛型標(biāo)識(shí)> extends 父類名 <引用泛型標(biāo)識(shí)>, implements 接口名 <引用泛型標(biāo)識(shí)> { private 引用泛型標(biāo)識(shí) var; ... }
案例 —— 生成器具體實(shí)現(xiàn)類:
public class TestBasicGenerator泛型方法implements Generator { private Class type = null; // 本來(lái)下面寫法會(huì)更優(yōu)雅一點(diǎn),但是因?yàn)榉盒偷念愋筒脸龑?dǎo)致這種寫法是會(huì)報(bào)錯(cuò)的 // private Class type = T.class; public TestBasicGenerator(Class clazz) { type = clazz; } public static Generator gen(Class clazz) { return new TestBasicGenerator (clazz); } public T next() { try { return (T) type.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { // 書上的方法,通過(guò)靜態(tài)泛型方法:簡(jiǎn)單 Generator gen = TestBasicGenerator.gen(Coffee.class); Coffee c = gen.next(); // 課后習(xí)題,通過(guò)實(shí)例化方法:復(fù)雜 Generator gen1 = new TestBasicGenerator (Coffee.class); Coffee c1 = gen1.next(); } }
語(yǔ)法定義:
定義泛型:該方法修飾符之后定義該方法會(huì)使用到的所有泛型。
引用泛型:包括返回值、參數(shù)、內(nèi)部變量,內(nèi)部方法等,除非該方法是static方法,否則也可以使用類上定義的泛型,兩者不沖突。
泛型實(shí)參:
顯示傳遞:方法調(diào)用時(shí)鉆石符放在方法名之前,"."號(hào)之后,如:New.
隱式傳遞:無(wú)需傳遞,編譯器會(huì)根據(jù)上下文(入?yún)⒒蛘叻祷刂到邮兆兞浚┩茖?dǎo)出具體的類型,如:New.show("hello")、Map
語(yǔ)法結(jié)構(gòu):
public class 類名 { public <定義泛型標(biāo)識(shí)> 返回類型 方法名(引用泛型標(biāo)識(shí) 形參名) { ... } }
案例 —— 創(chuàng)建常用容器對(duì)象的工具類(簡(jiǎn)略版):
public class New { public static可變參數(shù)與泛型方法Map map() { return new HashMap (); } public static void show(T title) { System.out.println(title); } // Examples: public static void main(String[] args) { Map > sls = New.map(); // 顯示傳遞 New. show("hello"); // 隱式傳遞 New.show("hello"); // 編譯器會(huì)報(bào)show方法不能傳遞String類型,證明顯示傳遞為主,隱式傳遞為輔 New. show("hello"); } }
可變參數(shù)方法可以與泛型無(wú)縫結(jié)合:
案例 —— 入?yún)⒕酆?
public class GenericVarargs { public static泛型邊界List makeList(T... args) { List result = new ArrayList (); for(T item : args) result.add(item); return result; } public static void main(String[] args) { ls = makeList("A", "B", "C"); System.out.println(ls); // 打印出:[A, B, C] } }
有時(shí)您可能希望在泛型類、泛型方法、泛型接口中限制一下傳入的泛型實(shí)參的類型。例如,對(duì)數(shù)字進(jìn)行操作的方法可能只想接受Number子類或者父類的實(shí)例,那這個(gè)時(shí)候就需要用到邊界通配符了:
泛型上界類型通配符使用上界限制類型參數(shù),需要借助extends關(guān)鍵之,先在<>中寫類型參數(shù)的標(biāo)志號(hào),后跟extends,最后才是上界類型(注意:上界類型可以多個(gè),但是最多只允許一個(gè)類搭配多個(gè)接口,類還必須寫在第一位,因?yàn)閖ava是單繼承多實(shí)現(xiàn)),用法一般只用于泛型方法、泛型接口、泛型類這三處語(yǔ)法中的定義泛型的地方。
復(fù)雜案例如下(僅做案例):
// 表示類型實(shí)參只能是Number的子類,并且該子類還要實(shí)現(xiàn)List接口,否則編譯報(bào)錯(cuò)(上界不包含上) public class TestTypeErasure> { ... }
上界通配符
泛型下屆類型通配符使用下屆限制類型參數(shù),需要借助super關(guān)鍵之,先在<>中寫無(wú)界通配符?,后跟super,最后才是下屆具體類型或泛型標(biāo)識(shí)符(注意:這里就只能一個(gè)了),用法一般只用于限制容器類值。
復(fù)雜案例如下(僅做案例):
// 表示類型實(shí)參可以是Integer類型,或者Integer的父類(下屆包含下) public static void addNumbers(List super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }泛型無(wú)界類型通配符
上面講下屆通配符時(shí)需要借助無(wú)界通配符,但是它不止有哪一種寫法,還可以直接在<>中寫?,表示不限定類型參數(shù),類似。但不同的是使用了無(wú)界通配符<>,就限定使用add/addAll這樣的插入方法插入非null值,只能通過(guò)賦值來(lái)實(shí)現(xiàn),所以一般>只用在方法的形參中。
案例如下:
// 表示類型實(shí)參可以是Integer類型,或者Integer的父類(下屆包含下) public class TestBounds{ public static void printList(List> list) { list.add(null); // 正常 list1.add(2); // 編譯報(bào)錯(cuò) } public static void main(String[] args) { List泛型的實(shí)現(xiàn)原理——類型擦除(type erasure) 什么是類型擦除?
1、Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
2、Insert type casts if necessary to preserve type safety.
3、Generate bridge methods to preserve polymorphism in extended generic types.
---摘自oracle官網(wǎng)java8文檔1、替換所有泛型類型中的類型參數(shù)為其邊界,如果無(wú)邊界,將替換為Object。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。
2、如有必要,插入類型強(qiáng)轉(zhuǎn)以保持類型安全。
3、生成橋接方法以保留繼承泛型類型中的多態(tài)性。
從官網(wǎng)描述上看程序在解析成字節(jié)碼之后,會(huì)把:定義泛型的地方擦除;使用泛型的地方用邊界(上界)替換;傳入泛型實(shí)參的地方也擦除,如果可能造成類型安全問(wèn)題,就加類型強(qiáng)轉(zhuǎn);如果父類在類型擦除之后不合符子類的調(diào)用了,子類會(huì)增加橋接方法來(lái)保留多態(tài)。
案例證明——TestTypeErasure類:
public class TestTypeErasure{ public T num; public void test(E arg) { System.out.println(arg); } public static void main(String[] args) { new ArrayList (); } }
java6對(duì)TestTypeErasure的字節(jié)碼反編譯之后:
public class generics.yjm.TestTypeErasure extends java.lang.Object{ public java.lang.Number num; public generics.yjm.TestTypeErasure(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public void test(java.lang.Object); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_1 4: invokevirtual #3; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 7: return public static void main(java.lang.String[]); Code: 0: new #4; //class java/util/ArrayList 3: dup 4: invokespecial #5; //Method java/util/ArrayList." ":()V 7: pop 8: return }
從java6的反編譯的代碼中可以分析出,原來(lái)的T被編譯成了java.lang.Number,E被替換成了java.lang.Object,
java8反編譯之后:
public class generics.yjm.TestTypeErasure{ public T num; public generics.yjm.TestTypeErasure(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: return public void test(E); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_1 4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 7: return public static void main(java.lang.String[]); Code: 0: new #4 // class java/util/ArrayList 3: dup 4: invokespecial #5 // Method java/util/ArrayList." ":()V 7: pop 8: return }
從java8的反編譯的代碼中可以分析出,原來(lái)的T還是T,E還是E,
我們已經(jīng)知道泛型是在java SE5才出現(xiàn)的產(chǎn)物,出現(xiàn)的主要原因就是為了解決容器類的類型安全問(wèn)題,可是因?yàn)橐裱璲ava的版本迭代原則:二進(jìn)制兼容(Binary Compatibility)原則,從而折中的采用了類型擦除這樣的方法來(lái)實(shí)現(xiàn)java版的泛型。
泛型限制那既然java的泛型是一個(gè)折中版,總是有一些限制是要注意的,如下:
泛型不支持基本類型
無(wú)法創(chuàng)建類型參數(shù)(泛型)的實(shí)例
靜態(tài)字段static不能修飾類型參數(shù)
無(wú)法使用類型參數(shù)進(jìn)行強(qiáng)制轉(zhuǎn)換或者instanceof
無(wú)法創(chuàng)建參數(shù)化類型的數(shù)組
無(wú)法創(chuàng)建,捕獲或拋出參數(shù)化類型的對(duì)象
兩個(gè)方法,在其他條件相同的情況下,只是泛型不同不能當(dāng)做是方法的重載,會(huì)編譯出錯(cuò)
泛型標(biāo)志號(hào)的通用定義泛型的標(biāo)志號(hào)的范圍是這26個(gè)字母的大小寫,因?yàn)?strong>標(biāo)志號(hào)太多,為了增加代碼可讀性,讓每個(gè)標(biāo)志號(hào)有自己的含義,就默認(rèn)有了下面一套規(guī)范(非強(qiáng)制規(guī)范,只是為了方便理解和閱讀):
集合泛型類型:E或者T,如:ArrayList
映射泛型類型:K,V,如:Map
數(shù)值泛型類型:N
字符泛型類型:S
布爾值泛型類型:B
總的來(lái)說(shuō),命名規(guī)則就是:方便理解
文章涉及的小知識(shí)點(diǎn)
類型推導(dǎo)(type inference):
類型推導(dǎo)與泛型類:是指,編譯器會(huì)在編譯期根據(jù)變量聲明時(shí)的泛型類型自動(dòng)推斷出實(shí)例化的泛化類型,當(dāng)然要求就是java6版本中不可以省略<>(術(shù)語(yǔ):diamond,我喜歡稱之為鉆石符)即,ArrayList
類型推導(dǎo)與泛型方法:如上述的方法隱式傳遞就是方法的類型推導(dǎo),不一樣的是鉆石符可以省略。
二進(jìn)制兼容原則:指在相同系統(tǒng)環(huán)境中,高版本的Java編譯低版本的java文件產(chǎn)生的二進(jìn)制要與低版本編譯出來(lái)的二進(jìn)制兼容(如:java8版本編譯java7語(yǔ)法寫的java類生成的二進(jìn)制要和在java7時(shí)編譯出來(lái)的二進(jìn)制兼容),也就是所謂的向后兼容:
Java 8(完全二進(jìn)制與Java 7兼容)
Java 7(大多數(shù)二進(jìn)制與Java 6兼容)
Java 6(主要是與Java 5兼容的二進(jìn)制文件,加上一些模糊處理程序在規(guī)范之外生成類文件的注釋,因此這些類文件可能無(wú)法運(yùn)行)
Java 5(大多數(shù)二進(jìn)制與Java 1.4.2兼容,加上與混淆器相同的注釋)
JAVA 1.0-1.4.2(大多數(shù)二進(jìn)制版本與以前的版本兼容,一些前向兼容性的注釋甚至可以工作,但沒(méi)有經(jīng)過(guò)測(cè)試)
getTypeParameters方法作用:返回在類上申明的泛型的標(biāo)識(shí)符,并合并成數(shù)組放回,如果類上未申明未泛型標(biāo)識(shí)符,那就返回空數(shù)組。
橋接方法生成案例:
// 編譯前: public class Node{ public T data; public Node(T data) { this.data = data; } public void setData(T data) { System.out.println("Node.setData"); this.data = data; } } public class MyNode extends Node { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } } // 編譯后: public class Node { public Object data; public Node(Object data) { this.data = data; } public void setData(Object data) { System.out.println("Node.setData"); this.data = data; } } public class MyNode extends Node { public MyNode(Integer data) { super(data); } // 因?yàn)閟uper(data)緣故,編譯器會(huì)生成橋接方法,委托給原始的setData方法 public void setData(Object data) { setData((Integer) data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }
List>、List、List
List>:限制除了能使用add/addAll等方法插入null值,其他類型都不可以。也包含泛型的特性,可以是任意一種實(shí)參類型。
List:無(wú)限制,可以是任意一種或多種具體類型,但缺少泛型給予的編譯期類型安全保障。
List
List extends Object>:和List>基本相同,只是多加了一個(gè)限制,只能是Object類型的子類。
上下邊界通配符不能同時(shí)使用(廢話)。
文章引用oracle官網(wǎng)——類型擦除
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/71984.html
摘要:層次結(jié)構(gòu)如上所示,的子類都可以作為集合的元素加入到集合中,并且不會(huì)有任何影響。在實(shí)際編碼中一般都建議使用類型安全的容器,這樣不容易出錯(cuò),出錯(cuò)也會(huì)在編譯期間就會(huì)展現(xiàn)出來(lái)。 概述 說(shuō)起類型安全的容器,那么什么是類型不安全的容器呢?容器用來(lái)存儲(chǔ)數(shù)據(jù),常見(jiàn)的存儲(chǔ)數(shù)據(jù)的容器有數(shù)組和集合,數(shù)組有以下特點(diǎn): 長(zhǎng)度固定 只能存儲(chǔ)同一種類型的數(shù)據(jù) 因?yàn)閿?shù)組只能存儲(chǔ)同一種數(shù)據(jù)類型的數(shù)據(jù),那么它就是類型...
摘要:參數(shù)化的類型其中是參數(shù)化的類型。類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)其中是類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)。它們并沒(méi)有重載,而且泛型中也不存在重載這一說(shuō)法。除此之外,我們應(yīng)該盡量地多用泛型方法,而減少對(duì)整個(gè)類的泛化,因?yàn)榉盒头椒ǜ菀装咽虑檎f(shuō)明白。 泛型是適用于許多許多的類型 ---《JAVA編程思想》 在Java的面向?qū)ο缶幊踢^(guò)程中, 或許你知道運(yùn)用繼承、接口等一系列面向?qū)ο蟮膭?dòng)作來(lái)實(shí)現(xiàn)代碼復(fù)用...
摘要:然而中的泛型使用了類型擦除,所以只是偽泛型??偨Y(jié)本文介紹了泛型的使用,以及類型擦除相關(guān)的問(wèn)題。一般情況下泛型的使用比較簡(jiǎn)單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時(shí)要注意類型擦除的問(wèn)題。 簡(jiǎn)介 Java 在 1.5 引入了泛型機(jī)制,泛型本質(zhì)是參數(shù)化類型,也就是說(shuō)變量的類型是一個(gè)參數(shù),在使用時(shí)再指定為具體類型。泛型可以用于類、接口、方法,通過(guò)使用泛型可以使代碼更簡(jiǎn)單、安全。然...
摘要:泛型類容器類應(yīng)該算得上最具重用性的類庫(kù)之一。也就是說(shuō),如果使用泛型方法可以取代將整個(gè)類泛化,那么應(yīng)該有限采用泛型方法。以上,泛型的第一部分的結(jié)束。 根據(jù)《Java編程思想 (第4版)》中的描述,泛型出現(xiàn)的動(dòng)機(jī)在于: 有許多原因促成了泛型的出現(xiàn),而最引人注意的一個(gè)原因,就是為了創(chuàng)建容器類。 泛型類 容器類應(yīng)該算得上最具重用性的類庫(kù)之一。先來(lái)看一個(gè)沒(méi)有泛型的情況下的容器類如何定義: pub...
閱讀 989·2019-08-30 15:54
閱讀 1538·2019-08-30 15:54
閱讀 2457·2019-08-29 16:25
閱讀 1362·2019-08-29 15:24
閱讀 823·2019-08-29 12:11
閱讀 2566·2019-08-26 10:43
閱讀 1305·2019-08-26 10:40
閱讀 530·2019-08-23 16:24