一. 泛型的本質(zhì)是什么:
- 是寫(xiě)給編譯器使用的“語(yǔ)法糖”
List<String> list=new ArrayList<String>(); //<String>會(huì)被編譯器看到愕掏,但會(huì)在字節(jié)碼中刪除
list.add("abc");//編譯成功:編譯器對(duì)“abc” instaceof String檢查,符合通過(guò)
// list.add(100); //編譯失敹ド :編譯器對(duì)100 instaceof String檢查饵撑,不符合失敗
String s=list.get(0); //編程器將代碼轉(zhuǎn)換為:String s=(String)list.get(0); 再進(jìn)行編譯
而JDK6+的編譯器將更加智能,第一行代碼可以自動(dòng)推測(cè)出對(duì)象實(shí)際類型的“泛型類型”
List<String> list=new ArrayList<>();
- 以下運(yùn)行結(jié)果為真:
ArrayList<Number> list1=new ArrayList<>();
ArrayList<Object> list2=new ArrayList<>();
boolean b=list1.getClass()==list2.getClass();//rs: true
沒(méi)有新的Class唆貌,所有的泛型信息滑潘,都會(huì)在編譯后進(jìn)行“型別擦除”。
- 結(jié)論:
(1) 泛型是在源代碼中加入的語(yǔ)法锨咙,為編譯器提供:(1)編譯檢查(2)源代碼生成语卤,這兩大工作。
(2) 擴(kuò)展認(rèn)知:“注解技術(shù)(Annotation)”實(shí)質(zhì)上是對(duì)泛型技術(shù)的擴(kuò)展,通過(guò)我們對(duì)源代碼的“修飾”(加入@xxx)粹舵,從而讓編譯器(進(jìn)一步:可以是執(zhí)行器)獲取更多的“糖”钮孵,從而可以做到:語(yǔ)法檢查(@Override)、源代碼生成(@Getter)眼滤、運(yùn)行特殊生命周期(@Autowared)等一系列的工作巴席,而以上這些工作,只需要在源代碼中修改诅需,而不在需要使用繁瑣的XML漾唉,從而使用Java以一種全新的姿態(tài)展現(xiàn)給開(kāi)發(fā)者,即:后Java時(shí)代诱担。
(3) 回想下:junit,lombok,spring,mybatis,JPA,spirng-cloud等毡证,都是在這種大技術(shù)背景下的產(chǎn)物。
二. 泛型的作用:
1. 可以讓編譯器提前檢查 一些運(yùn)行時(shí)異常:
List<String> list=new ArrayList<String>();
list.add(100);//編譯失斈柘伞:
2. 讓“數(shù)據(jù)類型參數(shù)化”:
(1)如下代碼完成了一個(gè)通用的“結(jié)果類型”設(shè)計(jì):
public class CommonResult<T> {
private int code;
private String message;
//可以認(rèn)為:T 是一種可變的數(shù)據(jù)類型(數(shù)據(jù)類型參數(shù)化)
private T entity;
public T getEntity(){
return entity;
}
public void setEntity(T entity){
this.entity=entity;
}
}
如此設(shè)計(jì):CommonResult將不會(huì)加為entity的數(shù)據(jù)類型有多種可能料睛,而去設(shè)計(jì)出相應(yīng)的CommonResult。
(2)結(jié)論:變量名稱的參數(shù)化摇邦,讓“方法的調(diào)用”與“方法的實(shí)現(xiàn)”在【變量名稱】達(dá)到解藕的目的恤煞;變量類型的參數(shù)化,讓“方法的調(diào)用”與“方法的實(shí)現(xiàn)”在【變量類型】達(dá)到解藕的目的施籍。
(3)聯(lián)想:java認(rèn)為“數(shù)據(jù)類型”是“算法+數(shù)據(jù)結(jié)構(gòu)”的結(jié)合體居扒,但FaaS時(shí)代的迫切任務(wù)是:把“算法”獨(dú)立成為一種數(shù)據(jù)類型,“Java的Lamda”就是為此而誕生(以前是使用接口完成丑慎,但太笨重了O参埂!竿裂!玉吁,此論點(diǎn)與泛型關(guān)系不大,但需要注意會(huì)有Lamda類型參數(shù)的存在腻异,同樣可以結(jié)合泛型进副,達(dá)到更靈活的目的)
三、泛型語(yǔ)法施加的主體有哪些:
1. 泛型類:
以下形式都是泛型類悔常,T影斑、E、V代表著可變的數(shù)據(jù)類型机打,class中任何出現(xiàn)變量類型的地方都可以使用(域矫户,形參類型,返回類型姐帚、局部變量類型吏垮、異常類型), 以下是一個(gè)相對(duì)極端的例子:
public class CommonResult<T,E,V extends Throwable> {
private T entity;
private E e;
private V v;
public CommonResult(T entity,E e,V v){
this.entity=entity;
this.e=e;
this.v=v;
}
public E ProcessEntity(T entity) throws V{
this.entity=entity;
if(Math.random()>0.5) {
throw v;
}
return e;
}
...
要引起注意的是: T,E,V代表著未知的類型障涯,所以在代碼中不可以做任何有假設(shè)其類型的行為:如new(假設(shè)它是一個(gè)非抽象類),instanceof(假設(shè)它是某一類型)等操作,這也是使用泛型的副作用膳汪,沒(méi)有銀彈唯蝶。
2. 泛型接口:
public interface WorkService<T> {
T produce();
void consume(T t);
}
3. 泛型方法:
public <M> M process(M m){
return m;
}
以上代碼理解:
(1) 此方法可以位于泛型或非泛型類中
(2)<M> 的作用有兩個(gè):一是聲明此方法是泛型方法;二是此方法中可以使用M做為參數(shù)化類型的符號(hào)遗嗽。
(3)常見(jiàn)的錯(cuò)誤:認(rèn)為在泛型類粘我,使用了帶有參數(shù)化類型的方法,就是泛型方法:
public interface WorkService<T> {
T produce();//這是不是泛型方法痹换,只是使用了參數(shù)化類型符號(hào)的方法
}
四征字、所謂的“形參”和“實(shí)參”問(wèn)題:
在定義類、接口娇豫、方法時(shí)匙姜,相當(dāng)于定義了數(shù)據(jù)類型的“形參”,傳入方法總結(jié)如下:
1. 【聲明變量類型】時(shí)傳入實(shí)參:
List<String> list;
WorkService<Number> ws;
此時(shí)冯痢,泛型類或接口中所有的包含T(或其它)的變量聲明將被Strring或Number替換氮昧,語(yǔ)法檢查機(jī)制和代碼生成機(jī)制將隨之變化。
需要注意浦楣,并不需要在實(shí)例化時(shí)指定袖肥,目前的編譯器可以智能的填入。
2. 【實(shí)現(xiàn)或繼承時(shí)】傳入實(shí)參:
public interface WorkService<T> {
T produce();
void consume(T t);
}
//=========實(shí)現(xiàn)或繼承式傳入"類型實(shí)參"==============//
class TomWrok implements WorkService<String>{
@Override
public String produce() {
return null;
}
@Override
public void consume(String t) {
}
}
注意幾點(diǎn):
(1)此時(shí)振劳,所有的替換工作必須由硬編碼方式完成椎组,
(2)TomWork類并不是泛型類,當(dāng)然也可以根據(jù)需要將之變成泛型類历恐,但此時(shí)和它的泛型接口r 的泛型類型已經(jīng)沒(méi)有任何關(guān)系了寸癌,因?yàn)橐呀?jīng)確定為String類型了。
/**============================================
* 實(shí)現(xiàn)或繼承式傳入"類型實(shí)參"
* 繼承將Tomwork變?yōu)榉盒皖惾踉簦琓只是符號(hào)灵份,可以是任何字母
* TomWrok確定了接口中實(shí)參,但又引入了新的形參
============================================**/
class TomWrok<T> implements WorkService<String>{
private T t;
@Override
public String produce() {
return null;
}
@Override
public void consume(String t) {
}
}
(3)以這種方式哮洽,只是將形參進(jìn)行了傳遞,并沒(méi)有實(shí)現(xiàn)弦聂,子類仍為泛型類
class Work<T> implements WorkService<T>{
@Override
public T produce() {
return null;
}
@Override
public void consume(T t) {
}
}
3. 【方法調(diào)用時(shí)】傳入:
此方式只是針對(duì)“泛型方法”:
public static <M> M process(M m) {
return m;
}
public static void main(String[] args) {
String s = "abc";
String rs = process(s);//由傳入實(shí)參的聲明類型做為實(shí)參
Number m = new Integer(123);
Number rm = process(m);//由傳入實(shí)參的聲明類型做為實(shí)參
int i = process(123);//由傳入實(shí)參的實(shí)際類型推斷做為實(shí)參
}
四鸟辅、對(duì)“參數(shù)化類型”的模糊限定:
1. 原理:
(1) 我們的需要是:全方面的“IS 關(guān)系”的檢查,但是目前編譯器做不到]汉7肆埂!
Java編譯器捺檬,可以對(duì)變量類型進(jìn)行"IS關(guān)系"進(jìn)行檢查再层,但無(wú)法對(duì)”泛型類型“的變量中的參數(shù)化類型進(jìn)行”IS關(guān)系檢查“。即:List<Number> :只能對(duì)ArrayList IS List
的檢查,但是不能做Integer IS Number
的檢查聂受。
上述問(wèn)題:就是本節(jié)要討論的解決方法蒿秦,我們要進(jìn)行【準(zhǔn)全面檢查】
原因是:
List<String>
和List<Integer>‘是同一個(gè)List class類型,這也稱為類型擦除蛋济,這樣編譯器在進(jìn)行”參數(shù)化類型的檢查時(shí)“棍鳖,就無(wú)法獲取有效的信息;但實(shí)際上編譯器可以做的更加高級(jí)碗旅,從源代碼中獲取類型渡处,再進(jìn)行遞歸方式的檢查,但這樣就會(huì)把編譯器做的足夠的復(fù)雜祟辟,導(dǎo)致編譯速度過(guò)慢医瘫,從而導(dǎo)致基于編譯器的工具鏈不具備生產(chǎn)價(jià)值。
Java在運(yùn)行時(shí)無(wú)法對(duì)傳入“由泛型類”生成的對(duì)象旧困,進(jìn)行“泛型類型”檢查醇份。即:List<String>
和List<Integer>‘是同一個(gè)List class類型(這稱為類型擦除)。
觀察如下案例:
/*
* @param list:此時(shí)只能通過(guò)基于 "IS List"的檢查叮喳,
* 而不能通過(guò)"IS LIST && IS Number"的檢查
*/
public static void fn1(List<Number> list){
}
public static void main(String[] args) {
ArrayList<Number> list1=new ArrayList<>();
/**
* 此時(shí)編譯器有足夠的"類型信息"被芳,對(duì)"IS List"進(jìn)行檢查,此時(shí)編譯檢查通過(guò)
*/
fn1(list1);
ArrayList<Integer> list2=new ArrayList<>();
/**
* 此時(shí)編譯器有足夠的"類型信息"馍悟,對(duì)"IS List"進(jìn)行檢查,
* 但沒(méi)有足夠的信息畔濒,對(duì)"IS Number"進(jìn)行檢查(因?yàn)樾蛣e擦除,同時(shí)現(xiàn)階段編譯器沒(méi)有能力讀到Number后進(jìn)行檢查)
* 基于安全角度锣咒,此時(shí)編譯不通過(guò)(信息不足侵状,任可錯(cuò)殺)
*/
//fn1(list2);
}
2. 通配符閃亮登場(chǎng):
泛型體系中設(shè)計(jì)了“?extends”毅整,原理是對(duì)“參數(shù)化類型”進(jìn)地適當(dāng)?shù)南薅ㄈば郑瑥亩咕幾g器適當(dāng)?shù)慕槿耄_(dá)到在編譯期讀取足夠多的信息悼嫉,完成“準(zhǔn)全方面的”的“IS”檢查艇潭。
觀察如下案例:
public static void fn2(List<? extends Number> list){
}
public static void main(String[] args) {
ArrayList<Integer> list2=new ArrayList<>();
/**
* 此時(shí)編譯器有足夠的"類型信息",對(duì)"IS List"進(jìn)行檢查,
* 也有了足夠的信息戏蔑,對(duì)"IS Number"進(jìn)行檢查(不是從類信息查詢蹋凝,而是從源代碼中讀到了 extends Number后進(jìn)行檢查)
* 此時(shí):ArrayList IS List && Integer IS Number,編譯通過(guò)
*/
fn2(list2);
ArrayList<String> list3=new ArrayList<>();
//此時(shí):ArrayList IS List 但是 String IS Not Number,編譯不通過(guò)
// fn2(list3);
...
}
泛型體系又進(jìn)而衍生出了一種更加“高級(jí)”的方式:“? super ”,完成了“參數(shù)化類型”的向上檢查总棵。
觀察以下案例:
public static void fn3(List<? super Number> list){
}
public static void main(String[] args) {
ArrayList<Integer> list4=new ArrayList<>();
//list4 is List;但是 Integer不是Number的祖先鳍寂,編譯不通過(guò)
//fn3(list4);
ArrayList<Object> list5=new ArrayList<>();
//list5 is List && Object 是 Number的祖先,編譯通過(guò)
fn3(list5);
...
}
五情龄、使用通配符的副作用(建議把前面的熟悉之后再看):
沒(méi)有銀彈迄汛,在使用通配符時(shí)捍壤,我們會(huì)根據(jù)對(duì)參數(shù)的不同操作采用不同的?與 extends鞍爱、super的組合鹃觉,一般原則稱為:PECS(Producer Extends Customer Super),案例如下:
1. Producer Extends :
public static void fn2(List<? extends Number> list){
/**
* 進(jìn)行消費(fèi)類型的操作(傳入對(duì)象)硬霍,是不安全的
* 由于使用extends帜慢,代表著"參數(shù)化類型"有上限,而無(wú)下限唯卖,
* 此時(shí)可以使用ArrayList<BigDecimal> 類型的實(shí)參粱玲,從而導(dǎo)致運(yùn)行時(shí)的異常,編譯器為預(yù)期的錯(cuò)誤而禁止
*/
// list.add(100); //針對(duì)任何類型的參數(shù)拜轨,消費(fèi)類型操作都無(wú)法進(jìn)行抽减,這是希望看到的結(jié)果
/**
* 進(jìn)行生產(chǎn)類型的操作(產(chǎn)生該類型的對(duì)象),將是大體上安全的
* 如果獲取的引用聲明是Number或父類型橄碾,是絕安全的
* 如果是Number的子類型,需要做向下強(qiáng)轉(zhuǎn)卵沉,但不一定安全
*/
Number m1=list.get(0);
Integer m2=(Integer) list.get(0);
Object o1=list.get(0);//多態(tài)引用:Object ref到Number,沒(méi)有問(wèn)題
/**
* String并不在Number的繼承樹(shù)之下,這會(huì)在編譯期中被檢查出來(lái)法牲,而無(wú)法通過(guò)
* 這也是使用extends的原動(dòng)力:即:Producer use Extends==PE
*/
//String s1=(String)list.get(0); 編譯無(wú)法通過(guò)史汗,是希望看到的結(jié)果
}
public static void main(String[] args) {
ArrayList<BigDecimal> list1=new ArrayList<>();
fn2(list1);//此時(shí)fn2中如果使用list.add(100),將發(fā)生錯(cuò)誤,所以編譯器不允許通過(guò)
...
}
2. Producer use Extends
public static void fn3(List<? super Number> list){
//傳入的list可以是List<Number的祖先們>,所以無(wú)法直接由編譯生成強(qiáng)轉(zhuǎn)的代碼拒垃,只能硬編碼
// Number number= list.get(0);
Number number=(Number)list.get(0);//硬編碼強(qiáng)轉(zhuǎn)停撞,提醒我們不應(yīng)該在Producer中使用 super
/**
* 此時(shí)可以確認(rèn)"參數(shù)化類型的下限是Number",此時(shí)傳入fn3的list只能是:"IS LIST && ?是Number的祖先",
* 形如:ArrayList<Object> 這樣的類型悼瓮,
* 此時(shí)如下的操作都是安全的戈毒,
*/
list.add(100);
list.add(100.0);
Number n=new Float(2.4);
list.add(n);
//Number的父類型,只能強(qiáng)轉(zhuǎn)横堡,提醒我們安全的消費(fèi)類型是Number
list.add((Number) new Object());
/**
* 由于String 并不是Number的祖先埋市,所以編譯器直接檢出錯(cuò)誤,
* 即: Consumer use Super
*/
// list.add("abc"); 這是我們希望看到的結(jié)果
}
public static void main(String[] args) {
ArrayList<Number> list1=new ArrayList<>();
fn3(list1);
ArrayList<Object> list2=new ArrayList<>();
fn3(list2);