在Java8以前,在某個類或者方法,字段或者參數上標註註解時,同一個註解只能標註一次。可是在Java8中,新增了重複註解和類型註解,也就是說,從Java8開始,支持在某個類或者方法,字段或者參數上標註多個相同的註解。那麼,有讀者就會問了:如何實現呢?別急,往下看!文中不僅是Java8中的註解。java
註解就至關於一種標記,在程序中加了註解就等於爲程序加了某種標記。(JDK1.5新特性)。程序員
告訴javac編譯器或者java開發工具……向其傳遞某種信息,做爲一個標記。數組
一個註解就是一個類。bash
標記能夠加在包、類、字段、方法,方法參數以及局部變量上。能夠同時存在多個註解。微信
每個註解結尾都沒有「;」或者其餘特別符號。ide
定義註解須要的基礎註解信息以下所示。工具
@SuppressWarnings("deprecation") //編譯器警告過期(source階段) @Deprecated //過期(Runtime階段) @Override //重寫(source階段) @Retention(RetentionPolicy.RUNTIME) //保留註解到程序運行時。(Runtime階段) @Target({ElementType.METHOD,ElementType.TYPE}) //標記既能定義在方法上,又能定義在類、接口、枚舉上等。
注意:學習
1)添加註解須要有註解類。RetentionPolicy是一個枚舉類(有三個成員)。開發工具
2)Target中能夠存放數組。它的默認值爲任何元素。測試
3)ElementType也是枚舉類。成員包括:ANNOTATION_TYPE(註解)、CONSTRUCTOR(構造方法)、FIEID(成員變量)、LOCAL_VARIABLE(變量)、METHOD(方法)、PACKAGE(包)、PARAMETER(參數)、TYPE。
java源文件--> class文件 --> 內存中的字節碼。
Retention的註解有三種取值:(分別對應註解的三個階段)
注意:註解的默認階段是Class。
原始類型(就是八個基本數據類型)、String類型、Class類型、數組類型、枚舉類型、註解類型。
value:是一個特殊的屬性,若在設置值時只有一個value屬性須要設置或者其餘屬性都採用默認值時 ,那麼value=能夠省略,直接寫所設置的值便可。
例如:@SuppressWarnings("deprecation") 爲屬性指定缺省值(默認值): 例如:String value() default "blue"; //定義在註解類中 數組類型的屬性: 例如:int[] arrayArr() default {3,4,5,5};//定義在註解類中 SunAnnotation(arrayArr={3,9,8}) //設置數組值 注意:若是數組屬性中只有一個元素時,屬性值部分能夠省略大括號。 例如:SunAnnotation(arrayArr=9) 枚舉類型的屬性: 例如:EnumDemo.TrafficLamp lamp() ////枚舉類型屬性, 定義在註解類中,這裏使用了自定義的枚舉類EnumDemo.java並無給出相關代碼,這裏只是舉個例子 default EnumDemo.TrafficLamp.RED; 註解類型的屬性: 例如:MetaAnnotation annotationAttr() //定義在一個註解類中,並指定缺省值, //此屬性關聯到註解類:MetaAnnotation.java, default @MetaAnnotation("lhm"); //設置註解屬性值 @SunAnnotation(annotationAttr=@MetaAnnotation("flx"))
對於註解(也被稱作元數據),Java 8 主要有兩點改進:類型註解和重複註解。
1)Java 8 的類型註解擴展了註解使用的範圍。
在java 8以前,註解只能是在聲明的地方所使用,java8開始,註解能夠應用在任何地方。
例如:
建立類實例
new @Interned MyObject();
類型映射
myString = (@NonNull String) str;
implements 語句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }
throw exception聲明
void monitorTemperature() throws@Critical TemperatureException { ... }
注意:
在Java 8裏面,當類型轉化甚至分配新對象的時候,均可以在聲明變量或者參數的時候使用註解。
Java註解能夠支持任意類型。
類型註解只是語法而不是語義,並不會影響java的編譯時間,加載時間,以及運行時間,也就是說,編譯成class文件的時候並不包含類型註解。
2)新增ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER(在Target上)
新增的兩個註釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用來描述註解的新場合。
例如,下面的示例。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}
3)類型註解的做用
類型註解被用來支持在Java的程序中作強類型檢查。配合第三方插件工具Checker Framework(注:此插件so easy,這裏不介紹了),能夠在編譯的時候檢測出runtime error(例如:UnsupportedOperationException; NumberFormatException;NullPointerException異常等都是runtime error),以提升代碼質量。這就是類型註解的做用。
注意:使用Checker Framework能夠找到類型註解出現的地方並檢查。
例以下面的代碼。
import checkers.nullness.quals.*; public class TestDemo{ void sample() { @NonNull Object my = new Object(); } }
使用javac編譯上面的類:(固然若下載了Checker Framework插件就不須要這麼麻煩了)
javac -processor checkers.nullness.NullnessChecker TestDemo.java
上面編譯是經過的,但若修改代碼:
@NonNull Object my = null;
但若不想使用類型註解檢測出來錯誤,則不須要processor,正常javac TestDemo.java是能夠經過編譯的,可是運行時會報 NullPointerException 異常。
爲了能在編譯期間就自動檢查出這類異常,能夠經過類型註解結合 Checker Framework 提早排查出來錯誤異常。
注意java 5,6,7版本是不支持註解@NonNull,但checker framework 有個向下兼容的解決方案,就是將類型註解@NonNull 用/**/註釋起來。
import checkers.nullness.quals.*; public class TestDemo{ void sample() { /*@NonNull*/ Object my = null; } }
這樣javac編譯器就會忽略掉註釋塊,但用checker framework裏面的javac編譯器一樣可以檢測出@NonNull錯誤。
經過 類型註解 + checker framework 能夠在編譯時就找到runtime error。
容許在同一聲明類型(類,屬性,或方法)上屢次使用同一個註解。
Java8之前的版本使用註解有一個限制是相同的註解在同一位置只能使用一次,不能使用屢次。
Java 8 引入了重複註解機制,這樣相同的註解能夠在同一地方使用屢次。重複註解機制自己必須用 @Repeatable 註解。
實際上,重複註解不是一個語言上的改變,只是編譯器層面的改動,技術層面仍然是同樣的。
例如,咱們可使用以下示例來具體對比Java8以前的版本和Java8中的註解。
1)自定義一個包裝類Hints註解用來放置一組具體的Hint註解
@interface MyHints { Hint[] value(); } @Repeatable(MyHints.class) @interface Hint { String value(); }
使用包裝類當容器來存多個註解(舊版本方法)
@MyHints({@Hint("hint1"), @Hint("hint2")}) class Person {}
使用多重註解(新方法)
@Hint("hint1") @Hint("hint2") class Person {}
2)完整類測試以下所示。
public class RepeatingAnnotations { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Filters { Filter[] value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Filters.class) public @interface Filter { String value(); } @Filter("filter1") @Filter("filter2") public interface Filterable { } public static void main(String[] args) { for (Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) { System.out.println(filter.value()); } } }
輸出結果:
filter1 filter2
分析:
註釋Filter被@Repeatable( Filters.class )註釋。Filters 只是一個容器,它持有Filter, 編譯器盡力向程序員隱藏它的存在。經過這樣的方式,Filterable接口能夠被Filter註釋兩次。
另外,反射的API提供一個新方法getAnnotationsByType() 來返回重複註釋的類型(注意Filterable.class.getAnnotation( Filters.class )將會返回編譯器注入的Filters實例。
3)java 8以前也有重複使用註解的解決方案,但可讀性很差。
public @interface MyAnnotation { String role(); } public @interface Annotations { MyAnnotation[] value(); } public class RepeatAnnotationUseOldVersion { @Annotations({@MyAnnotation(role="Admin"),@MyAnnotation(role="Manager")}) public void doSomeThing(){ } }
Java8的實現方式(由另外一個註解來存儲重複註解,在使用時候,用存儲註解Authorities來擴展重複註解),可讀性更強。
@Repeatable(Annotations.class) public @interface MyAnnotation { String role(); } public @interface Annotations { MyAnnotation[] value(); } public class RepeatAnnotationUseOldVersion { @MyAnnotation(role="Admin") @MyAnnotation(role="Manager") public void doSomeThing(){ } }
什麼?沒看懂?那就再來一波!!!
Java 8對註解處理提供了兩點改進:可重複的註解及可用於類型的註解。整體來講,比較簡單,下面,咱們就以實例的形式來講明Java8中的重複註解和類型註解。
首先,咱們來定義一個註解類BingheAnnotation,以下所示。
package io.mykit.binghe.java8.annotition; import java.lang.annotation.*; /** * @author binghe * @version 1.0.0 * @description 定義註解 */ @Repeatable(BingheAnnotations.class) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface BingheAnnotation { String value(); }
注意:在BingheAnnotation註解類上比普通的註解多了一個@Repeatable(BingheAnnotations.class)註解,有小夥伴會問:這個是啥啊?這個就是Java8中定義可重複註解的關鍵,至於BingheAnnotations.class,你們別急,繼續往下看就明白了。
接下來,我們定義一個BingheAnnotations註解類,以下所示。
package io.mykit.binghe.java8.annotation; import java.lang.annotation.*; /** * @author binghe * @version 1.0.0 * @description 定義註解 */ @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface BingheAnnotations { BingheAnnotation[] value(); }
看到這裏,你們明白了吧!!沒錯,BingheAnnotations也是一個註解類,它相比於BingheAnnotation註解類來講,少了一個@Repeatable(BingheAnnotations.class)註解,也就是說,BingheAnnotations註解類的定義與普通的註解幾乎沒啥區別。值得注意的是,咱們在BingheAnnotations註解類中,定義了一個BingheAnnotation註解類的數組,也就是說,在BingheAnnotations註解類中,包含有多個BingheAnnotation註解。因此,在BingheAnnotation註解類上指定@Repeatable(BingheAnnotations.class)來講明能夠在類、字段、方法、參數、構造方法、參數上重複使用BingheAnnotation註解。
接下來,咱們建立一個Binghe類,在Binghe類中定義一個init()方法,在init方法上,重複使用@BingheAnnotation註解指定相應的數據,以下所示。
package io.mykit.binghe.java8.annotation; /** * @author binghe * @version 1.0.0 * @description 測試註解 */ @BingheAnnotation("binghe") @BingheAnnotation("class") public class Binghe { @BingheAnnotation("init") @BingheAnnotation("method") public void init(){ } }
到此,咱們就能夠測試重複註解了,建立類BingheAnnotationTest,對重複註解進行測試,以下所示。
package io.mykit.binghe.java8.annotation; import java.lang.reflect.Method; import java.util.Arrays; /** * @author binghe * @version 1.0.0 * @description 測試註解 */ public class BingheAnnotationTest { public static void main(String[] args) throws NoSuchMethodException { Class<Binghe> clazz = Binghe.class; BingheAnnotation[] annotations = clazz.getAnnotationsByType(BingheAnnotation.class); System.out.println("類上的重複註解以下:"); Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " ")); System.out.println(); System.out.println("============================="); Method method = clazz.getMethod("init"); annotations = method.getAnnotationsByType(BingheAnnotation.class); System.out.println("方法上的重複註解以下:"); Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " ")); } }
運行main()方法,輸出以下的結果信息。
類上的重複註解以下: binghe class ============================= 方法上的重複註解以下: init method
若是以爲文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習Java8新特性。
最後,附上Java8新特性核心知識圖,祝你們在學習Java8新特性時少走彎路。