【Java8新特性】重複注解與類型注解,你真的學會了嗎?
寫在前面
在Java8之前,在某個類或者方法,字段或者參數上标注注解時,同一個注解隻能标注一次。但是在Java8中,新增了重複注解和類型注解,也就是說,從Java8開始,支援在某個類或者方法,字段或者參數上标注多個相同的注解。那麼,有讀者就會問了:如何實作呢?别急,往下看!文中不隻是Java8中的注解。
JDK5中的注解
1.注解(@)
注解就相當于一種标記,在程式中加了注解就等于為程式加了某種标記。(JDK1.5新特性)。
2.作用
告訴javac編譯器或者java開發工具……向其傳遞某種資訊,作為一個标記。
3.如何了解注解?
一個注解就是一個類。
标記可以加在包、類、字段、方法,方法參數以及局部變量上。可以同時存在多個注解。
每一個注解結尾都沒有“;”或者其他特别符号。
定義注解需要的基礎注解資訊如下所示。
@SuppressWarnings("deprecation") //編譯器警告過時(source階段)
@Deprecated //過時(Runtime階段)
@Override //重寫(source階段)
@Retention(RetentionPolicy.RUNTIME)
//保留注解到程式運作時。(Runtime階段)
@Target({ElementType.METHOD,ElementType.TYPE})
//标記既能定義在方法上,又能定義在類、接口、枚舉上等。
注意:
1)添加注解需要有注解類。RetentionPolicy是一個枚舉類(有三個成員)。
2)Target中可以存放數組。它的預設值為任何元素。
ElementType.METHOD:表示隻能标記在方法上。
ElementType.TYPE:表示隻能标記定義在類上、接口上、枚舉上等
3)ElementType也是枚舉類。成員包括:ANNOTATION_TYPE(注解)、CONSTRUCTOR(構造方法)、FIEID(成員變量)、LOCAL_VARIABLE(變量)、METHOD(方法)、PACKAGE(包)、PARAMETER(參數)、TYPE。
4.關于注解
元注解:注解的注解(了解:給一個注解類再加注解)
中繼資料:資料的資料
元資訊:資訊的資訊
5.注解分為三個階段
java源檔案--> class檔案 --> 記憶體中的位元組碼。
Retention的注解有三種取值:(分别對應注解的三個階段)
RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIME
注意:注解的預設階段是Class。
6.注解的屬性類型
原始類型(就是八個基本資料類型)、String類型、Class類型、數組類型、枚舉類型、注解類型。
7.為注解增加屬性
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"))
Java8中的注解
對于注解(也被稱做中繼資料),Java 8 主要有兩點改進:類型注解和重複注解。
1.類型注解
1)Java 8 的類型注解擴充了注解使用的範圍。
在java 8之前,注解隻能是在聲明的地方所使用,java8開始,注解可以應用在任何地方。
例如:
建立類執行個體
new @Interned MyObject();
類型映射
myString = (@NonNull String) str;
implements 語句中
class UnmodifiableList 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用來描述注解的新場合。
ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中。
ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中(例如:聲明語句、泛型和強制轉換語句中的類型)。
例如,下面的示例。
@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 用/**/注釋起來。
void sample() {
/*@NonNull*/ Object my = null;
}
這樣javac編譯器就會忽略掉注釋塊,但用checker framework裡面的javac編譯器同樣能夠檢測出@NonNull錯誤。
通過 類型注解 + checker framework 可以在編譯時就找到runtime error。
2.重複注解
允許在同一聲明類型(類,屬性,或方法)上多次使用同一個注解。
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")
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)
String role();
MyAnnotation[] value();
@MyAnnotation(role="Admin")
@MyAnnotation(role="Manager")
public void doSomeThing(){
}
}
什麼?沒看懂?那就再來一波!!!
Java8對注解的增強
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;
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注解指定相應的資料,如下所示。
- @description 測試注解
@BingheAnnotation("binghe")
@BingheAnnotation("class")
public class Binghe {
@BingheAnnotation("init")
@BingheAnnotation("method")
public void init(){
}
到此,我們就可以測試重複注解了,建立類BingheAnnotationTest,對重複注解進行測試,如下所示。
import java.lang.reflect.Method;
import java.util.Arrays;
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新特性時少走彎路。
原文位址
https://www.cnblogs.com/binghe001/p/13033447.html