Java基礎(chǔ)知識(shí)(7)-- 泛型、注解和Java9新特性

一、泛型

泛型在java中有很重要的地位,在面向?qū)ο缶幊碳案鞣N設(shè)計(jì)模式中有非常廣泛的應(yīng)用。

什么是泛型?為什么要使用泛型?

首先泛型的本質(zhì)便是類(lèi)型參數(shù)化,通俗的說(shuō)就是用一個(gè)變量來(lái)表示類(lèi)型,這個(gè)類(lèi)型可以是String,Integer等等不確定,表明可接受的類(lèi)型。一提到參數(shù),最熟悉的就是定義方法時(shí)有形參,然后調(diào)用此方法時(shí)傳遞實(shí)參。那么參數(shù)化類(lèi)型怎么理解呢?顧名思義,就是將類(lèi)型由原來(lái)的具體的類(lèi)型參數(shù)化,類(lèi)似于方法中的變量參數(shù),此時(shí)類(lèi)型也定義成參數(shù)形式(可以稱(chēng)之為類(lèi)型形參),然后在使用/調(diào)用時(shí)傳入具體的類(lèi)型(類(lèi)型實(shí)參)。

泛型的本質(zhì)是為了參數(shù)化類(lèi)型(在不創(chuàng)建新的類(lèi)型的情況下,通過(guò)泛型指定的不同類(lèi)型來(lái)控制形參具體限制的類(lèi)型)。也就是說(shuō)在泛型使用過(guò)程中,操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù),這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法中,分別被稱(chēng)為泛型類(lèi)、泛型接口、泛型方法。

泛型類(lèi)形式如下:

classTest

{

??? private T t;

??? Test(T t)

??? {

??????? this.t = t;

??? }

??? public?T getT()

??? {

??????? return t;

??? }


??? public void setT(T t)

??? {

??????? this.t = t;

??? }

}


泛型方法舉例代碼如下:

public void show()

{

??? operation about T...

}

泛型參數(shù)類(lèi)型聲明必須在返回類(lèi)型之前。


為何要引入泛型,即泛型與Object的優(yōu)勢(shì):

由于泛型可以接受多個(gè)參數(shù),而Object經(jīng)過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換可以轉(zhuǎn)換為任何類(lèi)型,既然二者都具有相同的作用,為何還要引進(jìn)泛型呢?

解答:泛型可以把使用Object的錯(cuò)誤提前到編譯后,而不是運(yùn)行后,提升安全性。以下用帶泛型的ArrayList和不帶泛型的Arraylist舉例說(shuō)明。

ArrayList al =new ArrayList();

al.add("hello");

al.add(4);//自動(dòng)裝箱

String s1 =(String)al.get(0);

String s2 =

(String)al.get(1);//在編譯時(shí)沒(méi)問(wèn)題,但在運(yùn)行時(shí)出現(xiàn)問(wèn)題

首先聲明無(wú)泛型的ArrayList時(shí),其默認(rèn)的原始類(lèi)型是Object數(shù)組,既然為Object類(lèi)型,就可以接受任意數(shù)據(jù)的賦值,因此編譯時(shí)沒(méi)有問(wèn)題,但是在運(yùn)行時(shí),Integer強(qiáng)轉(zhuǎn)成String,肯定會(huì)出現(xiàn)ClassCastException.因此泛型的引入增強(qiáng)了安全性,把類(lèi)轉(zhuǎn)換異常提前到了編譯時(shí)期。


類(lèi)型擦除和原始類(lèi)型

類(lèi)型擦除的由來(lái)

在JAVA的虛擬機(jī)中并不存在泛型,泛型只是為了完善java體系,增加程序員編程的便捷性以及安全性而創(chuàng)建的一種機(jī)制,在JAVA虛擬機(jī)中對(duì)應(yīng)泛型的都是確定的類(lèi)型,在編寫(xiě)泛型代碼后,java虛擬機(jī)會(huì)把這些泛型參數(shù)類(lèi)型都擦除,用相應(yīng)的確定類(lèi)型來(lái)代替,代替的這一動(dòng)作叫做類(lèi)型擦除,而用于替代的類(lèi)型稱(chēng)為原始類(lèi)型,在類(lèi)型擦除過(guò)程中,一般使用第一個(gè)限定的類(lèi)型來(lái)替換,若無(wú)限定則使用Object。

對(duì)泛型類(lèi)的翻譯:

泛型類(lèi)(不帶泛型限定)代碼:

class Test

{

??? private T t;

??? public void show(T t)

??? {


??? }

}

虛擬機(jī)進(jìn)行翻譯后的原始類(lèi)型:

class Test

{

??? private Object t;

??? public void show(Objectt)

??? {


??? }

}


泛型類(lèi)(帶泛型限定)代碼:

class Test

{

??? private T t;

??? public void show(T t)

??? {

? ? }

}

虛擬機(jī)進(jìn)行翻譯后的原始類(lèi)型:

class Test

{

??? private Comparable t;

??? public voidshow(Comparable t)

??? {

? ? }

}


泛型方法的翻譯:

class Test

{

??? private T t;

??? public void show(T t)

??? {

? ? }

}


class TestDemo extends Test

{

??? private String t;

??? public void show(Stringt)

??? {

? ? }

}

由于TestDemo繼承Test<String>,但是Test在類(lèi)型擦除后還有一個(gè)public void Show(Object t),這和那個(gè)show(String t)出現(xiàn)重載,但是本意卻是沒(méi)有show(Object t)的,

因此在虛擬機(jī)翻譯泛型方法中,引入了橋方法,即在類(lèi)型擦除后的show(Object t)中調(diào)用另一個(gè)方法,代碼如下:

public void show(Object t)

{

??? show((String) t);

}


泛型限定

泛型限定是通過(guò)?(通配符)來(lái)實(shí)現(xiàn)的,表示可以接受任意類(lèi)型,那一定有人有疑問(wèn),那?和T(二者單獨(dú)使用時(shí))有啥區(qū)別了,其實(shí)區(qū)別也不是很大,僅僅在對(duì)參數(shù)類(lèi)型的使用上。

例如:

public void print(ArrayList al)

{

??? Iterator it =al.iterator();

??? while(it.hasNext())

??? {

???????System.out.println(in.next());

??? }

}


public void print(ArrayList al)

{

??? Iterator it =al.iterator();

??? while(it.hasNext())

??? {

??????? T t = it.next();? //區(qū)別就在此處,T可以作為類(lèi)型來(lái)使用,而??jī)H能作為接收任意類(lèi)型

???????System.out.println(t);

??? }

}


? extends SomeClass? 這種限定,說(shuō)明的是只能接收SomeClass及其子類(lèi)類(lèi)型,所謂的“上限”

? super SomeClass 這種限定,說(shuō)明只能接收SomeClass及其父類(lèi)類(lèi)型,所謂的“下限”

以下舉例? extends SomeClass說(shuō)明一下這類(lèi)限定的一種應(yīng)用方式:

由于泛型參數(shù)類(lèi)型可以表示任意類(lèi)型的類(lèi)類(lèi)型,若T要引用compareTo方法,如何保證在T類(lèi)中定義了compareTo方法呢?利用如下代碼:

public shwo(T a, T b)

{

??? int num =a.compareTo(b);

}

此處用于限定T類(lèi)型繼承自Comparable,因此T類(lèi)型可以調(diào)用compareTo方法。


可以有多個(gè)類(lèi)型限定,例如:

這種書(shū)寫(xiě)方式為何把Comparable寫(xiě)在前邊?因?yàn)轭?lèi)中存在Comparable的泛型方法,由于類(lèi)型擦除的問(wèn)題,原始類(lèi)型是由Comparable替換的,避免調(diào)用方法時(shí)類(lèi)型的強(qiáng)制轉(zhuǎn)換,提高效率。

class Test

{

??? private T lower;

??? private T upper;

? ? public Test(T first, Tsecond) //此處是利用Comparable的方法,因此把Comparable寫(xiě)在前邊,類(lèi)型擦除后為Comparable,若為Serializable,還得用強(qiáng)制類(lèi)型轉(zhuǎn)換,否則不能使用compareTo方法。

??? {

??????? int a =first.compareTo(second);

??????? ...

??? }

}


要澄清一個(gè)概念,還是借用我們上面定義的Box類(lèi),假設(shè)我們添加一個(gè)這樣的方法:

public void boxTest(Box n) { /* ... */ }

那么現(xiàn)在Box<Number> n允許接受什么類(lèi)型的參數(shù)?我們是否能夠傳入Box<Integer>或者Box<Double>呢?答案是否定的,雖然Integer和Double是Number的子類(lèi),但是在泛型中Box<Integer>或者Box<Double>與Box<Number>之間并沒(méi)有任何的關(guān)系。


PECS原則

上面我們看到了類(lèi)似<? extends T>的用法,利用它我們可以從list里面get元素,那么我們可不可以往list里面add元素呢?我們來(lái)嘗試一下:

public class GenericsAndCovariance {

??? public static voidmain(String[] args) {

??????? // Wildcards allowcovariance:

??????? List flist = new ArrayList();

??????? // Compile Error:can't add any type of object:

??????? // flist.add(newApple())

??????? // flist.add(newOrange())

??????? // flist.add(newFruit())

????? ??// flist.add(new Object())

??????? flist.add(null); //Legal but uninteresting

??????? // We Know that itreturns at least Fruit:

??????? Fruit f = flist.get(0);

??? }

}

答案是否定,Java編譯器不允許我們這樣做,為什么呢?對(duì)于這個(gè)問(wèn)題我們不妨從編譯器的角度去考慮。因?yàn)長(zhǎng)ist<? extends Fruit> flist它自身可以有多種含義:

List flist = new ArrayList();

List flist = new ArrayList();

List flist = new ArrayList();

當(dāng)我們嘗試add一個(gè)Apple的時(shí)候,flist可能指向new ArrayList();

當(dāng)我們嘗試add一個(gè)Orange的時(shí)候,flist可能指向new ArrayList();

當(dāng)我們嘗試add一個(gè)Fruit的時(shí)候,這個(gè)Fruit可以是任何類(lèi)型的Fruit,而flist可能只想某種特定類(lèi)型的Fruit,編譯器無(wú)法識(shí)別所以會(huì)報(bào)錯(cuò)。


所以對(duì)于實(shí)現(xiàn)了<? extends T>的集合類(lèi)只能將它視為Producer向外提供(get)元素,而不能作為Consumer來(lái)對(duì)外獲取(add)元素。

如果我們要add元素應(yīng)該怎么做呢?可以使用<? super T>:

public classGenericWriting {

??? static List apples = newArrayList();

??? staticList fruit = new ArrayList();

??? static voidwriteExact(List list, T item) {

??????? list.add(item);

??? }

??? static void f1() {

??????? writeExact(apples,new Apple());

??????? writeExact(fruit,new Apple());

??? }

??? static voidwriteWithWildcard(List list, T item) {

??????? list.add(item)

??? }

??? static void f2() {

??????? writeWithWildcard(apples,new Apple());

???????writeWithWildcard(fruit, new Apple());

??? }

??? public static voidmain(String[] args) {

??????? f1(); f2();

??? }

}

這樣我們可以往容器里面添加元素了,但是使用super的壞處是以后不能get容器里面的元素了,原因很簡(jiǎn)單,我們繼續(xù)從編譯器的角度考慮這個(gè)問(wèn)題,對(duì)于List<? super Apple> list,它可以有下面幾種含義:

List list = new ArrayList();

List list = new ArrayList();

List list = new ArrayList();

當(dāng)我們嘗試通過(guò)list來(lái)get一個(gè)Apple的時(shí)候,可能會(huì)get得到一個(gè)Fruit,這個(gè)Fruit可以是Orange等其他類(lèi)型的Fruit。


根據(jù)上面的例子,我們可以總結(jié)出一條規(guī)律,”P(pán)roducer Extends, Consumer Super”:

“Producer

Extends” – 如果你需要一個(gè)只讀List,用它來(lái)produce T,那么使用? extends T。

“Consumer

Super” – 如果你需要一個(gè)只寫(xiě)List,用它來(lái)consume T,那么使用? super T。

如果需要同時(shí)讀取以及寫(xiě)入,那么我們就不能使用通配符了。


泛型面試題:

1. Java中的泛型是什么 ? 使用泛型的好處是什么?

  這是在各種Java泛型面試中,一開(kāi)場(chǎng)你就會(huì)被問(wèn)到的問(wèn)題中的一個(gè),主要集中在初級(jí)和中級(jí)面試中。那些擁有Java1.4或更早版本的開(kāi)發(fā)背景的人都知道,在集合中存儲(chǔ)對(duì)象并在使用前進(jìn)行類(lèi)型轉(zhuǎn)換是多么的不方便。泛型防止了那種情況的發(fā)生。它提供了編譯期的類(lèi)型安全,確保你只能把正確類(lèi)型的對(duì)象放入集合中,避免了在運(yùn)行時(shí)出現(xiàn)ClassCastException。


2. Java的泛型是如何工作的 ? 什么是類(lèi)型擦除?

  這是一道更好的泛型面試題。泛型是通過(guò)類(lèi)型擦除來(lái)實(shí)現(xiàn)的,編譯器在編譯時(shí)擦除了所有類(lèi)型相關(guān)的信息,所以在運(yùn)行時(shí)不存在任何類(lèi)型相關(guān)的信息。例如List<String>在運(yùn)行時(shí)僅用一個(gè)List來(lái)表示。這樣做的目的,是確保能和Java 5之前的版本開(kāi)發(fā)二進(jìn)制類(lèi)庫(kù)進(jìn)行兼容。你無(wú)法在運(yùn)行時(shí)訪問(wèn)到類(lèi)型參數(shù),因?yàn)榫幾g器已經(jīng)把泛型類(lèi)型轉(zhuǎn)換成了原始類(lèi)型。根據(jù)你對(duì)這個(gè)泛型問(wèn)題的回答情況,你會(huì)得到一些后續(xù)提問(wèn),比如為什么泛型是由類(lèi)型擦除來(lái)實(shí)現(xiàn)的或者給你展示一些會(huì)導(dǎo)致編譯器出錯(cuò)的錯(cuò)誤泛型代碼。請(qǐng)閱讀我的Java中泛型是如何工作的來(lái)了解更多信息。


3. 什么是泛型中的限定通配符和非限定通配符?

  這是另一個(gè)非常流行的Java泛型面試題。限定通配符對(duì)類(lèi)型進(jìn)行了限制。有兩種限定通配符,一種是<? extends T>它通過(guò)確保類(lèi)型必須是T的子類(lèi)來(lái)設(shè)定類(lèi)型的上界,另一種是<? super T>它通過(guò)確保類(lèi)型必須是T的父類(lèi)來(lái)設(shè)定類(lèi)型的下界。泛型類(lèi)型必須用限定內(nèi)的類(lèi)型來(lái)進(jìn)行初始化,否則會(huì)導(dǎo)致編譯錯(cuò)誤。另一方面<?>表示了非限定通配符,因?yàn)?lt;?>可以用任意類(lèi)型來(lái)替代。更多信息請(qǐng)參閱我的文章泛型中限定通配符和非限定通配符之間的區(qū)別。


4. List<?

extends T>和List <? super T>之間有什么區(qū)別?

  這和上一個(gè)面試題有聯(lián)系,有時(shí)面試官會(huì)用這個(gè)問(wèn)題來(lái)評(píng)估你對(duì)泛型的理解,而不是直接問(wèn)你什么是限定通配符和非限定通配符。這兩個(gè)List的聲明都是限定通配符的例子,List<? extends T>可以接受任何繼承自T的類(lèi)型的List,而List<? super

T>可以接受任何T的父類(lèi)構(gòu)成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出現(xiàn)的連接中可以找到更多信息。


5. 如何編寫(xiě)一個(gè)泛型方法,讓它能接受泛型參數(shù)并返回泛型類(lèi)型?

  編寫(xiě)泛型方法并不困難,你需要用泛型類(lèi)型來(lái)替代原始類(lèi)型,比如使用T, E or K,V等被廣泛認(rèn)可的類(lèi)型占位符。泛型方法的例子請(qǐng)參閱Java集合類(lèi)框架。最簡(jiǎn)單的情況下,一個(gè)泛型方法可能會(huì)像這樣:

????? public V put(K key, Vvalue) {

????????????? returncache.put(key, value);

????? }


6. Java中如何使用泛型編寫(xiě)帶有參數(shù)的類(lèi)?

  這是上一道面試題的延伸。面試官可能會(huì)要求你用泛型編寫(xiě)一個(gè)類(lèi)型安全的類(lèi),而不是編寫(xiě)一個(gè)泛型方法。關(guān)鍵仍然是使用泛型類(lèi)型來(lái)代替原始類(lèi)型,而且要使用JDK中采用的標(biāo)準(zhǔn)占位符。


7. 編寫(xiě)一段泛型程序來(lái)實(shí)現(xiàn)LRU緩存?

  對(duì)于喜歡Java編程的人來(lái)說(shuō)這相當(dāng)于是一次練習(xí)。給你個(gè)提示,LinkedHashMap可以用來(lái)實(shí)現(xiàn)固定大小的LRU緩存,當(dāng)LRU緩存已經(jīng)滿了的時(shí)候,它會(huì)把最老的鍵值對(duì)移出緩存。LinkedHashMap提供了一個(gè)稱(chēng)為removeEldestEntry()的方法,該方法會(huì)被put()和putAll()調(diào)用來(lái)刪除最老的鍵值對(duì)。當(dāng)然,如果你已經(jīng)編寫(xiě)了一個(gè)可運(yùn)行的JUnit測(cè)試,你也可以隨意編寫(xiě)你自己的實(shí)現(xiàn)代碼。


8. 你可以把List<String>傳遞給一個(gè)接受List<Object>參數(shù)的方法嗎?

  對(duì)任何一個(gè)不太熟悉泛型的人來(lái)說(shuō),這個(gè)Java泛型題目看起來(lái)令人疑惑,因?yàn)檎Э雌饋?lái)String是一種Object,所以List<String>應(yīng)當(dāng)可以用在需要List<Object>的地方,但是事實(shí)并非如此。真這樣做的話會(huì)導(dǎo)致編譯錯(cuò)誤。如果你再深一步考慮,你會(huì)發(fā)現(xiàn)Java這樣做是有意義的,因?yàn)長(zhǎng)ist<Object>可以存儲(chǔ)任何類(lèi)型的對(duì)象包括String, Integer等等,而List<String>卻只能用來(lái)存儲(chǔ)Strings。

?????? List objectList;

?????? ListstringList;

?????? objectList =stringList;? //compilation errorincompatible types


9. Array中可以用泛型嗎?

  這可能是Java泛型面試題中最簡(jiǎn)單的一個(gè)了,當(dāng)然前提是你要知道Array事實(shí)上并不支持泛型,這也是為什么Joshua Bloch在Effective Java一書(shū)中建議使用List來(lái)代替Array,因?yàn)長(zhǎng)ist可以提供編譯期的類(lèi)型安全保證,而Array卻不能。


10. 如何阻止Java中的類(lèi)型未檢查的警告?

  如果你把泛型和原始類(lèi)型混合起來(lái)使用,例如下列代碼,Java 5的javac編譯器會(huì)產(chǎn)生類(lèi)型未檢查的警告,例如

?????? ListrawList = new ArrayList()


二、注解

什么是注解????

對(duì)于很多初次接觸的開(kāi)發(fā)者來(lái)說(shuō)應(yīng)該都有這個(gè)疑問(wèn)?Annontation是Java5開(kāi)始引入的新特征,中文名稱(chēng)叫注解。它提供了一種安全的類(lèi)似注釋的機(jī)制,用來(lái)將任何的信息或元數(shù)據(jù)(metadata)與程序元素(類(lèi)、方法、成員變量等)進(jìn)行關(guān)聯(lián)。為程序的元素(類(lèi)、方法、成員變量)加上更直觀更明了的說(shuō)明,這些說(shuō)明信息是與程序的業(yè)務(wù)邏輯無(wú)關(guān),并且供指定的工具或框架使用。Annontation像一種修飾符一樣,應(yīng)用于包、類(lèi)型、構(gòu)造方法、方法、成員變量、參數(shù)及本地變量的聲明語(yǔ)句中。

  Java注解是附加在代碼中的一些元信息,用于一些工具在編譯、運(yùn)行時(shí)進(jìn)行解析和使用,起到說(shuō)明、配置的功能。注解不會(huì)也不能影響代碼的實(shí)際邏輯,僅僅起到輔助性的作用。包含在 java.lang.annotation 包中。

注解的用處:

????? 1、生成文檔。這是最常見(jiàn)的,也是java 最早提供的注解。常用的有@param @return 等

????? 2、跟蹤代碼依賴性,實(shí)現(xiàn)替代配置文件功能。比如Dagger 2依賴注入,未來(lái)java開(kāi)發(fā),將大量注解配置,具有很大用處;

????? 3、在編譯時(shí)進(jìn)行格式檢查。如@override 放在方法前,如果你這個(gè)方法并不是覆蓋了超類(lèi)方法,則編譯時(shí)就能檢查出。

注解的原理:

  注解本質(zhì)是一個(gè)繼承了Annotation的特殊接口,其具體實(shí)現(xiàn)類(lèi)是Java運(yùn)行時(shí)生成的動(dòng)態(tài)代理類(lèi)。而我們通過(guò)反射獲取注解時(shí),返回的是Java運(yùn)行時(shí)生成的動(dòng)態(tài)代理對(duì)象$Proxy1。通過(guò)代理對(duì)象調(diào)用自定義注解(接口)的方法,會(huì)最終調(diào)用AnnotationInvocationHandler的invoke方法。該方法會(huì)從memberValues這個(gè)Map中索引出對(duì)應(yīng)的值。而memberValues的來(lái)源是Java常量池。


元注解:

java.lang.annotation提供了四種元注解,專(zhuān)門(mén)注解其他的注解(在自定義注解的時(shí)候,需要使用到元注解):

?? @Documented –注解是否將包含在JavaDoc中

?? @Retention –什么時(shí)候使用該注解

?? @Target –注解用于什么地方

?? @Inherited –是否允許子類(lèi)繼承該注解


? 1.)@Retention– 定義該注解的生命周期

? ●?? RetentionPolicy.SOURCE :在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義,所以它們不會(huì)寫(xiě)入字節(jié)碼。@Override, @SuppressWarnings都屬于這類(lèi)注解。

? ●?? RetentionPolicy.CLASS :在類(lèi)加載的時(shí)候丟棄。在字節(jié)碼文件的處理中有用。注解默認(rèn)使用這種方式

? ●?? RetentionPolicy.RUNTIME :始終不會(huì)丟棄,運(yùn)行期也保留該注解,因此可以使用反射機(jī)制讀取該注解的信息。我們自定義的注解通常使用這種方式。


? 2.)Target – 表示該注解用于什么地方。默認(rèn)值為任何元素,表示該注解用于什么地方。可用的ElementType參數(shù)包括

? ● ElementType.CONSTRUCTOR:用于描述構(gòu)造器

? ● ElementType.FIELD:成員變量、對(duì)象、屬性(包括enum實(shí)例)

? ●ElementType.LOCAL_VARIABLE:用于描述局部變量

? ● ElementType.METHOD:用于描述方法

? ● ElementType.PACKAGE:用于描述包

? ● ElementType.PARAMETER:用于描述參數(shù)

? ● ElementType.TYPE:用于描述類(lèi)、接口(包括注解類(lèi)型) 或enum聲明


?3.)@Documented–一個(gè)簡(jiǎn)單的Annotations標(biāo)記注解,表示是否將注解信息添加在java文檔中。


?4.)@Inherited –定義該注釋和子類(lèi)的關(guān)系

???? @Inherited元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類(lèi)型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類(lèi)。


常見(jiàn)標(biāo)準(zhǔn)的Annotation

? 1.)Override

????? java.lang.Override是一個(gè)標(biāo)記類(lèi)型注解,它被用作標(biāo)注方法。它說(shuō)明了被標(biāo)注的方法重載了父類(lèi)的方法,起到了斷言的作用。如果我們使用了這種注解在一個(gè)沒(méi)有覆蓋父類(lèi)方法的方法時(shí),java編譯器將以一個(gè)編譯錯(cuò)誤來(lái)警示。

? 2.)Deprecated

???? Deprecated也是一種標(biāo)記類(lèi)型注解。當(dāng)一個(gè)類(lèi)型或者類(lèi)型成員使用@Deprecated修飾的話,編譯器將不鼓勵(lì)使用這個(gè)被標(biāo)注的程序元素。所以使用這種修飾具有一定的“延續(xù)性”:如果我們?cè)诖a中通過(guò)繼承或者覆蓋的方式使用了這個(gè)過(guò)時(shí)的類(lèi)型或者成員,雖然繼承或者覆蓋后的類(lèi)型或者成員并不是被聲明為@Deprecated,但編譯器仍然要報(bào)警。

?3.)SuppressWarnings

???? SuppressWarning不是一個(gè)標(biāo)記類(lèi)型注解。它有一個(gè)類(lèi)型為String[]的成員,這個(gè)成員的值為被禁止的警告名。對(duì)于javac編譯器來(lái)講,被-Xlint選項(xiàng)有效的警告名也同樣對(duì)@SuppressWarings有效,同時(shí)編譯器忽略掉無(wú)法識(shí)別的警告名。

  @SuppressWarnings("unchecked")


自定義注解:

自定義注解類(lèi)編寫(xiě)的一些規(guī)則:

? 1. Annotation型定義為@interface, 所有的Annotation會(huì)自動(dòng)繼承java.lang.Annotation這一接口,并且不能再去繼承別的類(lèi)或是接口.

? 2.參數(shù)成員只能用public或默認(rèn)(default)這兩個(gè)訪問(wèn)權(quán)修飾

? 3.參數(shù)成員只能用基本類(lèi)型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類(lèi)型和String、Enum、Class、annotations等數(shù)據(jù)類(lèi)型,以及這一些類(lèi)型的數(shù)組.

? 4.要獲取類(lèi)方法和字段的注解信息,必須通過(guò)Java的反射技術(shù)來(lái)獲取 Annotation對(duì)象,因?yàn)槟愠酥鉀](méi)有別的獲取注解對(duì)象的方法

? 5.注解也可以沒(méi)有定義成員, 不過(guò)這樣注解就沒(méi)啥用了

PS:自定義注解需要使用到元注解


自定義注解實(shí)例:

FruitName.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

?*水果名稱(chēng)注解

?*/

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitName {

??? String value() default"";

}


FruitColor.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

?*水果顏色注解

?*/

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitColor {

??? /**

???? *顏色枚舉

???? */

??? public enum Color{BLUE,RED,GREEN};

? ? /**

???? *顏色屬性

???? */

??? Color fruitColor()default Color.GREEN;

}


FruitProvider.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

?*水果供應(yīng)者注解

?*/

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitProvider {

??? /**

???? *供應(yīng)商編號(hào)

???? */

??? public int id() default-1;


??? /**

???? *供應(yīng)商名稱(chēng)

???? */

??? public String name()default "";


??? /**

???? *供應(yīng)商地址

???? */

??? public String address()default "";

}


FruitInfoUtil.java

import java.lang.reflect.Field;

/**

?*注解處理器

?*/

public class FruitInfoUtil {

??? public static voidgetFruitInfo(Class clazz){

? ? ? ? StringstrFruitName="水果名稱(chēng):";

??????? String strFruitColor="水果顏色:";

??????? StringstrFruitProvicer="供應(yīng)商信息:";

? ? ? ? Field[] fields =clazz.getDeclaredFields();


??????? for(Field field:fields){

???????????if(field.isAnnotationPresent(FruitName.class)){

??????????????? FruitNamefruitName = (FruitName) field.getAnnotation(FruitName.class);

???????????????strFruitName=strFruitName+fruitName.value();

???????????????System.out.println(strFruitName);

??????????? }

??????????? elseif(field.isAnnotationPresent(FruitColor.class)){

????????????? ??FruitColor fruitColor= (FruitColor)field.getAnnotation(FruitColor.class);

???????????????strFruitColor=strFruitColor+fruitColor.fruitColor().toString();

???????????????System.out.println(strFruitColor);

??????????? }

??????????? elseif(field.isAnnotationPresent(FruitProvider.class)){

???????????????FruitProvider fruitProvider= (FruitProvider)field.getAnnotation(FruitProvider.class);

???????????????strFruitProvicer="供應(yīng)商編號(hào):"+fruitProvider.id()+"供應(yīng)商名稱(chēng):"+fruitProvider.name()+" 供應(yīng)商地址:"+fruitProvider.address();

???????????????System.out.println(strFruitProvicer);

??????????? }

??????? }

??? }

}


Apple.java

import test.FruitColor.Color;

/**

?*注解使用

?*/

public class Apple {

? ?@FruitName("Apple")

??? private StringappleName;


??? @FruitColor(fruitColor=Color.RED)

??? private StringappleColor;


???@FruitProvider(id=1,name="陜西紅富士集團(tuán)",address="陜西省西安市延安路89號(hào)紅富士大廈")

??? private StringappleProvider;


??? public voidsetAppleColor(String appleColor) {

??????? this.appleColor =appleColor;

? ??}

??? public StringgetAppleColor() {

??????? return appleColor;

??? }


??? public voidsetAppleName(String appleName) {

??????? this.appleName =appleName;

??? }

??? public StringgetAppleName() {

??????? return appleName;

??? }


??? public voidsetAppleProvider(String appleProvider) {

??????? this.appleProvider =appleProvider;

??? }

??? public StringgetAppleProvider() {

??????? returnappleProvider;

??? }


??? public voiddisplayName(){

??????? System.out.println("水果的名字是:蘋(píng)果");

??? }

}


FruitRun.java

/**

?*輸出結(jié)果

?*/

public class FruitRun {

??? public static voidmain(String[] args) {

???????FruitInfoUtil.getFruitInfo(Apple.class);

??? }

}

運(yùn)行結(jié)果是:

?水果名稱(chēng):Apple

?水果顏色:RED

?供應(yīng)商編號(hào):1 供應(yīng)商名稱(chēng):陜西紅富士集團(tuán) 供應(yīng)商地址:陜西省西安市延安路89號(hào)紅富士大廈????


三、Java8和Java9新特性

1Java8新特性:

1)Lambda表達(dá)式與Functional接口

?? Lambda允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中),或者把代碼看成數(shù)據(jù)

?? lambda表達(dá)式完整語(yǔ)法:

?? (Type1 param1, Type2param2, ..., TypeN paramN) -> {

?????? statment1;

?????? statment2;

?????? //.............

?????? return statmentM;

??? }

??? 僅有一個(gè)參數(shù)時(shí)可省略小括號(hào),沒(méi)有參數(shù)可以使用空的小括號(hào),參數(shù)類(lèi)型可以根據(jù)上下文推斷的話可以省略參數(shù)類(lèi)型;

???Arrays.asList("a", "b", "d").forEach(e-> System.out.println(e));

??? 僅有一條語(yǔ)句時(shí)可省略大括號(hào)、return和語(yǔ)句結(jié)尾的分號(hào);

??? 可以使用方法引用:

??? stream.forEach(x ->System.out.println(x));可以替換為stream.forEach(System.out::println);


??? // JDK7匿名內(nèi)部類(lèi)寫(xiě)法

??? new Thread(newRunnable(){//接口名

??????? @Override

??????? public void run(){//方法名

???????????System.out.println("Thread run()");

??????? }

??? }).start();


??? // JDK8 Lambda表達(dá)式寫(xiě)法

??? new Thread(

??????????? () ->System.out.println("Thread run()")//省略接口名和方法名

??? ).start();


?? 函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。

?? 函數(shù)式接口可以被隱式轉(zhuǎn)換為lambda表達(dá)式。

?? 函數(shù)式接口可以現(xiàn)有的函數(shù)友好地支持lambda。


2)接口的默認(rèn)方法與靜態(tài)方法

?? Java 8用默認(rèn)方法與靜態(tài)方法這兩個(gè)新概念來(lái)擴(kuò)展接口的聲明。

?? 默認(rèn)方法與抽象方法不同之處在于抽象方法必須要求實(shí)現(xiàn),但是默認(rèn)方法則沒(méi)有這個(gè)要求。相反,每個(gè)接口都必須提供一個(gè)所謂的默認(rèn)實(shí)現(xiàn),這樣所有的接口實(shí)現(xiàn)者將會(huì)默認(rèn)繼承它(如果有必要的話,可以覆蓋這個(gè)默認(rèn)實(shí)現(xiàn))。

?? private interfaceDefaulable {

??????? // Interfaces nowallow default methods, the implementer may or

??????? // may not implement(override) them.

??????? default StringnotRequired() {

??????????? return"Default implementation";

??????? }???????

??? }


??? private static classDefaultableImpl implements Defaulable {

??? }


??? private static classOverridableImpl implements Defaulable {

??????? @Override

??????? public StringnotRequired() {

??????????? return"Overridden implementation";

??????? }

??? }


??? Java 8帶來(lái)的另一個(gè)有趣的特性是接口可以聲明(并且可以提供實(shí)現(xiàn))靜態(tài)方法。例如:

??? private interfaceDefaulableFactory {

??????? // Interfaces nowallow static methods

??????? static Defaulablecreate( Supplier< Defaulable > supplier ) {

??????????? returnsupplier.get();

??????? }

??? }

??? 盡管默認(rèn)方法非常強(qiáng)大,但是在使用默認(rèn)方法時(shí)我們需要小心注意一個(gè)地方:在聲明一個(gè)默認(rèn)方法前,請(qǐng)仔細(xì)思考是不是真的有必要使用默認(rèn)方法,因?yàn)槟J(rèn)方法會(huì)帶給程序歧義,并且在復(fù)雜的繼承體系中容易產(chǎn)生編譯錯(cuò)誤。


3)方法引用

?? 方法引用提供了非常有用的語(yǔ)法,可以直接引用已有Java類(lèi)或?qū)ο螅▽?shí)例)的方法或構(gòu)造器。與lambda聯(lián)合使用,方法引用可以使語(yǔ)言的構(gòu)造更緊湊簡(jiǎn)潔,減少冗余代碼。

?? public static class Car {

??????? public static Car create(final Supplier< Car > supplier ) {

??????????? returnsupplier.get();

??????? }?????????????


??????? public static voidcollide( final Car car ) {

???????????System.out.println( "Collided " + car.toString() );

??????? }


??????? public void follow(final Car another ) {

???????????System.out.println( "Following the " + another.toString() );

??????? }


??????? public void repair(){??

???????????System.out.println( "Repaired " + this.toString() );

??????? }

?? }

?? 第一種方法引用是構(gòu)造器引用,它的語(yǔ)法是Class::new,或者更一般的Class< T >::new。請(qǐng)注意構(gòu)造器沒(méi)有參數(shù)。

?? final Car car =Car.create( Car::new );

?? final List< Car >cars = Arrays.asList( car );

?? 第二種方法引用是靜態(tài)方法引用,它的語(yǔ)法是Class::static_method。請(qǐng)注意這個(gè)方法接受一個(gè)Car類(lèi)型的參數(shù)。

?? cars.forEach( Car::collide);

?? 第三種方法引用是特定類(lèi)的任意對(duì)象的方法引用,它的語(yǔ)法是Class::method。請(qǐng)注意,這個(gè)方法沒(méi)有參數(shù)。

?? cars.forEach( Car::repair);

?? 第四種方法引用是特定對(duì)象的方法引用,它的語(yǔ)法是instance::method。請(qǐng)注意,這個(gè)方法接受一個(gè)Car類(lèi)型的參數(shù)。

?? final Car police =Car.create( Car::new );

?? cars.forEach(police::follow );


4)重復(fù)注解

?? 自從Java 5引入了注解機(jī)制,這一特性就變得非常流行并且廣為使用。然而,使用注解的一個(gè)限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8打破了這條規(guī)則,引入了重復(fù)注解機(jī)制,這樣相同的注解可以在同一地方聲明多次。

?? 重復(fù)注解機(jī)制本身必須用@Repeatable注解。事實(shí)上,這并不是語(yǔ)言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。

?? public classRepeatingAnnotations {

??????? @Target(ElementType.TYPE )

??????? @Retention(RetentionPolicy.RUNTIME )

??????? public @interfaceFilters {

??????????? Filter[]value();

??????? }


??????? @Target(ElementType.TYPE )

??????? @Retention(RetentionPolicy.RUNTIME )

??????? @Repeatable(Filters.class )

??? ????public @interface Filter {

??????????? String value();

??????? };


??????? @Filter("filter1" )

??????? @Filter("filter2" )

??????? public interfaceFilterable {???????

??????? }


??????? public static voidmain(String[] args) {

?? ?????????for( Filter filter:Filterable.class.getAnnotationsByType( Filter.class ) ) {

???????????????System.out.println( filter.value() );

??????????? }

??????? }

?? }

?? 正如我們看到的,這里有個(gè)使用@Repeatable( Filters.class )注解的注解類(lèi)Filter,F(xiàn)ilters僅僅是Filter注解的數(shù)組,但Java編譯器并不想讓程序員意識(shí)到Filters的存在。這樣,接口Filterable就擁有了兩次Filter(并沒(méi)有提到Filter)注解。


5)更好的類(lèi)型推測(cè)機(jī)制

?? Java 8在類(lèi)型推測(cè)方面有了很大的提高。在很多情況下,編譯器可以推測(cè)出確定的參數(shù)類(lèi)型,這樣就能使代碼更整潔。


6)擴(kuò)展注解的支持

?? Java 8擴(kuò)展了注解的上下文。現(xiàn)在幾乎可以為任何東西添加注解:局部變量、泛型類(lèi)、父類(lèi)與接口的實(shí)現(xiàn),就連方法的異常也能添加注解。

?

2Java9新特性

1)集合工廠方法

????? Setints = Set.of(1, 2, 3);

????? Liststrings = List.of("first", "second");


2)改進(jìn)的Stream API

?? Stream接口中添加了 4 個(gè)新的方法:dropWhile, takeWhile, ofNullable。還有個(gè) iterate 方法的新重載方法,可以讓你提供一個(gè) Predicate (判斷條件)來(lái)指定什么時(shí)候結(jié)束迭代:

IntStream.iterate(1, i -> i < 100, i -> i +1).forEach(System.out::println);

?? 第二個(gè)參數(shù)是一個(gè) Lambda,它會(huì)在當(dāng)前 IntStream 中的元素到達(dá) 100 的時(shí)候返回 true。因此這個(gè)簡(jiǎn)單的示例是向控制臺(tái)打印 1 到 99。


3)私有接口方法

?? 如果在接口上有幾個(gè)默認(rèn)方法,代碼幾乎相同,會(huì)發(fā)生什么情況? 通常,您將重構(gòu)這些方法,調(diào)用一個(gè)可復(fù)用的私有方法。 但默認(rèn)方法不能是私有的。 將復(fù)用代碼創(chuàng)建為一個(gè)默認(rèn)方法不是一個(gè)解決方案,因?yàn)樵撦o助方法會(huì)成為公共API的一部分。使用 Java 9,您可以向接口添加私有輔助方法來(lái)解決此問(wèn)題:

????? publicinterface MyInterface {

????? voidnormalInterfaceMethod();

????? default voidinterfaceMethodWithDefault() {? init(); }

????? default voidanotherDefaultMethod() { init(); }

????? // This method is notpart of the public API exposed by MyInterface

????? private void init() {System.out.println("Initializing"); }

????? }

如果您使用默認(rèn)方法開(kāi)發(fā) API ,那么私有接口方法可能有助于構(gòu)建其實(shí)現(xiàn)。



參考書(shū)目:《Java編程思想》、《Java核心技術(shù)卷一》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容