1. 程式人生 > 實用技巧 >Java註解和反射筆記

Java註解和反射筆記

1 註解

1.1 定義

Annotation是從JDK1.5開始引入的技術

作用

  • 不是程式本身,可以對程式作出解釋
  • 可以被其他程式(編譯器等)讀取

格式

  • @註釋名,可以新增一些數值
  • 註解可以附加在package,class,method,field上面,可以通過反射機制實現對這些元資料的訪問

1.2 內建註解

  • @Override:定義在java.lang.Override中,只適用於修飾方法,表示一個方法宣告打算重寫超類中的另一個方法宣告
  • @Deprecated:定義在java.lang.Deprecated中,可以修飾方法,屬性,類,表示不建議使用這樣的元素,有更好的選擇
  • @SuppressWarnings:定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊

1.3 元註解

元註解的作用是負責註解其他註解,Java定義了4個標註的meta-annotation型別

  • @Target:用於描述註解的使用範圍(類,方法,屬性等)
  • @Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期
    • SOURCE < CLASS < RUNTIME
  • @Documented:說明該註解將被包含在javadoc中
  • @Inherited:說明子類可以繼承父類中的該註解

1.4 自定義註解

  • 使用@interface自定義註解,自動繼承java.lang.annotation.Annotation介面
  • 其中的每個方法實際上是聲明瞭配置引數,方法的名稱就是引數的名稱,返回值的型別就是引數的型別
  • 用default來宣告引數的預設值
  • 如果只有一個引數成員,一般引數名為value,且定義為value後,使用時可以省略引數名value,直接寫值
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
    
    //註解的引數:引數型別+引數名()
    String name() default "";	//預設為空
    
    int age() default 0;
    
    int id() default -1;	//預設值為-1,代表不存在
    
    String[] jobs();
}

2 靜態語言和動態語言

  • 動態語言
    • 在執行時可以改變其結構,新的函式、物件、程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化
    • Object-C,C#,JavaScript,PHP,Python
  • 靜態語言
    • 執行時結構不可改變
    • C,C++,Java

Java不是動態語言,但是可以利用反射機制獲得類似動態語言的特性

3 反射

3.1 概述

反射機制允許程式在執行期間藉助於Reflection API獲得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法

Class c = Class.forName("java.lang.String");

載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件包含了完整的類的結構資訊,通過這個類可以看到類的結構

  • 正常方式
    • 引入需要的包類名稱 ---> 通過new例項化 ---> 取得例項化物件
  • 反射方式
    • 例項化物件 ---> getClass()方法 ---> 得到完整的包類結構資訊

3.2 Class類

對於每個類而言,jre都會為其保留一個不變的Class型別的物件。一個Class物件包含了特定某個結構的資訊

  • Class本身也是一個類
  • Class物件只能由系統建立物件
  • 一個載入的類在JVM中只會有一個Class例項
  • 一個Class物件對應的是一個載入到JVM中一個.class檔案
  • 每個類的例項都會記得自己是由哪個Class例項所生成的
  • Class類是Reflection的根源,針對任何想動態載入、執行的類,只有先獲得相應的Class物件

3.3 獲得反射物件

首先,哪些型別有Class物件

  • class:外部類,成員內部類,靜態內部類,區域性內部類,匿名內部類
  • interface:介面
  • []:陣列
    • 只要元素型別和維度一樣,就是同一個Class,不管資料長度
  • enum:列舉
  • annotation:註解@interface
  • primitive type:基本資料型別
  • void

獲得Class物件的方法

  • 已知具體的類,通過類的class屬性獲取,這種方法最安全快速

    • Class clazz = Person.class;
      
  • 已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件

    • Class clazz = persion.getClass();
      
  • 已知類的全類名,通過Class類的靜態方法forName()獲取,可能丟擲異常

    • Class clazz = Class.forName("com.hjc.pojo.User");
      
  • 內建基本資料型別可以直接用類名.TYPE

  • 利用ClassLoader

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println(person.name);
        
        //通過物件獲得
        Class c1 = person.getClass();
        
        //通過forName獲得
        Class c2 = Class.forName("com.hjc.reflection.Student");
        
        //通過類名.class獲得
        Class c3 = Student.class;
        
        //基本資料型別
        Class c4 = Integer.TYPE;
        
        //獲得父類
        Class c5 = c1.getSuperClass();
    }
}

class Person {
    String name;
    
    //省略建構函式和set/get方法
}

class Student extends Person {
    public Student() {
        this.name = "student";
    }
}

class Teacher extends Person {
    public Teacher() {
        this.name = "teacher";
    }
}

3.4 類載入過程

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過三個步驟對該類進行初始化

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

3.5 類初始化

什麼時候會發生類初始化

  • 類的主動引用(一定會發生類的初始化)
    • 當虛擬機器啟動,先初始化main方法所在的類
    • new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初始化一個類,如果其父類沒有被初始化,則會先初始化其父類
  • 類的被動引用(不會發生類的初始化)
    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化
      • 比如,通過子類引用父類的靜態變數,不會導致子類初始化
    • 通過陣列定義類引用,不會觸發此類的初始化
    • 引用常量不會觸發此類的初始化
      • 常量在連結階段就存入呼叫類的常量池中了
public class Test {
    
    static {
        System.out.println("Main類被載入");
    }
    
    public static void main(String[] args) throws ClassNotFoundException {
        //主動引用
        B b = new B();
        
        //反射
        Class.forName("com.hjc.reflection.B");
        
        //B不會被載入,會載入A
        System.out.println(B.n);
        
        //B不被載入
        B[] array = new B[10];
        System.out.println(B.M);
    }
}

class A {
    
    static {
        System.out.println("父類被載入");
    }
    
    static int n = 2;
}

classs B extends A {
    
    static {
        System.out.println("子類被載入");
        m = 300;
    }
    
    static int m = 100;
    static final int M = 1;
}

3.6 類載入器

類載入器的作用

  • 將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換程方法區的執行時資料結構,在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口

類快取

  • 標準的JavaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間,但JVM垃圾回收機制可以回收這些Class物件

類載入器

  • 引導類載入器
    • 用C++編寫,是JVM自帶的類載入器,負責Java平臺核心庫,用來裝載核心類庫,該載入器無法直接獲取
  • 擴充套件類載入器
    • 負責jre/lib/ext目錄下的jar包裝入工作庫
  • 系統類載入器
    • 負責java -classpath下的jar包裝入工作庫,是最常用的載入器
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        //獲取系統類載入器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        
        //獲取系統類載入器的父類,擴充套件類載入器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        
        //獲取擴充套件類載入器的父類,引導類載入器,無法獲取
        parent = parent.getParent();
        System.out.println(parent);
        
        //測試當前類是由哪個類載入器載入的
        ClassLoader classLoader = Class.forName("com.hjc.reflection.Test").getClassLoader();
        System.out.println(ClassLoader);
        
        //測試jdk內建的類
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(ClassLoader);
    }
}

3.7 獲取執行時類的完整結構

獲得了類的Class物件,那麼我們就可以得到類的執行時資訊

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c = Class.forName("com.hjc.reflection.User");
        
        //獲得類的名字
        System.out.println(c.getName());	//包名+類名
        System.out.println(c.getSimpleName());	//類名
        
        //獲得類的屬性
        Field[] fields = c.getFields();	//只能得到public屬性
        for (Field field : fields) {
            System.out.println(field);
        }
        
        fields = c.getDeclaredFields();	//可以得到全部屬性
        for (Field field : fields) {
            System.out.println(field);
        }
        
        //獲得指定屬性
        Field name = c.getField("name");	//找不到,因為name是private
        System.out.println(name);
        
        Field name = c.getDeclaredField("name");	//可以找到
        System.out.println(name);
        
        //獲得類的方法
        Method[] methods = c.getMethods();	//獲得本類及其父類的全部public方法
        for (Method method : methods) {
            System.out.println(method);
        }
        
        methods = c.getDeclaredMethods();	//獲得本類的全部方法
        for (Method method : methods) {
            System.out.println(method);
        }
        
        //獲得類的指定方法
        Method getName = c.getMethod("getName", null);	//方法名+引數
        Method setName = c.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);
        
        //獲得類的構造器
        Constructor[] constructors = c.getConstructors();	//獲得public
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        
        constructors = c.getDeclaredConstructors();		//獲得全部
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        
        //獲得指定構造器
        Constructor declaredConstructor = c.getDeclaredConstructor(int.class, String.class, int.class);
        System.out.println(declaredConstructor);
    }
}

class User {
    private int id;
    private String name;
    private int age;
    
    //省略構造方法和set/get方法
}

3.8 動態建立物件執行方法

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.hjc.reflection.User");
        
        //構造一個物件
        User user = (User) c.newInstance();	//本質上呼叫了類的無參構造器
        System.out.println(user);
        
        //如果沒有無參構造器,通過有參構造器建立物件
        Constructor constructor = c.getDeclaredConstructor(int.class, String.class, int.class);
        User user = (User) constructor.newInstance(1, "test", 18);
        System.out.println(user);
        
        //反射呼叫方法
        Method setName = c.getDeclaredMethod("setName", String.class);
        setName.invoke(user, "test1");	//invoke傳遞物件和方法引數值
        System.out.println(user);
        
        //反射操作屬性
        Field name = c.getDeclaredField("name");
        //不能直接操作私有屬性,需要關閉程式的安全檢測
        //呼叫屬性或者方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user, "test2");
        System.out.println(user);
    }
}

3.9 效能分析

public class Test {
    //普通方式
    public void test1() {
        User user = new User();
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        
        long endTime = System.currentTimeMillis();
        
        System.out.println("普通方式執行十億次:" + (endTime - startTime) + "ms");
    }
    
    //反射方式
    public void test2() throws NoSuchMethodException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }
        
        long endTime = System.currentTimeMillis();
        
        System.out.println("反射方式執行十億次:" + (endTime - startTime) + "ms");
    }
    
    //反射方式,關閉檢測
    public void test3() throws NoSuchMethodException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }
        
        long endTime = System.currentTimeMillis();
        
        System.out.println("關閉檢測後,反射方式執行十億次:" + (endTime - startTime) + "ms");
    }
    
    public static void main(String[] args) {
        test1();
        test2();
        test3();
    }
}

3.10 反射操作泛型

Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換問題,但是一旦編譯完成,所有和泛型有關的型別全部擦除

為了通過反射操作泛型,Java引入了幾個類

  • ParameterizedType:表示一種引數化型別,如Collection<String>
  • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
  • TypeVariable:各種型別變數的公共父介面
  • WildcardType:代表一種萬用字元型別表示式
public class Test {
    
    public void test1(Map<String, User> map, List<User> list) {
        System.out.println("test1");
    }
    
    public Map<String, User> test2() {
        System.out.println("test2");
        return null;
    }
    
    public static void main(String[] args) {
        Method method = Test.class.getMethod("test1", Map.class, List.class);
        Type[] types = method.getGenericParameterTypes();
        for (Type type : types) {
            System.out.println(type);
            if (type instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println(actualType);
                }
            }
        }
        
        method = Test.class.getMethod("test2", null);
        Type returnType = method.getGenericReturnType();
        System.out.println(resultType);
        if (returnType instanceof ParameterizedType) {
            Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println(actualType);
            }
        }
    }
}

3.11 反射操作註解

很多框架都是通過反射獲取註解資訊,來幫我們解決了很多事情

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.hjc.reflection.Student");
        
        //通過反射獲得註解
        Annotation[] annotations = c.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        
        //獲得註解的值
        Table1 table1 = (Table1) c.getAnnotation(Table1.class);
        String value = table1.value();
        System.out.println(value);
        
        //獲得指定的註解
        Field f = c.getDeclaredField("name");
        Field1 f1 = f.getAnnotation(Field1.class);
        System.out.println(f1.columnName());
        System.out.println(f1.type());
        System.out.println(f1.length());
    }
}

@Table1("db_student")
class Student {
    @Field1(columnName = "db_id", type = "int", length = 10)
    private int id;
    @Field1(columnName = "db_name", type = "varchar", length = 3)
    private String name;
    @Field1(columnName = "db_age", type = "int", length = 10)
    private int age;
    
    //省略構造器和set/get方法
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table1 {
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field1 {
    String columnName();
    String type();
    int length();
}