一、學(xué)習(xí)目標(biāo)
1.泛型的作用和定義
2.泛型的基本使用
3.泛型中的通配符
4.泛型擦除
5.泛型中的約束和局限
二亏钩、泛型的作用和定義
1.引言
Java屬于強(qiáng)類型語(yǔ)言,所定義的每一個(gè)對(duì)象都具有明確的類型(基本類型+其他自定義類型)怎披,關(guān)于語(yǔ)言類型誉券,專業(yè)的一些解釋。
由此在定義一些容器對(duì)象時(shí)需要明確容器的元素類型篮条,或者使用Object類型弟头。
方式一:枚舉容器能夠接納和記錄每一個(gè)類型
package com.java.basis.generic;
/**
* 定義每一個(gè)類型獨(dú)立的方法
*/
public class NoTypeGeneric {
private String stringconfig;
private int intconfig;
private float floatconfig;
private User userconfig;
public void addConfig(String config){
this.stringconfig = config;
}
public void addConfig(int config){
this.intconfig = config;
}
public void addConfig(float config){
this.floatconfig = config;
}
public void addConfig(User config){
this.userconfig = config;
}
public String getStringConfig(){
return stringconfig;
}
public int getIntConfig(){
return intconfig;
}
public float getfloatConfig(){
return floatconfig;
}
public User getUserConfig(){
return userconfig;
}
}
使用
NoTypeGeneric noTypeGeneric = new NoTypeGeneric();
User user = new User("張三", 18);
noTypeGeneric.addConfig(user);
User valus = noTypeGeneric.getUserConfig();
通過枚舉的方式來實(shí)現(xiàn)容器能夠裝入和取出不同的元素時(shí),容器需要提供足夠多的類型對(duì)應(yīng)的方法涉茧。
方式二:通過父類Object來實(shí)現(xiàn)能夠承載不同類型的容器
package com.java.basis.generic;
/**
* 定義所有進(jìn)容器的類型為Object
*/
public class NoGeneric {
private Object config;
public void addConfig(Object config){
this.config = config;
}
public Object getConfig(){
return config;
}
}
使用:
NoGeneric noGeneric = new NoGeneric();
noGeneric.addConfig(1);
//強(qiáng)制類型轉(zhuǎn)換
int value = (int) noGeneric.getConfig();
該方式也是JDK1.5之前Java容器的實(shí)現(xiàn)方式赴恨,Java容器都是通過將類型向上轉(zhuǎn)型為Object類型來實(shí)現(xiàn)的,因此在從容器中取出來的時(shí)候需要手動(dòng)的強(qiáng)制轉(zhuǎn)換伴栓。此方式帶來的問題是代碼在編譯期間是無法知道類型轉(zhuǎn)換是否合法伦连,不合法則會(huì)導(dǎo)致ClassCastException。
2.泛型的作用和定義
- 作用:
1.使用泛型能寫出更加靈活通用的代碼
一個(gè)容器同一個(gè)方法可以根據(jù)輸入的不同類型來生產(chǎn)對(duì)應(yīng)類型的結(jié)果钳垮,實(shí)現(xiàn)了多類型復(fù)用
2.泛型將代碼安全性檢查提前到編譯期
能讓編譯器在編譯的時(shí)候借助傳入的類型參數(shù)檢查對(duì)容器的插入惑淳,獲取操作是否合法,從而將運(yùn)行時(shí)ClassCastException轉(zhuǎn)移到編譯時(shí) - 定義:
泛型就是將所操作的數(shù)據(jù)類型作為參數(shù)的一種語(yǔ)法饺窿。
三歧焦、泛型基本使用
泛型分為:
1 : 泛型接口 interface Observer<T>{}
2 : 泛型類 class ImplObserver<T> {}
3 : 泛型方法 private <T> void<T> call(T t)
3.1泛型類
泛型類在實(shí)體化的需要指定泛型參數(shù)的具體類型,泛型類中的泛型方法是一個(gè)全新的泛型參數(shù)類型
/**
* 自定義泛型,泛型類定義的泛型參數(shù)只作用于普通的方法
* @param <T>
*/
public class GenericClass<T> {
private T config;
public void addConfig(T config){
this.config = config;
}
public T getConfig(){
return config;
}
/**
* 泛型類中定義的泛型方法肚医,是一個(gè)全新的類型
* @param a
* @param <T>
*/
public <T> void show(T a){
System.out.println(a);
}
public static void main(String[] args) {
User user2 = new User("張三", 18);
GenericClass<User> userGeneric = new GenericClass<>();
userGeneric.addConfig(user2);
System.out.println(userGeneric.getConfig().getName());
userGeneric.show("s");
}
}
3.2泛型接口
public interface IGeneric<T> {
public T next();
}
泛型接口的實(shí)現(xiàn)方式
方式一:實(shí)現(xiàn)時(shí)未指定泛型參數(shù)的具體類型绢馍,那么該類將成為泛型類向瓷,實(shí)例化時(shí)需要指定類型
/**
* 類實(shí)現(xiàn)泛型接口如果不指定泛型的具體類型時(shí),該類將成為泛型類
* @param <T>
*/
public class ImpGeneric<T> implements IGeneric<T>{
@Override
public T next() {
return null;
}
}
方式二:實(shí)現(xiàn)時(shí)指定泛型參數(shù)的具體類型舰涌,泛型接口的泛型參數(shù)在實(shí)現(xiàn)的時(shí)候?qū)?huì)具體化猖任,實(shí)例化時(shí)不需要指定類型
/**
* 類實(shí)現(xiàn)泛型接口如果指定了泛型的具體類型時(shí),泛型接口的泛型參數(shù)在實(shí)現(xiàn)的時(shí)候?qū)?huì)具體化
*
*/
public class ImpGeneric2 implements IGeneric<String>{
@Override
public String next() {
return null;
}
public static void main(String[] args) {
ImpGeneric2 impGeneric = new ImpGeneric2();
impGeneric.next();
}
}
3.3泛型方法
泛型方法瓷耙,是在調(diào)用方法的時(shí)候指明泛型的具體類型 朱躺,泛型方法可以在任何地方和任何場(chǎng)景中使用,包括普通類和泛型類搁痛。
public class GenericMethod {
public <T> T getConfig(T...a){
System.out.println(a.getClass().getName().toString());
System.out.println(a.length);
return a[a.length/2];
}
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
String t = genericMethod.getConfig("adb", "ahb", "shshsh", "uuuu");
System.out.println(t);
}
}
四室琢、泛型中的通配符
4.1常用的 T,E落追,K盈滴,V,轿钠?
本質(zhì)上這些個(gè)都是通配符巢钓,沒啥區(qū)別,只不過是編碼時(shí)的一種約定俗成的東西
T(type) 表示具體的一個(gè)java類型疗垛,我們可以換成 A-Z 之間的任何一個(gè) 字母都可以
K V (key value) 分別代表java鍵值中的Key Value
E (element) 代表Element
症汹?表示不確定的 java 類型 ,無界通配符
4.2 贷腕?和 T 的區(qū)別
區(qū)別一:通過 T 來 確保 泛型參數(shù)的一致性
public class TAndQuestionmark {
public static <T> void show1(List<T> list){
for (Object object : list) {
System.out.println(object.toString());
}
}
public static void show2(List<?> list) {
for (Object object : list) {
System.out.println(object.toString());
}
}
public static void main(String[] args) {
List<Student> list1 = new ArrayList<>();
list1.add(new Student("zhangsan",18,0));
list1.add(new Student("lisi",28,0));
list1.add(new Student("wangwu",24,1));
// 通過 T 來 確保 泛型參數(shù)的一致性
//這里如果add(new Teacher(...));就會(huì)報(bào)錯(cuò)背镇,因?yàn)槲覀円呀?jīng)給List指定了數(shù)據(jù)類型為Student
show1(list1);
System.out.println("************分割線**************");
//這里我們并沒有給List指定具體的數(shù)據(jù)類型,可以存放多種類型數(shù)據(jù)
List list2 = new ArrayList<>();
list2.add(new Student("zhaoliu",22,1));
list2.add(new User("sunba",30));
show2(list2);
}
}
運(yùn)行結(jié)果:
Student{name='zhangsan', age=18, id=0}
Student{name='lisi', age=28, id=0}
Student{name='wangwu', age=24, id=1}
************分割線**************
Student{name='zhaoliu', age=22, id=1}
User{name='sunba', age=30}
區(qū)別二:類型參數(shù)T可以多重限定泽裳,而通配符 瞒斩?不行,通配符 涮总?不能用于定義類和泛型方法
/**
* 此時(shí)T的類型限定為必須InterFaceA 和 InterFaceB 的實(shí)現(xiàn)類
* @param config
* @param <T>
*/
public static <T extends InterFaceA & InterFaceB> void setGenericConfig(T config){
}
interface InterFaceA{}
interface InterFaceB{}
class MultiInterface implements InterFaceA, InterFaceB{
}
class MultiInterfaceA implements InterFaceA{
}
public static <T extends InterFaceA & InterFaceB> void setGenericConfig(T config){
}
// 不允許
// public static <? extends InterFaceA & InterFaceB> void setGenericConfig(? config){
//
// }
MultiInterfaceA multiInterfaceA = new MultiInterfaceA();
//報(bào)錯(cuò)不允許
setMultiGenericConfig(multiInterfaceA);
MultiInterface multiInterfaceAB = new MultiInterface();
setMultiGenericConfig(multiInterfaceAB);
使用 & 符號(hào)設(shè)定多重邊界(Multi Bounds)胸囱,指定泛型類型 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子類型,此時(shí)變量 t 就具有了所有限定的方法和屬性瀑梗。對(duì)于通配符來說烹笔,因?yàn)樗皇且粋€(gè)確定的類型,所以不能進(jìn)行多重限定抛丽。
區(qū)別三:通配符 ? 可以使用超類限定谤职,而類型參數(shù)T不行
類型參數(shù) T 只具有 一種 類型限定方式:
T extends A
但是通配符 ? 可以進(jìn)行 兩種限定:
? extends A
? super A
總結(jié):T 是一個(gè) 確定的 類型,通常用于泛型類和泛型方法的定義亿鲜,允蜈?是一個(gè) 不確定 的類型,通常用于泛型方法的調(diào)用代碼和形參,不能用于定義類和泛型方法陷寝。
4.3上界通配符 < ? extends E> 和< T extends E>
< ? extends E>和< T extends E>作用一樣,但是指定T可以確保 泛型參數(shù)的一致性其馏,具體看下面凤跑?和 T 的區(qū)別
用 extends 關(guān)鍵字聲明,表示參數(shù)化的類型可能是所指定的類型叛复,或者是此類型的子類
在類型參數(shù)中使用 extends 表示這個(gè)泛型中的參數(shù)必須是 E 或者 E 的子類仔引,這樣有兩個(gè)好處:
如果傳入的類型不是 E 或者 E 的子類,編譯不成功
泛型中可以使用 E 的方法褐奥,要不然還得強(qiáng)轉(zhuǎn)成 E 才能使用
使用: 類型參數(shù)列表中如果有多個(gè)類型參數(shù)上限咖耘,用逗號(hào)分開
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
同時(shí)extends左右都允許有多個(gè),如 T,V *extends* Comparable & Serializable
注意限定類型中撬码,只允許有一個(gè)類儿倒,而且如果有類,這個(gè)類必須是限定列表的第一個(gè)呜笑。
get和set:
set方法是不允許被調(diào)用的夫否,會(huì)出現(xiàn)編譯錯(cuò)誤,叫胁? extends X 表示類型的上界凰慈,類型參數(shù)是X的子類,那么可以肯定的說驼鹅,get方法返回的一定是個(gè)X(不管是X或者X的子類)編譯器是可以確定知道的微谓。但是set方法只知道傳入的是個(gè)X,至于具體是X的那個(gè)子類输钩,不知道豺型。
總結(jié):主要用于安全地訪問數(shù)據(jù),可以訪問X及其子類型买乃,并且不能寫入非null的數(shù)據(jù)触创。
4.4下界通配符 < ? super E>
用 super 進(jìn)行聲明,表示參數(shù)化的類型可能是所指定的類型为牍,或者是此類型的父類型哼绑,直至 Object
在類型參數(shù)中使用 super 表示這個(gè)泛型中的參數(shù)必須是 E 或者 E 的父類。
實(shí)際的應(yīng)用場(chǎng)景碉咆,合并兩個(gè)list時(shí)抖韩,假如源數(shù)據(jù)src的類型范圍大于合成的目標(biāo)數(shù)據(jù)范圍,則無法實(shí)現(xiàn)合并的需求:
dst 類型 “大于等于” src 的類型疫铜,這里的“大于等于”是指 dst 表示的范圍比 src 要大茂浮,因此裝得下 dst 的容器也就能裝 src 。
private <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子類
class Dog extends Animal {
}
get和set:
? super X 表示類型的下界席揽,類型參數(shù)是X的超類(包括X本身)顽馋,那么可以肯定的說,get方法返回的一定是個(gè)X的超類幌羞,那么到底是哪個(gè)超類寸谜?不知道,但是可以肯定的說属桦,Object一定是它的超類熊痴,所以get方法返回Object。編譯器是可以確定知道的聂宾。對(duì)于set方法來說果善,編譯器不知道它需要的確切類型,但是X和X的子類可以安全的轉(zhuǎn)型為X系谐。
總結(jié):主要用于安全地寫入數(shù)據(jù)巾陕,可以寫入X及其子類型。
4.5Class<T>和Class<?>
具體
public Class<?> clazz;
// 不可以纪他,因?yàn)?T 需要指定類型惜论,除非定義類時(shí)指定A的類型
public Class<A> clazzT;
public static void main(String [] args) throws InstantiationException, IllegalAccessException {
A a = createInstance(A.class);
B b = createInstance(B.class);
}
class A{}
class B{}
public static <A> A createInstance(Class<A> aClass) throws IllegalAccessException, InstantiationException {
return aClass.newInstance();
}
Class<T>在實(shí)例化的時(shí)候,T 要替換成具體類止喷。
Class<?>它是個(gè)通配泛型馆类,? 可以代表任何類型,所以主要用于聲明時(shí)的限制情況弹谁。
那如果也想 public Class clazzT;
這樣的話乾巧,就必須讓當(dāng)前的類也指定 T ,
public class Test3<T> {
public Class<?> clazz;
// 不會(huì)報(bào)錯(cuò)
public Class<T> clazzT;
}
4.6泛型嵌套
定義兩個(gè)泛型類预愤,Myclass類的泛型就是student類沟于,而student類的泛型是String類
class Student<T>{
private T score;
public T getScore(){
return score;
}
public void setScore(T score){
this.score=score;
}
}
class MyClass<T>{
private T cls;
public T getCls(){
return cls;
}
public void setCls(T cls){
this.cls=cls;
}
}
public static void main(String[] args) {
Student<String> stu=new Student<String>();
stu.setScore("great");
//泛型嵌套
MyClass<Student<String>> cls=new MyClass<Student<String>>();
cls.setCls(stu);
Student<String> stu2=new Student<String>();
stu2=cls.getCls();
System.out.println(stu2.getScore());//great
}
如上就實(shí)現(xiàn)了泛型的嵌套,在HsahMap中對(duì)鍵值對(duì)進(jìn)行便利的時(shí)候植康,也利用了泛型的嵌套
public static void main(String[] args) {
Map<String,String> map=new HashMap<String,String>();
map.put("a", "java300");
map.put("b", "馬士兵javase");
Set<Entry<String,String>> entrySet=map.entrySet();
for(Entry<String,String> entry:entrySet){
String key=entry.getKey();
String value=entry.getValue();
}
}
五旷太、泛型擦除
Java語(yǔ)言的泛型采用的是擦除法實(shí)現(xiàn)的偽泛型,泛型信息(類型變量销睁、參數(shù)化類型)編譯之后通通被除掉了供璧。
泛型類型只有在靜態(tài)類型檢查期間才出現(xiàn),在此之后冻记,程序中的所有泛型類型都將被擦除睡毒,替換成它們非泛型上界。
為了驗(yàn)證擦除冗栗,我們看下面分析:
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
GenericType<Food> a = new GenericType<>();
GenericType<Food> b = new GenericType<>();
//報(bào)錯(cuò)
// if(a instanceof GenericType<Fruit>){
//
// }
System.out.println(a.getClass() == b.getClass());
執(zhí)行結(jié)果為
true
true
ArrayList<String>和ArrayList<Integer>在運(yùn)行時(shí)事實(shí)上是相同的類型演顾。這兩種類型都被擦除成它們的“原生”類型供搀,即ArrayList。
所以不能獲取或者用instanceof等關(guān)鍵字來判斷泛型的具體類型钠至,只能得到原始類型葛虐。
在JDK1.5后Signature屬性被增加到了Class文件規(guī)范中,它是一個(gè)可選的定長(zhǎng)屬性棉钧,可以出現(xiàn)在類屿脐、字段表和方法表結(jié)構(gòu)的屬性表中。在JDK1.5中大幅度增強(qiáng)了Java語(yǔ)言的語(yǔ)法掰盘,在此之后,任何類赞季、接口愧捕、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Singature屬性會(huì)為它記錄泛型簽名信息申钩。Signature屬性就是為了彌補(bǔ)擦除法的缺陷而增設(shè)的次绘,Java可以通過反射獲得泛型類型,最終的數(shù)據(jù)來源也就是這個(gè)屬性撒遣。
可以看class編碼如下:
從Signature屬性的出現(xiàn)我們還可以得出結(jié)論邮偎,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除义黎,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息禾进,這也是我們能通過反射手段取得參數(shù)化類型的根本依據(jù)。
六廉涕、泛型中的約束和局限性
5.1不能用基本類型實(shí)例化類型參數(shù)
泛型要求包容的是對(duì)象類型泻云,而基本數(shù)據(jù)類型在Java中不屬于對(duì)象。但是基本數(shù)據(jù)類型有其封裝類狐蜕,且為對(duì)象類型宠纯。
5.2因?yàn)榉盒筒脸\(yùn)行時(shí)類型查詢只適用于原始類型
Restrict<Double> restrict = new Restrict<>();
// if(restrict instanceof Restrict<Double>)
// if(restrict instanceof Restrict<T>)
Restrict<String> restrictString= new Restrict<>();
System.out.println(restrict.getClass()==restrictString.getClass());
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());
5.2泛型類的靜態(tài)上下文中類型變量失效
不能在靜態(tài)域或方法中引用類型變量层释。因?yàn)榉盒褪且趯?duì)象創(chuàng)建的時(shí)候才知道是什么類型的婆瓜,而對(duì)象創(chuàng)建的代碼執(zhí)行先后順序是static的部分,然后才是構(gòu)造函數(shù)等等贡羔。所以在對(duì)象初始化之前static的部分已經(jīng)執(zhí)行了廉白,如果你在靜態(tài)部分引用的泛型,那么毫無疑問虛擬機(jī)根本不知道是什么東西乖寒,因?yàn)檫@個(gè)時(shí)候類還沒有初始化蒙秒。
5.3不能創(chuàng)建參數(shù)化類型的數(shù)組
Restrict<Double>[] restrictArray;
//Restrict<Double>[] restricts = new Restrict<Double>[10];
//ArrayList<String>[] list1 = new ArrayList<String>[10];
//ArrayList<String>[] list2 = new ArrayList[10];
5.4不能實(shí)例化類型變量
//不能實(shí)例化類型變量
// public Restrict() {
// this.data = new T();
// }
5.5不能捕獲泛型類的實(shí)例
public class ExceptionRestrict {
/*泛型類不能extends Exception/Throwable*/
//private class Problem<T> extends Exception;
/*不能捕獲泛型類對(duì)象*/
// public <T extends Throwable> void doWork(T x){
// try{
//
// }catch(T x){
// //do sth;
// }
// }
public <T extends Throwable> void doWorkSuccess(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
}
七、總結(jié)
1.泛型就是將所操作的數(shù)據(jù)類型作為參數(shù)的一種語(yǔ)法宵统,作用:A:使用泛型能寫出更加靈活通用的代碼晕讲;B:泛型將代碼安全性檢查提前到編譯期覆获;C:泛型會(huì)在編譯期被擦出,導(dǎo)致類型無法獲取泛型具體類型瓢省,或者方法重載沖突弄息,但是JDK1.5之后加入Signature規(guī)范記錄了泛型具體類型,所以并沒有真正的擦除泛型的具體類型勤婚,當(dāng)然我們也可以使用反射獲取泛型的具體類型摹量;
2.泛型接納的是對(duì)象類型,不能用基本類型實(shí)例化類型參數(shù)馒胆;
3.泛型的類型查詢以及繼承關(guān)系缨称,都?xì)w屬于原始類型;
4.可以聲明參數(shù)化類型的數(shù)組祝迂,但是不能創(chuàng)建睦尽;
5.?通配符可以實(shí)現(xiàn)下界以及多多重限定型雳,而多重限定中当凡,只允許有一個(gè)類,而且如果有類纠俭,這個(gè)類必須是限定列表的第一個(gè)沿量;
6.extends限定上線,主要用于數(shù)據(jù)訪問冤荆,能夠get到明確的類型朴则,而super主要用于安全地寫入數(shù)據(jù),可以寫入X及其子類型钓简。