1. 程式人生 > 實用技巧 >一、註解

一、註解

註解:

一、 標準註解:@Override、@Deprecated、@SuppressWarnings

二、 元註解:用於修飾註解的註解,通常用在註解的定義上

1. @Target : 註解的作用目標

  • package、type(類、介面、列舉、Annotation型別)
  • 型別成員(方法、構造方法、成員變數、列舉值)
  • 方法引數和本地變數(如迴圈變數、catch引數)

檢視@Target註解原始碼:

package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

其中定義了一個value()的陣列,檢視陣列是個列舉類:

package java.lang.annotation;

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

前面的幾個都好理解,註釋都有,後面在JDK1.8中新增了兩個(TYPE_PARAMETER和TYPE_USE)

其中TYPE_USE包含以上所有型別,即如果想讓註解標註在上面任何型別,就可以選用TYPE_USE了。而另一個TYPE_PARAMETER也是做引數標註的,和PARAMETER不同的是,TYPE_PARAMETER是作用在泛型引數上的。
例如自定義TYPE_PARAMETER型別的註解@TypeParameterAnnotation:

@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {
}

//將@TypeParameterAnnotation作用於泛型引數之上
public class TypeParameterDemo<@TypeParameterAnnotation T> {

    public <@TypeParameterAnnotation U> T foo(T t){
        return t;
    }
}

2. @Retention : 標註註解被保留時間的長短

  • 定義註解的生命週期

檢視@Retention註解原始碼:

package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

其中定義了一個型別為RetentionPolicy的value(),進一步檢視RetentionPolicy是個列舉類:

package java.lang.annotation;

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
  • SOURCE:註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄;
  • CLASS:註解被保留到class檔案,但jvm載入class檔案時候被遺棄,這是預設的生命週期;
  • RUNTIME:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在;

這三個可以簡單的理解為Java原始檔(.java檔案) ---> .class檔案 ---> 記憶體中的位元組碼

3. @Documented : 註解是否應該被包含在JavaDoc文件中

4. @Inherited : 是否允許子類繼承該註解

關於@Inherited介紹看這篇文章比較好 https://www.jianshu.com/p/7f54e7250be3

三、 自定義註解

1. 格式

public @interface 註解名 {
    修飾符 返回值 屬性名() 預設值;
    修飾符 返回值 屬性名() 預設值;
}

2. 註解屬性支援的型別

  • 所有基本型別(int float double boolean byte char short long)
  • String型別
  • Class型別
  • Enum型別
  • Annotation型別
  • 以上所有型別的陣列

3. 關於類的基本資訊

  • Package

      public class Package implements java.lang.reflect.AnnotatedElement {
      //省略...
      }
    

Package實現了AnnotatedElement

  • Class

      public final class Class<T> implements java.io.Serializable,
                                GenericDeclaration,
                                Type,
                                AnnotatedElement {
      //省略...
      }
    

檢視Class發現其實現了AnnotatedElement

  • Constructor

      public final class Constructor<T> extends Executable {
      //省略...
      }
    
      public abstract class Executable extends AccessibleObject
          implements Member, GenericDeclaration {
      //省略...
      }
    
      public class AccessibleObject implements AnnotatedElement {
      //省略...
      }
    

Constructor也是實現了AnnotatedElement,其間還繼承了是否可執行以及是否可達兩個類

  • Field

      public final class Field extends AccessibleObject implements Member {
      //省略...
      }
      
      public class AccessibleObject implements AnnotatedElement {
      //省略...
      }
    

Field也實現了AnnotatedElement,其繼承了是否可達這個類

  • Method

      public final class Method extends Executable {
      //省略...
      }
      
      public abstract class Executable extends AccessibleObject
          implements Member, GenericDeclaration {
      //省略...
      }
    
      public class AccessibleObject implements AnnotatedElement {
      //省略...
      }
    

Method與Constructor類似,都存在是否可執行以及是否可達,而Field只存在是否可達情況(Field並不被執行),但是四個類都實現了AnnotatedElement,來檢視一下其原始碼(註釋太多都被我幹掉了):

public interface AnnotatedElement {

    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    Annotation[] getAnnotations();

    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
         /*
          * Definition of associated: directly or indirectly present OR
          * neither directly nor indirectly present AND the element is
          * a Class, the annotation type is inheritable, and the
          * annotation type is associated with the superclass of the
          * element.
          */
         T[] result = getDeclaredAnnotationsByType(annotationClass);

         if (result.length == 0 && // Neither directly nor indirectly present
             this instanceof Class && // the element is a class
             AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
             Class<?> superClass = ((Class<?>) this).getSuperclass();
             if (superClass != null) {
                 // Determine if the annotation is associated with the
                 // superclass
                 result = superClass.getAnnotationsByType(annotationClass);
             }
         }

         return result;
     }

    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
         Objects.requireNonNull(annotationClass);
         // Loop over all directly-present annotations looking for a matching one
         for (Annotation annotation : getDeclaredAnnotations()) {
             if (annotationClass.equals(annotation.annotationType())) {
                 // More robust to do a dynamic cast at runtime instead
                 // of compile-time only.
                 return annotationClass.cast(annotation);
             }
         }
         return null;
     }

    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.
            getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
                                            collect(Collectors.toMap(Annotation::annotationType,
                                                                     Function.identity(),
                                                                     ((first,second) -> first),
                                                                     LinkedHashMap::new)),
                                            annotationClass);
    }

    Annotation[] getDeclaredAnnotations();
}

根據單一職責原則,介面中只定義方法,實現都在實現類中實現的,所以Package、Class、Constructor、Field、Method都是有其對應的實現方法的,這裡我們不關心他們是怎麼實現的,而檢視這幾個方法都是做什麼的,這裡有getDeclaredAnnotation和getAnnotation兩個方法,Declared是自獲取自身的註解,不包括繼承過來的,而getAnnotation是或者所有註解,包括繼承過來的。

//判斷指定型別的註解是否存在於此元素上
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
	return getAnnotation(annotationClass) != null;
}
//獲取物件上單個指定註解
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//獲取物件上面所有的註解
Annotation[] getAnnotations();
//獲取物件上面指定型別的註解陣列
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
     T[] result = getDeclaredAnnotationsByType(annotationClass);

     if (result.length == 0 && // Neither directly nor indirectly present
         this instanceof Class && // the element is a class
         AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
         Class<?> superClass = ((Class<?>) this).getSuperclass();
         if (superClass != null) {
             // Determine if the annotation is associated with the
             // superclass
             result = superClass.getAnnotationsByType(annotationClass);
         }
     }

     return result;
 }

前幾個都沒問題,後面getAnnotationsByType是JDK1.8新增的屬性,我們來舉例說明:

首先定義兩個註解

@Repeatable(MultipleAnnotation.class)
public @interface TypeTestAnnotation {

    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultipleAnnotation {

    TypeTestAnnotation[] value();
}

public class ZhangSan {

    @TypeTestAnnotation("第一個")
    @TypeTestAnnotation("第二個")
    @TypeTestAnnotation("第三個")
    String language;

    public static void main(String[] args) throws NoSuchFieldException {

        TypeTestAnnotation[] languages = ZhangSan.class.getDeclaredField("language").getDeclaredAnnotationsByType(TypeTestAnnotation.class);
        for (TypeTestAnnotation language : languages) {
            System.out.println(language);
        }
    }
}

out:
@com.demo.annotation.TypeTestAnnotation(value=第一個)
@com.demo.annotation.TypeTestAnnotation(value=第二個)
@com.demo.annotation.TypeTestAnnotation(value=第三個)

其中@Repeatable註解也是在JDK1.8中新增的,旨在擴充套件重複註解,這裡的TypeTestAnnotation指向儲存註解MultipleAnnotation,1.8之前如果想使用重複註解得這樣定義:

public @interface TypeTestAnnotation {

    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultipleAnnotation {

    TypeTestAnnotation[] value();
}

public class ZhangSan {

    @MultipleAnnotation({@TypeTestAnnotation("第一個"),@TypeTestAnnotation("第二個"),@TypeTestAnnotation("第三個")})
    String language;

    public static void main(String[] args) throws NoSuchFieldException {

        MultipleAnnotation language = ZhangSan.class.getDeclaredField("language").getDeclaredAnnotation(MultipleAnnotation.class);
        System.out.println(language);
    }
}

out:
@com.demo.annotation.MultipleAnnotation(value=[@com.demo.annotation.TypeTestAnnotation(value=第一個), @com.demo.annotation.TypeTestAnnotation(value=第二個), @com.demo.annotation.TypeTestAnnotation(value=第三個)])

這樣做也是可以,但是很明顯結合了@Repeatable的getDeclaredAnnotationsByType更加的人性化。

講完AnnotatedElement之後,進入以下實戰測試:

四、 自定義註解實戰

1. 先定義兩個註解:

//類和方法
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {

    //課程名稱
    String courseName();

    //課程標籤
    String courseTag();

    //課程簡介
    String courseProfile();

    //課程式號
    int courseIndex() default 1503;

}

//欄位
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {

    //名字
    String name();

    //年齡
    int age() default 18;

    //性別
    String gender() default "男";

    //開發語言
    String[] language();
}

2. 在定義一個用於標註的類English:

@CourseInfoAnnotation(
        courseName = "類名字",
        courseTag = "類標籤",
        courseProfile = "類簡介")
public class English {

    @PersonInfoAnnotation(name = "Joker", language = {"Java", "C++", "Python"})
    private String author;

    @CourseInfoAnnotation(
            courseName = "方法名字",
            courseTag = "方法標籤",
            courseProfile = "方法簡介",
    courseIndex = 200)
    public void getCourseInfo(){

    }
}

3. 編寫測試類AnnotationParser:

public class AnnotationParser {

    //解析類的註解
    public static void parseTypeAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.demo.annotation.English");

        //獲取class的註解,而非成員或方法的註解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //獲取單個註解
        CourseInfoAnnotation annotation2 = clazz.getAnnotation(CourseInfoAnnotation.class);
        System.out.println(annotation2);
    }

    //解析成員變數的註解
    public static void parseFieldAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.demo.annotation.English");

        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //是否被PersonInfoAnnotation註解修飾
            boolean hasAnnotation = declaredField.isAnnotationPresent(PersonInfoAnnotation.class);
            if(hasAnnotation){
                PersonInfoAnnotation annotation = declaredField.getAnnotation(PersonInfoAnnotation.class);
                System.out.println(annotation);
            }
        }
    }

    //解析成員方法的註解
    public static void parseMethodAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.demo.annotation.English");

        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            boolean hasAnnotation = declaredMethod.isAnnotationPresent(CourseInfoAnnotation.class);
            if(hasAnnotation){
                CourseInfoAnnotation annotation = declaredMethod.getAnnotation(CourseInfoAnnotation.class);
                System.out.println(annotation);
            }
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        parseTypeAnnotation();
        System.out.println("-----------------------------------------------------");
        parseFieldAnnotation();
        System.out.println("-----------------------------------------------------");
        parseMethodAnnotation();
    }
}

out:
@com.demo.annotation.CourseInfoAnnotation(courseIndex=1503, courseName=類名字, courseTag=類標籤, courseProfile=類簡介)
@com.demo.annotation.CourseInfoAnnotation(courseIndex=1503, courseName=類名字, courseTag=類標籤, courseProfile=類簡介)
-----------------------------------------------------
@com.demo.annotation.PersonInfoAnnotation(gender=男, age=18, name=Joker, language=[Java, C++, Python])
-----------------------------------------------------
@com.demo.annotation.CourseInfoAnnotation(courseIndex=200, courseName=方法名字, courseTag=方法標籤, courseProfile=方法簡介)

結果無誤,此篇結。