摘要: 只有掌握了Java的高級特性,這門語言纔算真正地登堂入室。本文將帶領你們一同瞭解Java語言的三個經常使用的高級特性——泛型、反射和註解。java
本次直播視頻精彩回顧,戳這裏!編程
專家簡介:
澳明,阿里巴巴高級開發工程師,來自於阿里巴巴研發效能事業部-研發平臺-代碼智能化團隊。jvm
如下內容根據演講嘉賓視頻分享以及PPT整理而成。ide
本次的分享主要圍繞如下三個方面:性能
1、泛型介紹
2、反射機制
3、註解的使用spa
在平常編程的過程當中,泛型在這三個特性之中使用頻率是最高的。」泛型」一詞中的泛字能夠理解爲泛化的意思,即由具體的、個別的擴大爲通常的。Oracle對泛型的官方定義是:泛型類型是經過類型參數化的泛型類或接口。一言以蔽之,泛型就是經過類型參數化,來解決程序的通用性設計和實現的若干問題。
Java泛型是1.5版本後引入的特性,它主要被用於解決三類問題:
一、編譯器類型檢查設計
例如上圖中的實例1設計了一個簡單的Box類,在其中定義了一個private的object的屬性,同時定義了get()和set()兩個行爲,其中set()用於保存object到Box內,set()用於獲取Box中的object對象。從抽象的角度看,Box類抽象了一個用於在盒子中存放物品對象和存取的行爲,存取的方法接受或者返回Object類型的對象。在這個抽象的基礎上,能夠存放除原始類型外任意類型的對象,Object類型的聲明體現了面向對象中繼承的理念。
在實例2中,實現了不一樣業務場景下對Box的使用方式。其中列舉了兩種不一樣的業務場景,場景一須要在Box中存放String類型的對象,場景二須要在Box中存放Integer類型的對象,這種狀況下,在實際開發時,場景二中頗有可能會錯誤地傳入一個String對象,致使運行時錯誤的發生,而這正是由於Box能夠被只有傳入任意類型的對象致使的,這種狀況在集合類操做時尤其突出。例如實例3中的狀況:視頻
首先聲明瞭一個List類型的boxes對象,其中存放了兩個對象,一個是String類型的「aaaaa」,另外一個是Integer類型的11111。在業務場景一下,使用者認爲boxes中存放的全部對象都是String類型的,所以在取出第二個對象並進行類型轉換的時候就發生了錯誤。這種狀況每每讓使用者十分迷惑,明明編譯時沒有問題,可是在運行時卻產生了異常。也就是說,在這種面向對象的抽象過程當中,沒法經過編譯來驗證類型該如何進行使用。對象
那麼泛型是如何解決這類問題的呢?繼承
Oracle意識到了上述的問題,在引入泛型以後,經過將代碼中的「public class Box」更改成「public class Box<T>」來建立泛型類型的聲明,而這個聲明的背後實質上是引入了能夠在類中任何地方使用的類型變量T。如實例4中所示:能夠看到,除了新增的泛型類型聲明<T>外,全部在原來代碼中出現的Object都被類型變量T所替換。
乍一看類型變量這個詞,感受有點晦澀難懂,但其實若是仔細思量一番會發現它其實並不難理解,上面的實例4能夠理解爲「在使用泛型時,能夠將類型參數T傳遞給Box類型自己」,結合Oracle給出的官方定義「泛型的本質是類型參數化」會有更深的理解。
在實例5中,在對象聲明和初始化的時候,都指定了類型參數T,在場景一種,T爲String;在場景二中,T爲Integer。這樣,在場景二中向IntegerBox中傳入String類型的數據「aaaaa」時,程序會報錯。實例6中的泛型集合對象的操做也與之相似,在聲明瞭一個List<String>的boxes對象以後,若是向boxes中傳入Integer對象11111,程序會報錯。
能夠看到,經過對於泛型的使用,以前的多業務場景中的問題都獲得瞭解決,由於如今在編譯階段就能夠解決以前類型不匹配的問題,而不用等到運行時才暴露問題,只要合理使用泛型,就能在很大程度上規避此類風險。對於泛型的使用,這種參數化類型的做用表面上看是聲明,背後實際上是約定。
二、強制類型轉換
再回顧一下實例3,在List類型的boxes對象中存放了兩個對象,分別是String類型的「aaaaa」和Integer類型的11111。其中存在一個問題,在對於boxes的聲明中,使用者不知道boxes的list中到底應該存放什麼類型的對象,而編譯器也不知道集合存放的數據類型,只能經過實際的業務場景來決定這個box是什麼類型,採用將Object強制轉換成String的方式,來達到業務要求的效果。
在使用泛型以後,解決了這種場景下必須進行強制類型轉換的問題。如實例7中,經過泛型聲明,指定集合內元素的類型參數爲String類型,這樣編譯器就直接知曉了元素的類型,而無需依靠實際的業務邏輯進行轉換,從而解決了這類類型強制轉換的問題。
三、可讀性和靈活性
泛型除了能進行編譯器類型檢查和規避類型強制轉換外,還能有效地提升代碼的可讀性。對於實例3,若是不使用泛型,當一個不清楚業務場景的人在對集合進行操做時,沒法知道list中存儲的是什麼類型的對象,若是使用了泛型,就可以經過其類型參數判斷出當前的業務場景,也增長了代碼的可讀性,同時也能夠大膽地在抽象繼承的基礎上進行開發了。
泛型使用上的靈活性體如今不少方面,由於它自己實質上就是對於繼承在使用上的一種加強。由於泛型在具體工做時,當編譯器在編譯源碼的時候,首先要進行泛型類型參數的檢查,檢查出類型不匹配等問題,而後進行類型擦除並同時在類型參數出現的位置插入強制轉換指令,從而實現泛型。
除了上述的基礎用法以外,泛型還有幾種特殊的高階用法:
通配符的設計存在必定的場景,例如在使用泛型後,首先聲明瞭一個Animal的類,然後聲明瞭一個繼承自Animal類的Cat類,顯然Cat類是Animal類的子類,可是List<Cat>卻不是List<Animal>的子類型,而在程序中每每須要表達這樣的邏輯關係。爲了解決這種相似的場景,在泛型的參數類型的基礎上新增了通配符的用法,具體來講有三種用法:<? extends T>、<? super T>、<?>。其中前二者被稱爲限定通配符,<?>被稱爲非限定通配符。
一、<? extends T> 上界通配符
上界通配符顧名思義,<? extends T>表示的是類型的上界(包含自身),所以通配的參數化類型多是T或T的子類。正由於沒法肯定具體的類型是什麼,add方法受限(能夠添加null,由於null表示任何類型),但能夠從列表中獲取元素後賦值給父類型。如上圖中的第一個例子,第三個add()操做會受限,緣由在於List<Animal>和List<Cat>是List<? extends Animal>的子類型。
二、<? super T> 下界通配符
下界通配符<? super T>表示的是參數化類型是T的超類型(包含自身),層層至上,直至Object,編譯器無從判斷get()返回的對象的類型是什麼,所以get()方法受限。可是能夠進行add()方法,add()方法能夠添加T類型和T類型的子類型,如第二個例子中首先添加了一個Cat類型對象,而後添加了兩個Cat子類類型的對象,這種方法是可行的,可是若是添加一個Animal類型的對象,顯然將繼承的關係弄反了,是不可行的。
三、<?> 無界通配符
在理解了上界通配符和下界通配符以後,其實也天然而然的理解了無界通配符。無界通配符用<?>表示,?表明了任何的一種類型,能表明任何一種類型的只有null(Object自己也算是一種類型,但卻不能表明任何一種類型,因此List<Object>和List<null>的含義是不一樣的,前者類型是Object,也就是繼承樹的最上層,然後者的類型徹底是未知的)。
反射是Java語言自己具有的一個重要的動態機制。用一句話來解釋反射的定義:自控制,自描述。即經過反射能夠動態的獲取類、屬性、方法的信息,也能構造對象並控制對象的屬性和行爲。
上圖中有一個Apple類,它有兩個構造器、一個屬性和get()、set()兩個行爲。在左側的「自描述」中主要是嘗試在動態的過程當中藉助反射獲取Apple類的構造器信息和對應的參數個數、類的屬性信息和類的方法信息。其中有一個Class類型,它能夠產生Class對象被ClassLoader加載,從而在jvm中實現對它的調用。在這段程序中,打印了一些類的信息、類的屬性信息和類的方法信息。在右側的「自控制」的代碼中,實現了在運行的過程當中建立了一些對象並觸發這個對象的一些行爲,最後還嘗試對對象的屬性進行賦值。反射的基本使用方法較爲簡單,可是這種機制卻加強了Java語言的靈活性。
如上圖所示,非反射的Java類的大體運行流程是:編寫源文件Apple.java,而後編譯器將其編譯成字節碼文件Apple.class,最後加載到jvm中並運行。而採用反射的方式時,編譯器一開始對其類型(編譯類型和動態類型)是一無所知的,只有在運行事後,編譯器才知道其真正的類型。
反射的優點主要在於兩點:一、在一些場景中,這種「未知類型」實際上大大加強了程序運行時的靈活性,可是其性能會有一些損耗;二、對於對象的類型能夠在運行時判斷,這樣的特性實質上是對多態極大地加強,進一步地將上層的抽象與下層的具體實現進行解耦。
這兩點在JDBC Driver中體現的很是明顯,例如上圖中的實例中,JDBC的驅動加載方式是經過反射機制實現的,從而保證運行時能夠動態選擇要加載的驅動程序,程序靈活性大大加強。另外,JDBC只是設計了驅動須要實現的接口,並不關心驅動廠商的個數和實現方式,只要安裝統一的規範便可,至於類型的判斷和具體方法的觸發,交給運行期動態判斷便可,這種反射機制的使用淋漓盡致的體現了多態,而且下降了類與類之間的耦合度。
註解是在1.5版本引入的,如今已經成爲平常程序開發中很是重要的一部分。註解是一種元數據,自己沒有任何做用,若是要有,必須依附在具體的對象上,在平常使用中最多見的兩個註解是@Override和@Deprecated。
先不考慮註解具體的概念、用法和如何工做等問題,註解與「標籤」的概念十分類似,@Override能夠理解爲在方法上添加了一個標籤,其表明的就是「這是一個繼承關係中,子類已經重寫的方法。」更進一步理解,這個標籤在某個方法上加上以後,若是父類中沒有該方法,那麼在編譯的時候就會報錯,並且能夠解決在繼承場景下一些不留心將方法名拼錯的狀況,同時加強了一些程序的可讀性。
如上圖所示,一樣以@Override爲例,對註解進行進一步的提取和抽象。具體抽象出了四個方面:首先在做用域方面,它只能做用於子類重寫的方法上;其次在生命週期方面,註解只是在編譯時進行檢查,在編譯結束後便沒有了任何做用;除此以外,在文檔支持方面,爲例解決可讀性的問題,設計了@Documented的註解,用來表示註解的說明註釋是否包含在JavaDoc中;在層級結構設計方面,設計了@inherited用來表示註解是否能夠被子類繼承。
在上圖中定義了一個蘋果描述註解,包含了@Target、@Retention、@Inherited和@Documented四個註解,表示它生命週期是程序運行的聲明週期、能夠被子類繼承、文檔能夠被包含。在設計出這個註解以後,能夠將其用在前文中的Apple實例上,如圖中在類和方法上各添加了一個註解,在添加完後,即可以配合反射看到註解的效果,這樣能夠更好的增強其自描述的能力和配置的靈活性。
本文做者:汪星人1997
本文爲雲棲社區原創內容,未經容許不得轉載。