註解 & 反射

HuDu發表於2022-02-15

一、註解

1.1、什麼是註解

  • Annotation 是 JDK 5.0 開始引入的
  • Annotation 的作用:
    • 不是程式本身,可以對程式作出解釋。(這一點和註釋(註解)沒什麼區別)
    • 不可以被其他程式(比如:編譯器等)讀取
  • Annotation 的格式:
    • 註解是以@註釋名在程式碼中存在的,還可以新增一些引數值,例如:@SuppressWarning(value=”unchecked”)
  • Annotation 使用範圍(packageclassmethodfield 等上面),相當於給它們新增了額外的輔助資訊,我們可以通過反射機制程式設計通過對這些後設資料的訪問。

1.2、註解分類

1.2.1、內建註解

  • @Override:定義在 java.lang.Override 中,此註解只適用於修辭方法,表示一個方法宣告打算重寫超類中的另一個方法宣告。

  • @Deprecated:定義在 java.lang.Deprecated 中,此註解可用於修辭方法、屬性、類,表示不鼓勵使用這樣的元素,通常因為它存在危險或存在更好的選擇。

  • @SupressWarning:定義在 java.lang.SupressWarning 中,用來抑制編譯時的警告資訊

    • 與前兩種註釋有所不同,你需要新增一個引數才能正常使用,這些引數都是已經定義好了的。
      • @SuppressWarning("all")
      • @SuppressWarning("unchecked")
      • @SuppressWarning(value={"unchecked","deprecation"})
  • @SafeVarargs:當使用可變數量的引數的時候,而引數的型別又是泛型T的話,就會出現警告。 這個時候,就使用@SafeVarargs來去掉這個警告。只能用在引數長度可變的方法或構造方法上,且方法必須宣告為static或final,否則會出現編譯錯誤。

  • @FunctionalInterface:用於約定函式式介面,函式式介面其存在的意義,主要是配合Lambda 表示式 來使用

    1.2.2、元註解

  • 元註解作用就是負責註解其它註解,Java 定義了 4 個標準的 meta-annotation 型別,他們被用來提供對其他 annotation 型別作說明

  • 這些型別和她們所支援的類在java.lang.annotation包中可以找到。(@Target,@Retention,Documented,Inherited)

    • @Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
    • @Retention:表示需要在什麼級別保留註解資訊,用於描述註解的生命週期(Source<Clas<Runtime)
    • @Documented:說明該註解將被包含在 javadoc 中
    • @Inherited:說明子類可以繼承父類中的該註解
    • @Repeatable:當沒有@Repeatable修飾的時候,註解在同一個位置,只能出現一次

@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: 註解在java檔案程式設計成.class檔案後,依然存在,但是執行起來後就沒了。@Retention的預設值,即當沒有顯式指定@Retention的時候,就會是這種型別。
RetentionPolicy.RUNTIME: 註解在執行起來之後依然存在,程式可以通過反射獲取這些資訊,自定義註解@JDBCConfig 就是這樣。

1.3、自定義註解

下面簡單自定義一個註解,如果註解中是有一個屬性,且屬性名是 value,那麼在使用註解時刻可以直接填寫屬性值,不需要再使用 屬性名 = 屬性值,如果自定了預設值,則可以不需要填寫

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation{
    private String value() defalut "";
}

二、反射

2.1、什麼是反射

  • Reflection(反射)是 Java 被視為動態語言的關鍵,反射機制允許程式在執行期間藉助於 Reflection API 取得任何類的內部資訊,並能夠直接操作任意物件的內部屬性以及方法。Class c = Class.forName("java.lang.String")
  • 載入完類後,在堆記憶體的方法區中就產生了一個 Class 型別的物件(一個類只有一個 Class 物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射

2.2、Java 反射機制研究及應用

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的成員變數和方法
  • 在執行時獲取範型資訊
  • 在執行時呼叫任意一個物件的成員方法和變數
  • 在執行時處理註解
  • 生成動態代理
  • 。。。

2.3、Java 反射優點和缺點

優點:可以實現動態建立物件和編譯,靈活性高
缺點:對效能有影。使用反射基本上是一種解釋操作,我們可以告訴 JVM,我們希望做什麼並且它滿足我們的需求。這類操作總是慢於直接執行相同的操作。

2.4、反射相關的主要 API

  • java.lang.Class:代表一個類
  • java.lang.reflection.Method:代表類的方法
  • java.lang.reflection.Field:代表類的成員變數
  • java.lang.reflection.Constructor:代表類的構造器
  • 。。。

Java 中,在 Object 類中定義了以下方法,此方法將被所有子類繼承 public final native Class<?> getClass();,此方法返回值型別是一個 Class 類,此類是 Java 反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,即:可以通過物件反射求出類的名稱。

2.5、Class 類的常用方法

方法名 功能說明
static Class forName(String name) 返回指定類名 name 的 Class 物件
T newInstance() 呼叫預設建構函式,返貨 Class 物件的一個例項
String getName() 返回此 Class 物件所表示的實體(類,介面,陣列類或 void)
Class getSuperClass() 返回當前 Class 物件的父類的 Class 物件
Class[] getInterfaces() 獲取當前 Class 物件的介面
ClassLoader getClassLoader() 返回該類的類載入器
Constructor[] getConstructor 返回一個包含某些 Constructor 物件的陣列
Method getMethod(String name,Class… T) 返回一個 Method 物件,此物件的形參型別為 paramType
Field[] getDeclaredFields() 返回 Field 物件的一個陣列

注意,對於 private 修飾的成員,需要先設定關閉安全檢驗 setAccessible(true)

2.6、獲取 Class 物件的例項

1.若已知具體的類,通過該類的 class 屬性獲取,該方法最為安全可靠,程式效能最高。
Class clazz = Person.class;
2.已知某個類的例項,呼叫該例項的 getClass() 方法獲取 Class 物件
Class clazz = person.getClass();
3.已知一個全類名,且該類在類路徑下,可通過 Class 類的靜態方法 forName()獲取,可能丟擲 ClassNotFoundException
Class clazz = Class.forName("demo01.Person")
4.內建基本資料型別可以直接使用類名.Type
5.還可以利用 ClassLoader

獲取類的例項的效率:new > 反射(關閉安全檢驗)>反射

2.7、類載入過程

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟對該類進行初始化。
類的載入(Load):將類的 class 檔案讀入記憶體,併為之建立一個 java.lang.Class 物件。此過程由類載入器完成
類連結(Link):將類的二進位制資料合併到 JRE 中
類的初始化(Initialize):JVM 負責對類進行初始化

2.8、類的載入與 ClassLoader 的理解

2.8.1、類的載入

  • 載入:將 class 檔案位元組碼檔案內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個代表這個類的 java.lang.Class 物件。
  • 連結:將 Java 類的二進位制程式碼合併到 JVM 的執行狀態中的過程
    • 驗證:確保載入的類資訊符合 JVM 規範,沒有安全方面的問題
    • 準備:正式為類變數(static) 分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配。
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
  • 初始化:
    • 執行類構造器 <clinit>() 方法的過程。類構造器<clinit>()方法是由編譯期自劫收集類中所有類変量的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)
    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
    • 虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確加鎖和同步。

2.8.2、類載入器的作用

類載入器作用是用來把類(class)裝載進記憶體的。JVM 規範定義瞭如下類的類的載入器。
自定義類載入器->System ClassLoader->Extension ClassLoader->Bootstap ClassLoader
裝載的過程是自底向上檢查是否已裝載,即從左到右
載入類的順序是自頂向下

引導類載入器:用 C++ 編寫的。是 JVM 自帶的類載入器,負責 Java 平臺核心庫,用來裝載核心類庫。該載入器無法直接獲取
擴充類載入器:負責 jre/lib/ext 目錄下的 jar 包或 -D java.ext.dirs 指定目錄下的 jar 包裝入工作庫
系統類載入器:負責 java -classpath 或 -D java.class.path 所指的目錄下的類與 jar 包裝入工作,是最常用的載入器。

2.9、反射操作泛型

  • Java採用泛型擦除的機制來引入泛型, Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換問題,但是, 一旦編譯完成,所有和泛型有關的型別全部擦除
  • 為了通過反射操作這些型別,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別。

ParameterizedType:表示一種引數化型別,比如Collection<String>
GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
TypeVariable:各種型別的公共父介面
WildcardType:代表一種萬用字元型別表示式

示例

public class ReflectionGetGeneric {

    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("test01");
    }

    public Map<String,User> test02() {
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method01 = ReflectionGetGeneric.class.getMethod("test01", Map.class, List.class);
        // 獲得範型的引數型別
        Type[] genericParameterTypes = method01.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            // 範型的引數型別是否等於結構化的引數型別
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method method02 = ReflectionGetGeneric.class.getMethod("test02", null);
        Type genericReturnType = method02.getGenericReturnType();
        System.out.println(genericReturnType);
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

    }

}

輸出結果

java.util.Map<java.lang.String, annotation_reflection._04.User>
class java.lang.String
class annotation_reflection._04.User
java.util.List<annotation_reflection._04.User>
class annotation_reflection._04.User
java.util.Map<java.lang.String, annotation_reflection._04.User>
class java.lang.String
class annotation_reflection._04.User

2.10、ORM(Object Relationship Mapping)練習

很多框架中都會使用到註解開發,例如 MybatisPlus,只需要在資料庫對映的實體類中,加上@TableName即可對映表名,@TableField即可對映表中的欄位名

下面通過程式碼示例

自定義兩個註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableName {
    String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TableField {
    String columnName();
    String type();
    int length();
}

定義實體類

@TableName("db_student")
public class Student {
    @TableField(columnName = "db_id",type = "int",length = 10)
    private int id;
    @TableField(columnName = "db_age",type = "int",length = 10)
    private int age;
    @TableField(columnName = "db_name",type = "varchar",length = 3)
    private String name;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }
}

測試

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("annotation_reflection._07.Student2");
        // 通過反射獲得註解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 獲得註解的具體的值
        TableName tableAnnotation = (TableName) c1.getAnnotation(TableName.class);
        System.out.println(tableAnnotation.value());

        // 獲得指定的註解
        Field name = c1.getDeclaredField("name");
        TableField fieldAnnotation = name.getAnnotation(TableField.class);
        System.out.println(fieldAnnotation.columnName());
        System.out.println(fieldAnnotation.type());
        System.out.println(fieldAnnotation.length());
    }

執行結果

@annotation_reflection._07.TableName(value=db_student)
db_student
db_name
varchar
3
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章