初識 Java 註解

打雜匠發表於2018-11-16

正在學習的小白。不當之處,請多多指教

目錄

  • 前言
  • 自定義註解的使用
  • 元註解
  • 註解的解析反射
  • 解析註解的作用(引入IOC概念)

前言

java中有四大型別,其中三個是:列舉,介面,類。

今天簡單認識一下第四個型別:註解。

什麼是註解

Annotation 這裡是"註解"的意思。
除此之外,這個單詞患有一個“註釋”的意思。我們都知道,註釋是給程式設計師看的。那麼註解呢?
註解是給程式看的,所以Annotation既有註解也有註釋的意思
複製程式碼

我們很早就見過一些註解:jdk中的@Override,@FunctionalInterface,Junit框架的@Test等。

註解的基本語法

列舉,介面,類,這個三個型別我們都寫過(他們的原始碼 ),基本語法,想必大家都知道。

那麼註解有原始碼嗎?答案是肯定的,jvm沒有那麼厲害,不可能僅憑一個@+一個單詞就知道程式想表達什麼。

我們以@FunctionalInterface(關於這個註解不清楚的可以參考函數語言程式設計的內容)為例:

@FunctionalInterface
interface UsB{
    void show();
}
複製程式碼

按住Ctrl點選註解進入,就可以看到@FunctionalInterface的原始碼:

package java.lang;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製程式碼

先不著急認識這些是什麼意思,看懂其構成就可以,然後我們照貓畫虎,自己寫一個:

/**
* 自定義註解
*/
@interface MyAnnotation{ }
複製程式碼
  1. 於是,我們完成了一個自定義註解,由此,可以基本知道註解的基本構成:

@interface 註解名字{ }

那麼,註解有像其他普通類一樣的屬性嗎?

  1. 有,但是,註解的屬性不太一樣,他是這樣寫的,

String name(); 註解中 這是屬性,不是介面中的方法。

  1. 既然是屬性,那就可以賦值:

註解屬性賦值語法:String name() default "屬性";

(ps:可以賦值,但一般不再內部賦值,給外部使用者賦值)

  1. 那麼屬性這樣寫,註解有方法嗎?有的話的方法該怎麼寫?

不好意思,註解是沒有方法的!

  1. 普通類中的屬性,可以是任意型別,那麼註解也一樣嗎??

註解對屬性型別是有要求的:

8個基本資料型別 / 字串型別 / Class型別 / 註解型別 / 列舉型別 及其一維陣列

  1. 屬性補充:

註解中只有一個屬性, 那麼請將該屬性定義為 value 名稱. 好處: 使用該註解時可以省略 value=

以上6點就是註解的基本語法。

自定義註解的使用

之前我們使用註解,都是固定的,比如@Override只能在(重寫)方法使用,@FunctionalInterface只能在(有且僅有一個要實現的方法的)介面使用。乳溝隨便使用,就會立刻編譯報錯。 那麼,使用我們剛剛自定義的註解,使用上有限制嗎?

@MyAnnotation("省略了value")//可以用在類上
public class AnnotationDemo {
    @MyAnnotation("zhangsan") String name;//可以用在屬性上
    @MyAnnotation("show") //可以用在方法上
    public void show(){}
}

//自定義註解
@interface MyAnnotation{
    //屬性
   String value();
}
複製程式碼

元註解

這樣看來,我們目前自定義的註解是沒有任何使用位置的限制的,再回頭看看,前面@FunctionalInterface註解的原始碼,或者@Override的原始碼,發現我們自定義的註解少了幾樣東西。沒錯,少了一些註解。準確說,少了一些 “元註解”

package java.lang;
import java.lang.annotation.*;
//三個元註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製程式碼

什麼是元註解,“元”意為:最開始,初始的意思,那麼元註解就是其實的註解,或者叫,註解的註解。是用來修飾說明註解的。

元註解都來自於:java.lang.annotation.* 下

先來認識一下元註解:

@Target

意為目標,也就是說明註解的使用範圍

來看一下@Target的原始碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
複製程式碼

可以看到,他也有自己的元註解,和一個一維陣列屬性ElementType[],進入ElementType[],我們可以看到,ElementType是一個列舉類(控制文字長度,去除了所有原始碼的註釋):

public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
}
複製程式碼

這些列舉型別很容易看出,Target註解的value值,可以是這些列舉元素,例如:FIELD表示使用在屬性上,METHOD可以使用在方法上,等等,不在一一說明。

使用: 、

@Target({ElementType.FIELD,ElementType.METHOD}) 注意,多個值用中括號,屬性名為value,可省略

可以加在我們前面寫的自定義註解上看看效果。

@Retention

意為,保留策略,又稱之為生命週期。

我們繼續進入@Retention的原始碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
複製程式碼

也只有一個屬性,進入RetentionPolicy檢視:

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}
複製程式碼

RetentionPolicy也是一個列舉類,只有三個值。

這三個值很好理解,他與java程式的宣告週期是一一對應的:

原始碼階段(SOURCE),編譯階段(CLASS),執行階段(RUNTIME)。

@Retention的屬性不是陣列,所以只能選擇一個值

如:@Retention(RetentionPolicy.CLASS)

ps : RetentionPolicy.RUNTIME 最常用,因為通常和反射結合使用,而反射是在執行時操作類。

註解的反射解析

(不瞭解反射可以參考我的另一篇筆記:Java 反射機制那些事

我們通過程式碼的方式,簡單說明下,如何利用反射解析註解

先來準備一個 Student類:

public class Student {
    //一個屬性
    public String name;
    public Student() { }
    //一個構造
    public Student(String name) { this.name = name; }
    //一個方法
    public void show(String msg){
        System.out.println("show方法 = " + msg);
    }
    //重寫toString
    @Override
    public String toString() {
        return "Student{name= "+name+"}';
    }
}
複製程式碼

再來寫一個自定義註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface MyAnnotation{
   String value();
}
複製程式碼

然後,根據自定義註解的作用範圍,在Student類上加上我們自定義的註解,然後賦上一些值:

    @MyAnnotation("小明")
    public Student(String name) {
        this.name = name;
    }
    @MyAnnotation("小紅來了")
    public void show(String msg){
        System.out.println("show方法 = " + msg);
    }
複製程式碼

在新建一個類,實現我們的反射部分程式碼,這裡我的類就叫做:AnnotationDemo

補充:

註解 某種意義上講可以作為 一種配置檔案(常見的配置檔案 .properties 或者 .xml)

​ 既然寫了一個檔案,就要去對他進行一些 讀寫 操作,如果不去讀取並使用他的內容,呢這個(配置)檔案存在在程式中有什麼意義??檔案是儲存資訊資料的,所以檔案不讀出來,是沒有意義的。

​ 而讀出配置檔案的內容 稱之為 解析!

​ 註解有個解析技術,叫做: 反射!

​ 寫了註解,就相當於,寫了配置檔案不讀取!是沒有意義的。

反射是在執行時,操作Class物件,註解寫在Student類中,所以,反射可以操作Student的Class物件。

我們就先AnnotationDemo類中在利用反射獲取一個Student裡面的自定義註解:

public class AnnotationDemo {
	public static void main(String[] args) throws NoSuchMethodException {
        //1.獲取Student的Class物件
        Class<?> clazz = Student.class;
        //2.先從構造器開刀,找到構造器
        Constructor<?> constructor = clazz.getConstructor(String.class);
        /**
         * 3.使用構造起的方法 isAnnotationPresent(),
         *   方法意為:有沒有(引數)註解存在?注意:(一個方法。類等可以有多個註解)
         *   引數:註解型別的class物件
         *   返回值:存在(true),
         */
        boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
        //如果存在,來獲取這個註解
        if (annotation) {
            /*
                getAnnotation(註解.class)獲取註解
                此時獲取到 Student滿參構造上的註解
             */
            MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
            //註解有個屬性叫value
            String value = myAnnotation.value();
            System.out.println("value = " + value);        //value = 小明
        }
    }
}
複製程式碼

以上程式碼,就將 Student滿參構造上的註解的屬性值讀取出來了。

解析註解作用(引入IOC概念)

還是和反射一樣的問題?這樣解析註解有什麼用?

這要結合具體場景,有些專案中可能要自己定義註解使用,而最多使用的地方就是框架。

題外話——引入IOC概念

上面的示例程式碼我們可以看到,AnnotationDemo類中的一系列程式碼,就獲取到了Student的滿參構造的註解的value值。那麼獲取到註解的屬性值,我們就可以將值反轉到(傳入)這個構造裡面去。

這個就叫做控制反轉(IOC)

怎麼傳值呢?繼續看程式碼!

//部分程式碼和上面一樣,註釋省略
public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        Class<?> clazz = Student.class;
        Constructor<?> constructor = clazz.getConstructor(String.class);
        boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
        if (annotation) {
            MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
            String value = myAnnotation.value();
            System.out.println("value = " + value);        //value = 小明
            
            /*
                利用  newInstance 方法,就可以獲得Student例項
             */
            Object obj = constructor.newInstance(value);
            System.out.println("obj = " + obj);

        }
    }
複製程式碼

​ 我們可以想一下,假如AnnotationDemo類和自定義註解,不是我們所寫,是一種別人寫的框架,自己從來不知道這樣一些程式碼,而只是用一個註解,傳了個值,就構造出了一個例項物件。這就是框架技術的一部分底層原理。

​ 接下來解析Student的show方法的註解:

public class AnnotationDemo {
    public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        //獲取Class物件
        Class<?> clazz = Student.class;
        //獲取方法
        Method show = clazz.getMethod("show", String.class);
        boolean b = show.isAnnotationPresent(MyAnnotation.class);
        if (b) {
            //每個反射物件都有這樣一個方法,獲取註解
            MyAnnotation annotation = show.getAnnotation(MyAnnotation.class);
            String value = annotation.value();
           show.invoke(clazz.newInstance(), value);
           //執行,檢視結果
                //show方法 = 小紅來了
        }
    }
}
複製程式碼

相關文章