java-Annotation註解

期待發表於2019-01-19


@Override

  1. 是告訴編譯器檢查這個方法,保證父類要包含一個被該方法重寫的方法,否則編譯出錯。
  2. 只能修飾方法,不能修飾其他程式元素 。

Java 9 增強的@Deprecated

  1. 表示某個程式元素已經過時。
  2. Java 9 為@Deprecated註解增加了如下兩個屬性

    • ForRemoval:該boolean型別的屬性指定該API在將來是否會被刪除。
    • Since:該String型別的屬性指定該API從哪個版本被標記為過時。
public class DeprecatedS {
    //已過時,since是從9版本開始,forRemoval指定該API將來會被刪除
    @Deprecated(forRemoval=true,since="9")
    public void info() {
    }
    public static void main(String[] args) {
        DeprecatedS s = new DeprecatedS();
        s.info();
    }
}

抑制編譯器警告:@SuppressWarnings

  1. 指示被該註解修飾的程式元素,以及該程式元素中的所有子元素,取消顯示指定的編譯器警告。@SuppressWarnings會一直作用於該程式元素的所有子元素。
//關閉整個類裡的編譯器警告
@SuppressWarnings(value="unchecked")
public class SuppressWarningsS {
    public static void main(String[] args) {
        List<String> list = new ArrayList();
    }
}

堆汙染警告 與 java 9 增強的@SafeVarargs

  1. 當把一個不帶泛型的物件賦給一個帶泛型的變數時,往往就會發生堆汙染
public class SafeVarargsS {
    public static void main(String[] args) {
    }
    //Type safety: Potential heap pollution via varargs parameter list
    //型別安全:通過varargs引數列表的潛在堆汙染。
    public static void ai(List<String> ...list) {
        List[] list2 = list;
        List<Integer> myList = new ArrayList<>();
        myList.add(new Random().nextInt(100));
        list2[0] = myList;
        String string = list[0].get(0);
    }
}
  • 上面程式中的粗體字程式碼已經發生了堆汙染,由於該方法有個形參是List<String>…型別,個數可變的形參相當於陣列,但java又不會支援泛型陣列,因此程式只能把List<String>…當成List[]處理,這裡發生了堆汙染。
  • 使用@SafeVarargs
@SafeVarargs
public static void ai(List<String> ...list) {}

Java 8的函式式介面 @FuncationInterface

  1. 如果介面中只有一個抽象方法,可以包含多個預設方法或多個static方法,該介面就是函式式介面。
  2. @FuncationInterface就是用來指定某個介面必須是函式式介面。
@FunctionalInterface
public interface Function<T, R> {}
  • FuncationInterface只能修飾介面,不能修飾其他元素

JDK的元註解

  1. 在java.lang.annotation包下提供了6個Meta註解(元註解),@Repeatable專門用於定義java 8新增的重複註解。
  2. 使用@Retention :保留,扣留 Policy:政策,方針

    • @Retention只能用於修飾註解定義,用於指定被修飾的註解可以保留多長時間,他包含一個RetentionPolicy型別的value成員變數,所以使用時必須為該value成員變數指定值。
    • Value成員變數的值只能是如下三個

      • RetentionPolicy.CLASS : 編譯器將把註解記錄在class中,當執行java程式時,JVM不可獲得註解資訊,這是預設值。
      • RetentionPolicy.REUNTIME:編譯器將把註解記錄在class檔案中,當執行java程式時,JVM也可獲取註解資訊,程式可以通過反射獲得該註解資訊。
      • RetentionPolicy.SOURCE:註解只保留在原始碼中,編譯器直接丟棄這種註解。
    • 使用
@Retention(value = RetentionPolicy.RUNTIME)
public @interface My1 {}
@Retention(RetentionPolicy.RUNTIME)
public @interface My1 {}
  1. 使用@Target

    • 只能修飾註解定義,它用於指定被修飾的註解能用於修飾那些程式單元。
    • @Target元註解也包含一個value成員變數
    • 成員變數如下

      • ElementType.ANNOTATION_TYPE:指定該策略的註解只能修飾註解。
      • ElementType.CONSTRUCTOR:指定該策略的註解只能修飾構造器。
      • ElementType.FIELD:指定該策略的註解只能修飾成員變數。
      • ElementType.LOCAL_VARIABLE:指定該策略的註解只能修飾區域性變數。
      • ElementType.METHOD:指定該策略的註解只能修飾方法。
      • ElementType.PACKAGE:指定該策略的註解只能修飾包定義。
      • ElementType.PARAMETER:指定該策略的註解只能修飾引數。
      • ElementType.TYPE:指定該策略的註解只能修飾類,介面,註解型別,列舉定義。
    • 使用
@Target(value = ElementType.ANNOTATION_TYPE)
public @interface My1 {}
  1. 使用@Document

    • @Document用於指定被該元註解修飾的註解類將被javadoc工具提取城文件,如果定義註解類時使用了@Document,則所有使用該註解修飾的程式元素的API文件中將會包含該註解說明。
  2. 使用@Inherited : 可繼承的

    • 指定被他修飾的註解將具有繼承性。
@Inherited
public @interface My1 {}
  • 上面程式中程式碼表明@My1具有繼承性,如果某個類使用@My1修飾,則該類的子類將自動使用@My1修飾。
  • 檢查其子類是否預設使用@My1修飾。
@My1
class A{}
public class My1_Test extends A {
    public static void main(String[] args) {
        System.out.println(My1_Test.class.isAnnotationPresent(My1.class));//true
    }
}

自定義註解

  1. 定義新的註解使用@Interface關鍵字定義一個新的註解型別與定義一個介面非常像,如下
public @interface My1 {}
  1. 定義了該註解之後,就可以在程式的任何地方使用該註解。
  2. 預設情況下,註解可用於修飾任何程式元素,包括類,介面,方法等。
  3. 註解還可以帶成員變數,成員變數在註解定義中以無形參的方法形式來宣告,其方法名和返回值定義了該成員變數的名字和型別。
public @interface My1 {
    String name();
    int id();
}
  • 一旦在註解裡定義了成員變數後,使用該註解時就應該為他的成員變數指定值。
@My1(id=1,name="rrr")
class A{}
(6)    也可以在定義註解的成員變數時為其指定初始值,指定預設值default。
public @interface My1 {
    String name() default "ccc";
    int id() default 123;
}
  1. 成員變數指定了值,則預設值就不會起作用。
  2. 根據註解是否可以包含成員變數,可以把註解分為

    • 標記註解:沒有定義成員變數的註解型別被稱為標記。這種註解僅利用自身的存在與否來提供資訊,如@Test、
    • 後設資料註解:包括成員變數的註解,因為他們可以接受更多的後設資料,所以也被稱為後設資料註解。

提取註解資訊

  1. 使用註解修飾了類,方法,成員變數等之後,這些註解不會自己生效,必須由開發者提供相應的工具來提取並處理註解資訊。
  2. AnnotatedElement介面,該介面代表程式中可以接受註解的程式元素,該介面主要有如下個實現類:

    • Class:類定義
    • Constructor:構造器定義
    • Field:類的成員變數定義
    • Method:類的方法定義
    • Package:類的包定義
  3. 只有當定義註解時使用了@Retention(RetentionPolicy.RUNTIME)修飾,該註解才會在執行時可見,JVM才會在裝載class檔案時讀取儲存在class檔案中的註解資訊。
  4. 獲取My2類的方法上的Annotation
public class My2 {
    @My1(id =123,name ="dsds")
    public void info() {
    }
}
public class GetMy2Annotation{
    public static void main(String[] args) throws Exception{
        Class<?> forName = Class.forName("annotations.My2");//載入類
        Method method = forName.getMethod("info");//得到方法
        Annotation[] annotations = method.getAnnotations();//得到方法上所有註解
        //@annotations.My1(name="dsds", id=123)
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}
  1. 訪問註解中的後設資料
public class GetMy2Annotation{
    public static void main(String[] args) throws Exception{
        Class<?> forName = Class.forName("annotations.My2");//載入類
        Method method = forName.getMethod("info");//得到方法
        Annotation[] annotations = method.getAnnotations();//得到方法上所有註解
        /*
         * 123
         * dsds
         * */
        for (Annotation annotation : annotations) {
            if (annotation instanceof My1) {
                System.out.println(((My1)annotation).id());
                System.out.println(((My1)annotation).name());
            }
        }
    }
}
  1. 利用註解模擬@Test的JUnit效果
public class RunTest {
    public static void main(String[] args) throws Exception {
        Class<?> forName = Class.forName("annotations.RunTest_JUnit");//載入類
        Method[] methods = forName.getDeclaredMethods();//獲得本類所有方法
        int success = 0;//成功方法
        int fail = 0;//失敗方法
        for (Method method : methods) {
            System.out.println(method);
            System.out.println(method.isAnnotationPresent(Testable.class));
            if (method.isAnnotationPresent(Testable.class)) {
                try {
                    //抑制private訪問修飾符
                    method.setAccessible(true);
                    method.invoke( null);
                    success++;
                } catch (Exception e) {
                    fail++;
                }
            }
        }
        System.out.println("成功方法有:" + success +"個   失敗的有:"+fail+"個");
    }
}
class RunTest_JUnit{
    @Testable
    private static void t() {
        System.out.println("=========================t");
    }
    private static void t1() {
    }
    @Testable
    private static void t2() {
        System.out.println("=========================t2");
    }
    @Testable
    private static void r2() {
        System.out.println("=========================r2");
        throw new RuntimeException("出錯誤啦");
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testable{
}

Output:
private static void annotations.RunTest_JUnit.t1()
false
private static void annotations.RunTest_JUnit.t()
true
=========================t
private static void annotations.RunTest_JUnit.r2()
true
=========================r2
private static void annotations.RunTest_JUnit.t2()
true
=========================t2
成功方法有:2個   失敗的有:1個
  • 上面的@Testable用於標記那些方法是可測試的,該註解可以作為JUnit測試框架的補充,在JUnit框架中他要求測試用例的測試方法必須以test開頭。

Java 8 新增的重複註解

@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.TYPE)
@Repeatable(value=FkTags.class)//只能可以包容它的容器類
public @interface FkTag {
    String name() default "王自強";
    int id();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.TYPE)
public @interface FkTags {
    FkTag[] value();
}

//@FkTags({@FkTag(id=1,name="dsd"),@FkTag(id=213)})
@FkTag(id=123)
@FkTag(id=1233)
public class Test_FkTage {
    public static void main(String[] args) {
        Class<Test_FkTage> cl = Test_FkTage.class;//獲取類
        //這個方法可以獲得多個重複註解,而getDeclaredAnnotation只能獲取一個
        FkTag[] annotationsByType = cl.getAnnotationsByType(FkTag.class);
        for (FkTag fkTag : annotationsByType) {
            System.out.println(fkTag.id()+"      "+ fkTag.name());
        }
        FkTags annotation = cl.getAnnotation(FkTags.class);
        System.out.println(annotation);
    }
}
Output
123      王自強
1233      王自強
@fkAnnotation.FkTags(value={@fkAnnotation.FkTag(name="王自強", id=123), @fkAnnotation.FkTag(name="王自強", id=1233)})
  • 如上的重複註解只是一種簡便的寫法,運用@Repeatable註解來制定他的容器註解類即可。
  • 容器註解類註解的保留期必須比他所包含的註解的保留期更長,否則編譯器報錯。

Java 8新增的型別註解

編寫自定義註解時未寫@Inherited的執行結果 編寫自定義註解時寫了@Inherited的執行結果
子類的類上能否繼承到父類的類上的註解?
子類方法,實現了父類上的抽象方法,這個方法能否繼承到註解?
子類方法,實現了父類上的方法,這個方法能否繼承到註解?
子類方法,覆蓋了父類上的方法,這個方法能否繼承到註解?

相關文章