第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

Java 注解

本小節(jié)我們將學(xué)習(xí) Java5 引入的一種機制 —— 注解(Annotation)。通過本小節(jié)的學(xué)習(xí),你將了解什么是注解,注解的作用,Java 中內(nèi)置注解有哪些以及注解的分類,如何自定義注解,如何處理注解等內(nèi)容。

1. 什么是注解

Java 注解(Annotation)又稱為 Java 標(biāo)注,是 Java5開始支持加入源代碼的特殊語法元數(shù)據(jù)。Java 語言中的類、方法、變量、參數(shù)和包等都可以被標(biāo)注。Java 標(biāo)注可以通過反射獲取標(biāo)注的內(nèi)容。在編譯器生成class文件時,標(biāo)注可以被嵌入到字節(jié)碼中。Java 虛擬機可以保留標(biāo)注內(nèi)容,在運行時可以獲取到標(biāo)注內(nèi)容。

注解是一種用于做標(biāo)注的“元數(shù)據(jù)”,什么意思呢?你可以將注解理解為一個標(biāo)簽,這個標(biāo)簽可以標(biāo)記類、方法、變量、參數(shù)和包。

回想我們學(xué)習(xí)繼承時,子類若重寫了父類的方法,可以在子類重寫的方法上使用@Override注解:

@Override 注解標(biāo)注在子類重寫的方法上,可檢查該方法是否正確地重寫了父類的方法,如有錯誤將會編譯報錯。

2. 注解的作用

2.1 內(nèi)置的注解

我們先看一下 Java 提供了哪些內(nèi)置注解,以及這些注解的作用。(大致了解即可)

Java 定義了一套注解,共有 10 個,5 個在 java.lang 包中,剩下 5 個在 java.lang.annotation 包中。

2.1.1 用在代碼的注解

  • @Override:檢查該方法是否正確地重寫了父類的方法。如果重寫錯誤,會報編譯錯誤;

  • @Deprecated:標(biāo)記過時方法。如果使用該方法,會報編譯警告;

  • @SuppressWarnings:指示編譯器去忽略注解中聲明的警告;

  • @SafeVarargs:Java 7 開始支持,忽略任何使用參數(shù)為泛型變量的方法或構(gòu)造函數(shù)調(diào)用產(chǎn)生的警告;

  • @FunctionalInterface:Java 8 開始支持,標(biāo)識一個匿名函數(shù)或函數(shù)式接口。

2.1.2 用在其他注解的注解

此類注解也稱為元注解(meta annotation),在下面學(xué)習(xí)定義注解的時候,我們將會詳細(xì)講解。

  • @Retention:標(biāo)識這個注解怎么保存,是只在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問;

  • @Documented:標(biāo)記這些注解是否包含在用戶文檔中;

  • @Target:標(biāo)記這個注解應(yīng)該是哪種 Java 成員;

  • @Inherited:標(biāo)記這個注解是繼承于哪個注解類;

  • @Repeatable:Java 8 開始支持,標(biāo)識某注解可以在同一個聲明上使用多次。

2.2 分類

Java 注解可以分為 3 類:

  1. 由編譯器使用的注解:如@Override@Deprecated、@SupressWarnings等;

  2. 由工具處理.class文件使用的注解:比如有些工具會在加載class的時候,對class做動態(tài)修改,實現(xiàn)一些特殊的功能。這類注解會被編譯進入.class文件,但加載結(jié)束后并不會存在于內(nèi)存中。這類注解只被一些底層庫使用,一般我們不必自己處理;

  3. 在程序運行期間能夠讀取的注解:它們在加載后一直存在于JVM中,這也是最常用的注解。

3. 定義注解

學(xué)會使用注解非常簡單,很多框架都會提供豐富的注解文檔(例如 Spring)。但關(guān)鍵的一點在于定義注解,知道如何定義注解,才能看懂別人定義的注解。

下面我們來定義一個注解。

想要定義一個注解,通??煞譃?3 步:

  1. 創(chuàng)建注解;

  2. 定義注解的參數(shù)和默認(rèn)值;

  3. 用元注解配置注解。

關(guān)于這 3 個步驟是什么意思,如何來做,我們下面將來詳細(xì)講解。

3.1 創(chuàng)建注解

注解通過@interface關(guān)鍵字來定義。例如,我們想要定義一個可用于檢查字符串長度的注解,實例如下:

public @interface Length {
  
}

Tips:通過 @interface 關(guān)鍵字定義注解,通過關(guān)鍵字 interface定義接口。注意兩者不要混淆。

IDEA中,我們可以在新建 Java 類的時候,選擇新建注解:

輸入我們要定義的注解的名稱(遵循類命名規(guī)范),即可創(chuàng)建一個注解:

3.2 定義參數(shù)和默認(rèn)值

注解創(chuàng)建完成后,可以向注解添加一些要接收的參數(shù),下面我們?yōu)?code>@Length注解添加 3 個參數(shù):

public @interface Length {

    int min() default 0;

    int max() default Integer.MAX_VALUE;

    String message() default "長度不合法";
    
}

注解的參數(shù)類似無參數(shù)方法。另外參數(shù)的類型可以是基本數(shù)據(jù)類型、String類型、枚舉類型、Class類型、Annotation類型以及這些類型的數(shù)組。

如果注解中只有一個參數(shù),或者這個參數(shù)是最常用的參數(shù),那么應(yīng)將此參數(shù)命名為value。在調(diào)用注解時,如果參數(shù)名稱是value,且只有一個參數(shù),那么可以省略參數(shù)名稱。(由于此注解沒有最常用特征的參數(shù),沒有使用value

可以使用default關(guān)鍵字來指定參數(shù)的默認(rèn)值,推薦為每個參數(shù)都設(shè)定一個默認(rèn)值。

3.3 用元注解配置注解

在前面學(xué)習(xí) Java 內(nèi)置的注解的時候,我們已經(jīng)了解了元注解,元注解就是用于修飾其他注解的注解。

通常只需使用這些內(nèi)置元注解,就可以基本滿足我們自定義注解的需求。下面我們將會詳解 Java 內(nèi)置的 5 個元注解,你將會了解為什么需要這些元注解。

3.3.1 @Retention

Retention譯為保留。@Retention注解定義了一個注解的生命周期(我們前面對于 Java 注解的分類,就是通過其生命周期來劃定界限的)。它可以有如下幾種取值:

  • RetentionPolicy.SOURCE:注解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視;

  • RetentionPolicy.CLASS:注解只被保留到編譯進行的時候,它并不會被加載到 JVM 中;

  • RetentionPolicy.RUNTIME:注解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。

下面我們使用@Retention注解來指定我們自定義的注解@Length的生命周期,實例如下:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Length {

    int min() default 0;

    int max() default Integer.MAX_VALUE;

    String message() default "長度不合法";

}

上面的代碼中,我們指定 @Length 注解可以在程序運行期間被獲取到。

3.3.2 @Documented

這個元注解的作用很簡單,標(biāo)注了此注解的注解,能夠?qū)⒆⒔庵械脑匕?Javadoc 中去。因此不做過多解釋。

3.3.3 @Target

@Target 注解是最為常用的元注解,我們知道注解可以被應(yīng)用于類、方法、變量、參數(shù)和包等處,@Target 注解可以指定注解能夠被應(yīng)用于源碼中的哪些位置,它可以有如下幾種取值:

  • ElementType.ANNOTATION_TYPE:可以給一個注解進行注解;

  • ElementType.CONSTRUCTOR:可以給構(gòu)造方法進行注解;

  • ElementType.FIELD:可以給屬性進行注解;

  • ElementType.LOCAL_VARIABLE:可以給局部變量進行注解;

  • ElementType.METHOD:可以給方法進行注解;

  • ElementType.PACKAGE:可以給一個包進行注解;

  • ElementType.PARAMETER:可以給一個方法內(nèi)的參數(shù)進行注解;

  • ElementType.TYPE:可以給一個類型進行注解,比如類、接口、枚舉。

例如,我們定義注解@Length只能用在類的屬性上,可以添加一個@Target(ElementType.FIELD)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    int min() default 0;

    int max() default Integer.MAX_VALUE;

    String message() default "長度不合法";

}

@Target注解的參數(shù)也可以接收一個數(shù)組。例如,定義注解@Length可以用在屬性或局部變量上:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {

    int min() default 0;

    int max() default Integer.MAX_VALUE;

    String message() default "長度不合法";

}

至此,我們就完成了 @Length 注解的定義。下面,我們再來看下剩余的兩個元注解。

3.3.4 @Inherited

使用@Inherited定義子類是否可繼承父類定義的注解。@Inherited僅針對@Target(ElementType.TYPE)類型的注解有效,并且僅針對類的繼承有效,對接口的繼承無效:

@Inherited
@Target(ElementType.TYPE)
public @interface TestAnnotation {
  	String value() default "test";
}

在使用的時候,如果一個類用到了@TestAnnotation

@TestAnnotation("測試注解")
public class Pet {
}

則它的子類默認(rèn)也定義了該注解:

public class Cat extends Pet {  
}

3.3.5 @Repeatable

使用@Repeatable這個元注解可以定義注解是否可重復(fù)。

例如,一個注解用于標(biāo)注一個人的角色,他可以是學(xué)生,也可以是生活委員。

@Target(ElementType.TYPE)
@Repeatable(Roles.class)
public @interface Role {
    String value() default "";
}

@Target(ElementType.TYPE)
public @interface Roles {
    Role[] value();
}

@Repeatable 元注解標(biāo)注了@Role。而 @Repeatable 后面括號中的類相當(dāng)于一個容器注解,按照規(guī)定,它里面必須要有一個 value 的屬性,屬性類型是一個被 @Repeatable 注解過的注解數(shù)組。

經(jīng)過@Repeatable修飾后,在某個類型聲明處,就可以添加多個@Role注解:

@Role("學(xué)生")
@Role("生活委員")
public class Student {
}

4. 處理注解

4.1 嘗試使用注解

我們已經(jīng)完成了@Length注解的定義:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {

    int min() default 0;

    int max() default Integer.MAX_VALUE;

    String message() default "長度不合法";

}

現(xiàn)在就可以在字段上標(biāo)注這個注解了:

public class Student {

    // 標(biāo)注注解
    @Length(min = 2, max = 5, message = "昵稱的長度必須在2~5之間")
    private String nickname;

    public Student(String nickname) {
        this.setNickname(nickname);
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public static void main(String[] args) {
        // 實例化對象
        Student student = new Student("我的名字很長很長");
        System.out.println(student.getNickname());
    }
}

上面代碼中,我們在Student類中的nickname字段上標(biāo)注了@Length注解,限定了其長度。

那么現(xiàn)在,是不是將其標(biāo)注在字段上面就可以自動檢查字段的長度了呢?答案是否定的。

運行過程如下,昵稱長度不合法,但并沒有拋出任何異常:

Java 的注解本身對代碼邏輯沒有任何影響,它只是一個標(biāo)注。想要檢查字段的長度,就要讀取注解,處理注解的參數(shù),可以使用反射機制來處理注解。

4.2 通過反射讀取注解

我們先來學(xué)習(xí)一下通過反射讀取注解內(nèi)容相關(guān)的 API。

通過反射,判斷某個注解是否存在于Class、Field、MethodConstructor

  • Class.isAnnotationPresent(Class)

  • Field.isAnnotationPresent(Class)

  • Method.isAnnotationPresent(Class)

  • Constructor.isAnnotationPresent(Class)

isAnnotationPresent()方法的返回值是布爾類型,例如,判斷Student類中的nickname字段上,是否存在@Length注解:

boolean isLengthPresent = Student.class.getDeclaredField("nickname").isAnnotationPresent(Length.class);

通過反射,獲取 Annotation 對象:

使用反射 API 讀取Annotation:

  • Class.getAnnotation(Class)

  • Field.getAnnotation(Class)

  • Method.getAnnotation(Class)

  • Constructor.getAnnotation(Class)

例如,獲取nickname字段標(biāo)注的@Length注解:

Length annotation = Student.class.getDeclaredField("nickname").getAnnotation(Length.class);

通過反射讀取注解的完整實例如下:

public class Student {

    // 標(biāo)注注解
    @Length(min = 2, max = 5, message = "昵稱的長度必須在2~6之間")
    private String nickname;

    public Student(String nickname) {
        this.setNickname(nickname);
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public static void main(String[] args) throws NoSuchFieldException {
        boolean isLengthPresent = Student.class.getDeclaredField("nickname").isAnnotationPresent(Length.class);
        if (isLengthPresent) {
            Length annotation = Student.class.getDeclaredField("nickname").getAnnotation(Length.class);
            // 獲取注解的參數(shù)值
            int min = annotation.min();
            int max = annotation.max();
            String message = annotation.message();
            // 打印參數(shù)值
            System.out.println("min=" + min);
            System.out.println("max=" + max);
            System.out.println("message=" + message);
        } else {
            System.out.println("沒有在nickname字段上找到@Length注解");
        }
    }
}

運行結(jié)果:

min=2  
max=5  
message=昵稱的長度必須在2~6之間

運行過程如下:

4.3 編寫校驗方法

獲取到了注解以及其內(nèi)容,我們就可以編寫一個校驗方法,來校驗字段長度是否合法了。我們在Student類中新增一個checkFieldLength()方法,用于檢查字段長度是否合法,如果不合法則拋出異常。

完整實例如下:

import java.lang.reflect.Field;

public class Student {

    // 標(biāo)注注解
    @Length(min = 2, max = 5, message = "昵稱的長度必須在2~5之間")
    private String nickname;

    public Student(String nickname) {
        this.setNickname(nickname);
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void checkFieldLength(Student student) throws IllegalAccessException {
        // 遍歷所有Field
        for (Field field: student.getClass().getDeclaredFields()) {
            // 獲取注解
            Length annotation = field.getAnnotation(Length.class);
            if (annotation != null) {
                // 獲取字段
                Object o = field.get(student);
                if (o instanceof String) {
                    String stringField = (String) o;
                    if (stringField.length() < annotation.min() || stringField.length() > annotation.max()) {
                        throw new IllegalArgumentException(field.getName() + ":" + annotation.message());
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Student student = new Student("小");
        student.checkFieldLength(student);
    }
}

運行結(jié)果:

Exception in thread "main" java.lang.IllegalArgumentException: nickname昵稱的長度必須在2~5之間
	at Student.checkFieldLength(Student.java:32)
	at Student.main(Student.java:41)

運行過程如下:

5. 小結(jié)

通過本小節(jié)的學(xué)習(xí),我們知道了注解是 Java 語言的一種標(biāo)注,Java 內(nèi)置 10 個注解,要大致了解每個注解的作用。使用@interface關(guān)鍵字自定義注解;為注解定義參數(shù)的時候要注意其參數(shù)的類型,推薦為每個參數(shù)都設(shè)置默認(rèn)值;想要自定義注解,必須了解 Java 中內(nèi)置的 5 個元注解如何使用。

我們自定義的注解通常是用于運行時讀取的,因此必須使用@Retention(RetentionPolicy.RUNTIME)進行標(biāo)注。本小節(jié)的概念較多且較為抽象,建議讀者親手去編寫幾個注解,再來閱讀本節(jié)內(nèi)容會有更好的理解。