Java - annotation 的使用

古古

2018/06/11


  • Java 基本內置 annotation

    • @Override

      • @Override用在方法上,表示這個方法重寫了父方法,如toString()
      • 如果父方法沒有這個方法,那麼就無法編譯過
      • 如果實現接口,需要在每個實現方法都加上@Override,說明這是要實現那個接口的方法,而不是自己新創的方法
    • @Deprecated

      • @Deprecated 表示這個方法已經過期,不建議開發者使用
      • 暗示在將來某個不確定的版本,就有可能會被取消掉
    • @SuppressWarnings

      • @SuppressWarnings是抑制警告的意思,這個注解主要的用處就是忽略警告信息

      • 常見的警告值

        • deprecation : 使用了不贊成使用的類或方法時的警告
        • unused : 某個變量被定義,但是沒有被使用
        • path : 在類路徑、源文件路徑等中有不存在的路徑時的警告
    • 具體實例

      public class Test {
          //定義了一個不建議使用了方法
          @Deprecated
          public void sayHello() {
              System.out.println("Hello");
          }
      
          //聲明以下的方法,自動忽略deprecation和unused的警告,不要在console上報出warn
          //以下的方法使用了被Deprecated的方法sayHello
          //也沒有使用到一個被定義的變量 i
          //但是因為設置了警告忽略所以不會有warning
          @SuppressWarnings({"deprecation", "unused"})
          public static void main(String[] args) {
              int i;
              Test test = new Test();
              test.sayHello();
          }
      }
      
    • @FunctionallInterface

      • Java 1.8新增的注解,用於約定函數式接口

      • 函數式接口存在的意義,主要是配合Lambda表達式來使用

        • 如果接口中只允許有一個public的抽象方法,該接口就稱為函數式接口 (不過此接口可以包含多個default方法或是多個static方法)
        • 能夠包含多個default和static方法的原因是因為在使用函數式編程時,為了能夠使用lambda表達式,所以每個接口的實現類只允許實現一個方法
        • 而default方法和static方法不允許實現類實現,所以就不會影響lambda表達式,因此就可以存在在函數式接口裡
            //匿名實現類 new Function<Integer, String> 實現了Function接口
            List<Integer> intList = Lists.newArrayList(1, 2, 3);
            List<String> stringList = Lists.transform(intList, new com.google.common.base.Function<Integer, String>() {
                @Override
                public String apply(Integer input) {
                    return input + "aa";
                }
            });
        
        //可以轉換成下面的lambda表達式
        List<Integer> intList = Lists.newArrayList(1, 2, 3);
        List<String> stringList = Lists.transform(intList, input -> input + "aa");
        
      • 使用 @FunctionallInterface 自定義函數式接口

        //自定義一個函數式接口,並且只有一個public的抽象方法
        @FunctionalInterface
        public interface MyFunction {
            public String myApply();
        }
        
        //定義一個類,讓這個類去使用 MyFunction
        public class Hello {
            void say(MyFunction myFunction) {
                System.out.println(myFunction.myApply());
            }
        }
        
        public class MainTest {
            public static void main(String[] args) {
                Hello hello = new Hello();
        
                //輸出 Hello World
                hello.say(new MyFunction() {
                    @Override
                    public String myApply() {
                        return "Hello World";
                    }
                });  
        
                //可以轉換為以下的lambda表達式,同樣輸出 Hello World
                hello.say(() -> "Hello World");
            }
        }
        
  • 自定義新的 annotation

    • 使用元注解(meta annotation)來設定一個注解的作用,可以說他是 “自定義注解” 的注解

    • 元注解的種類

      • @Target : 表示這個注解可以放在什麼位置上,也就是可以修飾
        • ElementType.TYPE : 能修飾類、接口、枚舉、注解
        • ElementType.FIELD : 能修飾字段、枚舉的常量
        • ElementType.METHOD : 能修飾方法
        • ElementType.PARAMETER : 能修飾方法參數
        • ElementType.CONSTRUCTOR : 能修飾構造函數
        • ElementType.LOCAL_VARIABLE : 能修飾局部變量
        • ElementType.ANNOTATION_TYPE : 能修飾注解 (元注解就是此種)
        • ElementType.PACKAGE : 能修飾包
      • @Retention : 表示這個注解的生命週期
        • 可選的值有三種
          • RetentionPolicy.SOURCE
            • 表示此注解只在源代碼中有效果,不會編譯進class文件
            • @Override就是這種注解
          • RetentionPolicy.CLASS
            • 表示此注解除了在源代碼有效果,也會編譯進class文件,但是在運行期是無效果的
            • @Retention的默認值,即是當沒有指定@Retention的時候,就會是這種類型
          • RetentionPolicy.RUNTIME
            • 表示此注解從源代碼到運行期一直存在
            • 程序可以透過反射獲取這個注解的信息
      • @Inherited : 表示該注解有繼承性,即是子類可以拿到繼承父類上的注解信息
      • @Documetned : 在用javadoc命令生成API文檔後,文檔裡會出現該注解說明
      • @Repeatable (java1.8新增)
        • 當沒有使用@Repeatable修飾的時候,注解在同一個位置只能出現一次,如果寫重複的兩次就會報錯
        • 但是使用@Repeatable之後,就能夠在同一個地方使用多次
        • 使用@Repeatable的時機通常在想要取得一組資訊時
          • 像是一個User裡面可能有id、name屬性,我們想要以User為單位來取
          • 但是因為注解裡面無法使用自定義對象
          • 所以只能透過@Repeatable這種方法來達成目標
    • 注解參數的數據類型

      • 支持的類型
        • 所有基本數據類型 (int, float, boolean, byte, double, char, long, short)
        • String類型
          • Class類型
          • enum類型
          • Annotation類型
          • 以上所有類型的數組
      • 不支持的類型
        • 自定義對象
    • 注解參數

      • 可以使用default來自定義某個參數的默認值
      • 如果注解裡面只有一個參數value(名字很重要,只能用這個名字),或是其他參數值都有default設定,這時使用此注解時可以不用打參數值,例如 @GetMapping("/")
    • 具體實例

      • 如何使用一個自定義的注解

        //自定義注解 MyAnnotaion,使用 @interface 定義他是一個注解
        @Target({ElementType.METHOD, ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @interface MyAnnotation {
            String[] value();
            String comment() default "hello";
        }
        
        //使用自定義的注解 MyAnnotation,並給value值"John"
        @MyAnnotation("John")
        class MyTest {
            public static void test(){
                //透過反射取得某個類上的某個注解
                MyAnnotation config = MyTest.class.getAnnotation(MyAnnotation.class);
                System.out.println("value: " + config.value() + ", comment: " + config.comment());
            }
        }
        
        public class Main{
            public static void main(String[] args) {
                MyTest.test();  //輸出 value: John, comment: hello 
            }
        }
        
    • 使用 @Repeatable 元注解

      public class FindFiles{
      
          //此注解接住 FileType 注解,並把它存起來
          @Target(ElementType.METHOD)
          @Retention(RetentionPolicy.RUNTIME)
          public @interface FileTypes {
              FileType[] value();
          }
      
          //自定義的注解,可以想像成是一個自定義的對象,裡面有很多成員變量
          //使用 @Repeatable 讓這個注解可以被重複使用
          //並且 @Repeatable 還指定這個 FileType 注解要放到 FileTypes 注解裡
          @Target(ElementType.METHOD)
          @Retention(RetentionPolicy.RUNTIME)
          @Repeatable(FileTypes.class)
          public @interface FileType {
              String value();
              String comment();
          }
      
          @FileType(value=".java", comment="java")
          @FileType(value=".html", comment="html")
          @FileType(value=".css", comment="css")
          public void work() throws Exception {
              //透過反射取得類,並取得work方法,然後再work方法上的FileType注解們
              FileType[] fileTypes = this.getClass().getMethod("work").getAnnotationsByType(FileType.class);
              for (FileType fileType : fileTypes) {
                  System.out.println("value: " + fileType.value() + ", comment: " + fileType.comment());
              }
          }
      
          public static void main(String[] args) throws Exception {
              new FindFiles().work();
          }
      }
      
      //輸出
      value: .java, comment: java
      value: .html, comment: html
      value: .css, comment: css