1. 程式人生 > >java之美妙的註解

java之美妙的註解

註解

一、認識註解

先看百度百科對java註解的解釋:

定義:註解(Annotation),也叫元資料。一種程式碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。

作用分類:

①編寫文件:通過程式碼裡標識的元資料生成文件【生成文件doc文件】

② 程式碼分析:通過程式碼裡標識的元資料對程式碼進行分析【使用反射】

③編譯檢查:通過程式碼裡標識的元資料讓編譯器能夠實現基本的編譯檢查【Override】

提到的元資料,可以理解為描述資料的資料,看起來很繞,元註解也是描述註解的註解,後面說。

Annotation都是java.lang.annotation.Annotation介面的子介面,註解是一種特殊的介面,從程式碼格式上來看確實是,是在interface前加了一個@符號,格式為@interface xxx{}

二、JDK內建註解

jdk就存在註解,比如我們用eclipse子類覆蓋父類方法時候用快捷間alt+/會自帶在方法名稱上面貼上一個@Override的標籤。

  • @Override

    這個就是在子類覆蓋父類的方法時候,經常遇到

  • @Deprecated

    這個元素是用來標記過時的元素,想必大家在日常開發中經常碰到。編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在呼叫一個過時的元素比如過時的方法、過時的類、過時的成員變數。 如java.util.Data類中有很多過時的方法

  • @SupressWarings

    抑制警告,有很多人有程式碼潔癖,看到黃色的警告不爽,用它就可以抑制住。

  • @SafeVarargs

    java7出現的,抑制堆汙染警告,有點自欺欺人的感覺。

三、元註解

元註解就是註解的註解,來看一下有哪些元註解

  • @Retention

    Retention英文是保留的意思,在這裡可以約束註解的存活週期,程式碼執行有三個週期,分別為Source(原始碼)、Class(位元組碼)、Runtime(執行時期) 三個時期的值保留在RetentionPolicy這個列舉類中。所以我們可以這樣來玩,

    @Retention(RetetionPolicy.常量值)//RESOURCE,CLASS,RUNTIME

  • @Target

    Target是目標的意思,這裡約束這個註解在那可以貼(類,方法,構造器,引數等),位置的常量在ElementType這個列舉類中

    @Target({ElementsType.常亮值})//TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE表示類,介面,列舉

  • @Documented

    Document文件的意思,也就是使用這個標籤到時候會帶到API文件中去

  • @Inherited

    Inherited是遺傳的意思,顧名思義,這個註解會遺傳到子類去

  • @Repeatable 可重複的,Java8的一個新特性

四、自定義註解

1.註解的屬性

在註解中定義屬性,必須是8 種基本資料型別外加 類、介面、註解及它們的陣列。不能是Integer..註解中屬性可以有預設值,預設值需要用 default 關鍵值指定。

如:

@interface TestAnnotation{
    int id() default 0;
    String msg() default "hello";
}
@TestAnnotation(id = 3,msg = "aa")
class Test1{
    
}

//當註解中只有一個value屬性時候,貼標籤的時候可以省略value。
//當註解中沒有屬性時,括號都可以省略(override註解)
@interface Test1Annotation{
    int[] value();
}
@Test1Annotation({1,2})
class Test2{
    
}

2.用反射操作註解

註解可以在類Class,方法Method,欄位Field,構造器Constructor上等,所以在各自的類中都存在獲取註解的API

 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 
 如果指定型別的註釋存在於此元素上,則返回 true,否則返回 false。 
 Annotation[] getAnnotations() 返回此元素上存在的所有註釋。 
 <Annotation>  getAnnotation(Class annotationClass)如果存在該元素的指定型別的註釋,則返回這些註釋物件,否則返回 null。 獲取註解物件

自定義一個註解:

@Target({TYPE, FIELD, METHOD})//註解可以放的目標
@Retention(RetentionPolicy.RUNTIME)//註解保留週期
public @interface AnnotationTest {
    int id();
    String name();
}

測試一下這個自定義的註解:

@AnnotationTest(id = 1,name = "na")
class Test{
    public static void main(String[] args) throws Exception {
        //判斷Test(類元素)是否應用了這個註解
        boolean hasAnnotation = Test.class.isAnnotationPresent(AnnotationTest.class);
        if(hasAnnotation){
            //獲取註解物件
            AnnotationTest t = Test.class.getAnnotation(AnnotationTest.class);
            System.out.println(t.name());
            System.out.println(t.id());
            //獲取所有的註解s
            Annotation[] ans = Test.class.getAnnotations();
            for (Annotation an : ans) {
                System.out.println(an);
            }
        }
        System.out.println("============");
        //獲取方法中上的註解
        boolean has = Test.class.getMethod("work").isAnnotationPresent(AnnotationTest.class);
        if(has){
            AnnotationTest an = Test.class.getMethod("work").getAnnotation(AnnotationTest.class);
            System.out.println(an.name());
        }
    }
    @AnnotationTest(id = 0,name="hello")
    public void work(){
    }
}

結果為:

na
1
@annotation.AnnotationTest(id=1, name=na)
============
hello

五、註解的用途

實踐:模擬Junit4.x

Junit是做單元測試的,junit4用了註解,用法是在一個公共無參的方法貼上一個@Test標籤表示要測試這個方法,但是在測試之前有可能會出現一些初始化操作,在結束後可能需要釋放一些資源,需要初始化的方法上面貼上一個@Before,Test之後需要執行的需要貼上@After。

現在我們來自定義註解來模擬一下這些操作,需要在這裡提的是,其實註解本身沒什麼意義,需要人為的設定它完成一些功能。

我們先定義三個註解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAfter {
    
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBefore {
    
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}

來一個測試這些註解功能的類

public class Employee {
    @MyBefore
    public void init(){
        System.out.println("-------初始化-------");
    }
    @MyAfter
    public void end(){
        System.out.println("-------銷燬---------");
    }
    @MyTest
    public void save() {
        System.out.println("儲存操作");
    }
    @MyTest
    public void delete() {
        System.out.println("刪除操作");
    }
    @MyBefore
    public void ddd(){
        System.out.println("怎麼說呢?");
    }
}

好了,接下來我們就得具體去設定這個註解的意義

思路和步驟:

  • 獲取Employee類的位元組碼物件,得到所有的方法

    • 對所有方法進行迭代,將三類註解分別儲存,把有@MyBefore註解的存在beforeList,帶有@MyAfter存在afterList,帶有@MyTest存在testList 對testList集合迭代,在迭代 過程中先執行beforeList中的方法,在執行afterList中的方法
public class JunitTest {
    public static void main(String[] args) throws Exception {
        //得到Employee中所有的方法
        Method[] mds = Employee.class.getDeclaredMethods();
        //建立Employee的物件,用來執行方法
        Employee e = Employee.class.newInstance();
        //用集合儲存三類註解標註的方法
        List<Method> beforeList = new ArrayList<>();
        List<Method> afterList = new ArrayList<>();
        List<Method> testList = new ArrayList<>();
        //對所有的方法進行迭代,分類
        for (Method md : mds) {
            if(md.isAnnotationPresent(MyBefore.class)){
                beforeList.add(md);
            }else if(md.isAnnotationPresent(MyAfter.class)){
                afterList.add(md);
            }else if(md.isAnnotationPresent(MyTest.class)){
                testList.add(md);
            }
        }
        //在對test迭代的過程中,先執行所有被before註解了的方法,然後執行test註解的方法,最後再執行after註解了的方法
        for (Method method : testList) {
            for (Method before : beforeList) {
                before.invoke(e);  //方法都沒有引數
            }
            method.invoke(e);
            
            for (Method after : afterList) {
                after.invoke(e);
            }
        }
    }
}

結果:

-------初始化-------
怎麼說呢?
刪除操作
-------銷燬---------
-------初始化-------
怎麼說呢?
儲存操作
-------銷燬---------