Java學習_註解

  • 使用註解
    • 註解是放在Java源碼的類、方法、字段、參數前的一種特殊「註釋」。 
       1 // this is a component:
       2 @Resource("hello")
       3 public class Hello {
       4     @Inject
       5     int n;
       6 
       7     @PostConstruct
       8     public void hello(@Param String name) {
       9         System.out.println(name);
      10     }
      11 
      12     @Override
      13     public String toString() {
      14         return "Hello";
      15     }
      16 }

      註釋會被編譯器直接忽略,註解則能夠被編譯器打包進入class文件,所以,註解是一種用做標註的「元數據」。數組

    • 從JVM的角度看,註解自己對代碼邏輯沒有任何影響,如何使用註解徹底由工具決定。框架

    • Java的註解能夠分爲三類:ide

      • 第一類是由編譯器使用的註解,這類註解不會被編譯進入.class文件,它們在編譯後就被編譯器扔掉了。工具

        • @Override:讓編譯器檢查該方法是否正確地實現了覆寫;
        • @SuppressWarnings:告訴編譯器忽略此處代碼產生的警告。
      • 第二類是由工具處理.class文件使用的註解,好比有些工具會在加載class的時候,對class作動態修改,實現一些特殊的功能。這類註解會被編譯進入.class文件,但加載結束後並不會存在於內存中。這類註解只被一些底層庫使用,通常咱們沒必要本身處理。
      • 第三類是在程序運行期可以讀取的註解,它們在加載後一直存在於JVM中,這也是最經常使用的註解。例如,一個配置了@PostConstruct的方法會在調用構造方法後自動被調用(這是Java代碼讀取該註解實現的功能,JVM並不會識別該註解)。
    • 定義一個註解時,還能夠定義配置參數。配置參數能夠包括:測試

      • 全部基本類型;
      • String;
      • 枚舉類型;
      • 基本類型、String、Class以及枚舉的數組。
    • 由於配置參數必須是常量,因此,上述限制保證了註解在定義時就已經肯定了每一個參數的值。
    • 註解的配置參數能夠有默認值,缺乏某個配置參數時將使用默認值。此外,大部分註解會有一個名爲value的配置參數,對此參數賦值,能夠只寫常量,至關於省略了value參數。若是隻寫註解,至關於所有使用默認值。
      public class Hello {
          @Check(min=0, max=100, value=55)
          public int n;
      
          @Check(value=99)
          public int p;
      
          @Check(99) // @Check(value=99)
          public int x;
      
          @Check
          public int y;
      }
  • 定義註解
    • Java語言使用@interface語法來定義註解(Annotation),它的格式爲:
      public @interface Report {
          int type() default 0;
          String level() default "info";
          String value() default "";
      }

      註解的參數相似無參數方法,能夠用default設定一個默認值(強烈推薦)。最經常使用的參數應當命名爲valuethis

    • 有一些註解能夠修飾其餘註解,這些註解就稱爲元註解(meta annotation)。Java標準庫已經定義了一些元註解,咱們只須要使用元註解,一般不須要本身去編寫元註解。spa

    • 最經常使用的元註解是@Target。使用@Target能夠定義Annotation可以被應用於源碼的哪些位置。debug

      • 類或接口:ElementType.TYPE
      • 字段:ElementType.FIELD
      • 方法:ElementType.METHOD
      • 構造方法:ElementType.CONSTRUCTOR
      • 方法參數:ElementType.PARAMETER
    • 例如,定義註解@Report可用在方法上,咱們必須添加一個@Target(ElementType.METHOD)。定義註解@Report可用在方法或字段上,能夠把@Target註解參數變爲數組{ ElementType.METHOD, ElementType.FIELD }
      @Target(ElementType.METHOD)
      public @interface Report {
          int type() default 0;
          String level() default "info";
          String value() default "";
      }
      @Target({
          ElementType.METHOD,
          ElementType.FIELD
      })
      public @interface Report {
          ...
      }

      實際上@Target定義的valueElementType[]數組,只有一個元素時,能夠省略數組的寫法。code

    • 另外一個重要的元註解@Retention定義了Annotation的生命週期。
      • 僅編譯期:RetentionPolicy.SOURCE
      • 僅class文件:RetentionPolicy.CLASS
      • 運行期:RetentionPolicy.RUNTIME

                    若是@Retention不存在,則該Annotation默認爲CLASS。由於一般咱們自定義的Annotation都是RUNTIME,因此,務必要加上@Retention(RetentionPolicy.RUNTIME)這個元註解。component

    • @Repeatable這個元註解能夠定義Annotation是否可重複。  
      @Repeatable(Reports.class)
      @Target(ElementType.TYPE)
      public @interface Report {
          int type() default 0;
          String level() default "info";
          String value() default "";
      }
      
      @Target(ElementType.TYPE)
      public @interface Reports {
          Report[] value();
      }
      通過@Repeatable修飾後,在某個類型聲明處,就能夠添加多個@Report註解:
      
      @Report(type=1, level="debug")
      @Report(type=2, level="warning")
      public class Hello {
      }
    • 使用@Inherited定義子類是否可繼承父類定義的Annotation@Inherited僅針對@Target(ElementType.TYPE)類型的annotation有效,而且僅針對class的繼承,對interface的繼承無效。
      @Inherited
      @Target(ElementType.TYPE)
      public @interface Report {
          int type() default 0;
          String level() default "info";
          String value() default "";
      }
      在使用的時候,若是一個類用到了@Report:
      
      @Report(type=1)
      public class Person {
      }
      則它的子類默認也定義了該註解:
      
      public class Student extends Person {
      }
    • 如何定義Annotation

       1 //第一步,用@interface定義註解:
       2 
       3 public @interface Report {
       4 }
       5 
       6 //第二步,添加參數、默認值:
       7 
       8 public @interface Report {
       9     int type() default 0;
      10     String level() default "info";
      11     String value() default "";
      12 }
      13 //把最經常使用的參數定義爲value(),推薦全部參數都儘可能設置默認值。
      14 
      15 //第三步,用元註解配置註解:
      16 
      17 @Target(ElementType.TYPE)
      18 @Retention(RetentionPolicy.RUNTIME)
      19 public @interface Report {
      20     int type() default 0;
      21     String level() default "info";
      22     String value() default "";
      23 }

      其中,必須設置@Target@Retention@Retention通常設置爲RUNTIME,由於咱們自定義的註解一般要求在運行期讀取。通常狀況下,沒必要寫@Inherited@Repeatable

  • 處理註解

    • SOURCE類型的註解主要由編譯器使用,所以咱們通常只使用,不編寫。CLASS類型的註解主要由底層工具庫使用,涉及到class的加載,通常咱們不多用到。只有RUNTIME類型的註解不但要使用,還常常須要編寫。
    • Java提供的使用反射API讀取Annotation的方法包括:

      判斷某個註解是否存在於ClassFieldMethodConstructor

      • Class.isAnnotationPresent(Class)
      • Field.isAnnotationPresent(Class)
      • Method.isAnnotationPresent(Class)
      • Constructor.isAnnotationPresent(Class)
        // 判斷@Report是否存在於Person類:
        Person.class.isAnnotationPresent(Report.class);    

       使用反射API讀取Annotation:

      • Class.getAnnotation(Class)
      • Field.getAnnotation(Class)
      • Method.getAnnotation(Class)
      • Constructor.getAnnotation(Class)
        // 獲取Person定義的@Report註解:
        Report report = Person.class.getAnnotation(Report.class);
        int type = report.type();
        String level = report.level();
    • 使用反射API讀取Annotation有兩種方法。方法一是先判斷Annotation是否存在,若是存在,就直接讀取。 
      Class cls = Person.class;
      if (cls.isAnnotationPresent(Report.class)) {
          Report report = cls.getAnnotation(Report.class);
          ...
      }
    • 第二種方法是直接讀取Annotation,若是Annotation不存在,將返回null。

      Class cls = Person.class;
      Report report = cls.getAnnotation(Report.class);
      if (report != null) {
         ...
      }

       

    • 讀取方法、字段和構造方法的Annotation和Class相似。但要讀取方法參數Annotation就比較麻煩一點,由於方法參數自己能夠當作一個數組,而每一個參數又能夠定義多個註解,因此,一次獲取方法參數的全部註解就必須用一個二維數組來表示。例如,對於如下方法定義的註解。

      public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
      }

      要讀取方法參數的註解,咱們先用反射獲取Method實例,而後讀取方法參數的全部註解。

      // 獲取Method實例:
      Method m = ...
      // 獲取全部參數的Annotation:
      Annotation[][] annos = m.getParameterAnnotations();
      // 第一個參數(索引爲0)的全部Annotation:
      Annotation[] annosOfName = annos[0];
      for (Annotation anno : annosOfName) {
          if (anno instanceof Range) { // @Range註解
              Range r = (Range) anno;
          }
          if (anno instanceof NotNull) { // @NotNull註解
              NotNull n = (NotNull) anno;
          }
      }
    • 使用註解

      • 註解如何使用,徹底由程序本身決定。例如,JUnit是一個測試框架,它會自動運行全部標記爲@Test的方法。 

      • 來看一個@Range註解,咱們但願用它來定義一個String字段的規則:字段長度知足@Range的參數定義。

        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.FIELD)
        public @interface Range {
            int min() default 0;
            int max() default 255;
        }

        在某個JavaBean中,咱們可使用該註解:

        public class Person {
            @Range(min=1, max=20)
            public String name;
        
            @Range(max=10)
            public String city;
        }

        可是,定義了註解,自己對程序邏輯沒有任何影響。咱們必須本身編寫代碼來使用註解。這裏,咱們編寫一個Person實例的檢查方法,它能夠檢查Person實例的String字段長度是否知足@Range的定義:

        void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
            // 遍歷全部Field:
            for (Field field : person.getClass().getFields()) {
                // 獲取Field定義的@Range:
                Range range = field.getAnnotation(Range.class);
                // 若是@Range存在:
                if (range != null) {
                    // 獲取Field的值:
                    Object value = field.get(person);
                    // 若是值是String:
                    if (value instanceof String) {
                        String s = (String) value;
                        // 判斷值是否知足@Range的min/max:
                        if (s.length() < range.min() || s.length() > range.max()) {
                            throw new IllegalArgumentException("Invalid field: " + field.getName());
                        }
                    }
                }
            }
        }

        這樣一來,咱們經過@Range註解,配合check()方法,就能夠完成Person實例的檢查。注意檢查邏輯徹底是咱們本身編寫的,JVM不會自動給註解添加任何額外的邏輯。

相關文章
相關標籤/搜索