天天看點

java注解和反射1.注解 java.Annotation2. 反射 java.Reflection

文章目錄

  • 1.注解 java.Annotation
    • 1.1 什麼是注解
    • 1.2 内置注解
    • 1.3 元注解
    • 1.4 自定義注解
  • 2. 反射 java.Reflection
    • 2.1 反射
    • 2.2 得到Class類的方式
    • 2.3 所有類型的Class對象
    • 2.4 擴充:類的加載和ClassLoader
      • 2.4.1 類初始化
    • 2.5 建立運作時類的對象
      • 2.5.1 擷取運作時類的完整結構
      • 2.5.2 動态建立對象、執行方法
        • 2.5.2.1 建立類的對象
        • 2.5.2.2 執行方法
    • 2.6 性能對比分析
    • 2.7 擴充:反射操作泛型
    • 2.8 反射操作注解

1.注解 java.Annotation

1.1 什麼是注解

  1. 注解的作用:
    1. 不是程式本身,可以對程式作出解釋
    2. 可以被其他程式(如編譯器)讀取
  2. 注解的格式:“@注解名”,還可以添加一些參數值。如@Scheduled(cron=” “)
  3. 注解可以附加在包、類、方法等上面,相當于給他們添加了額外的輔助資訊

1.2 内置注解

  1. @Override:表示打算重寫父類中的一個方法
  2. @Deprecated:表示不推薦使用,但可以使用,通常是因為很危險或者存在更好的選擇
  3. @SuppressWarnings:抑制編譯器的警告資訊,需要加參數。
    @SuppressWarnings("all")
    @SuppressWarnings("unchecked")
               

1.3 元注解

  1. 元注解的作用是負責注釋其他注解
  2. 四個元注解:
    注解 作用
    @Target 描述注解的使用範圍
    @Retention 表示需要在什麼級别儲存該注釋資訊,描述注解的生命周期(source<class<runtime)
    @Documented 表示該注解将被包含在javadoc中
    @Inherited 子類可以繼承父類中的該注解
    • @Target:
      public class Test1{
      
          @MyAnnotation
          public void test(){
      
          }
      }
      
      // value的值也可以是一個數組
      @Target(value= ElementType.METHOD)
      @interface MyAnnotation{
      
      }
                 
      這裡使用@Target(value= ElementType.METHOD)表示自己定義的注解MyAnnotation僅在方法上生效,此時若把注解放在類前面就會報錯。
    • @Retention
      @Retention(value = RetentionPolicy.RUNTIME) // 表示該自定義的注解運作時有效
      @interface MyAnnotation{
      
      }
                 

1.4 自定義注解

  • 首先使用@interface自定義注解
    @interface MyAnnotation2{
    }
               
  • 使用元注解定義自定義注解的使用範圍和生命周期
    @Target(value = {ElementType.METHOD,ElementType.TYPE})
    @Retention(value = RetentionPolicy.RUNTIME)
               
  • 給注解定義一些參數
    @interface MyAnnotation2{
    
        // 注解的參數:參數的類型 + 參數名 + ()
        String name() default " ";  // default為預設值
        int age() default 0;
        int id() default -1; // 預設值為-1,代表不存在
    
        String[] schools() default {"NJU"};
    }
               
  • 在方法上使用注解
    public class Test2 {
        // 給注解顯示指派
        @MyAnnotation2(name="java",schools = {"南京大學"})
        public void test(){
        }
    }
               
  • 注意如果注解僅有一個參數,且參數的名字是value時,那麼使用注解時不用顯示的去指派,可以類似這樣去指派:@SuppressWarnings(“all”)

2. 反射 java.Reflection

2.1 反射

  • 反射機制允許程式在執行期間借助Reflection API取得任何類的内部資訊,并能直接操作任意對象的内部屬性和方法。
  • 反射可以通過getClass方法和一個類的執行個體化對象得到完整的類的資訊,簡單來說就是可以通過一個對象得到類
    java注解和反射1.注解 java.Annotation2. 反射 java.Reflection
  • 反射機制提供的功能
    • 在運作時判斷任意一個對象所屬的類
    • 在運作時構造任意一個類的對象
    • 在運作時判斷任意一個類所具有的成員變量和方法
    • 在運作時擷取泛型資訊
    • 在運作時調用任意一個對象的成員變量和方法
    • 在運作時處理注解
    • 生成動态代理
    • ……
  • 對性能有一定的影響
  • 通過代碼
    // 通過反射擷取類的class對象
    Class c = Class.forName("reflection.user");
    System.out.println(c);
               
    列印出來class reflection.user,其中user是自定義的實體類,含有id和name兩個屬性
  • 通過代碼
    Class c2 = Class.forName("reflection.user");
    Class c3 = Class.forName("reflection.user");
    Class c4 = Class.forName("reflection.user");
    System.out.println(c2.hashCode());
    System.out.println(c3.hashCode());
    System.out.println(c4.hashCode());
               
    列印出來的三個數值都是一樣的,表示一個類在記憶體中隻有一個class對象。另外,一個類被加載後,類的整個結構都會被封裝在class對象中。

2.2 得到Class類的方式

  1. 若已知具體的類(User),通過類的class屬性擷取
  2. 已知某個類的執行個體(user),調用該執行個體的getClass()方法擷取Class對象
  3. 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜态方法forName()擷取
  4. 内置基本資料類型可以直接用類名.Type
  5. 利用ClassLoader

實際例子:

package reflection;

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException {
        User user=new Students();
        System.out.println(user.getName());

        // 1:通過對象執行個體獲得
        Class c1=user.getClass();
        System.out.println(c1.hashCode());

        // 2:forName獲得
        Class c2 = Class.forName("reflection.Students");
        System.out.println(c2.hashCode());

        // 3:通過類名.class獲得
        Class c3=Students.class;
        System.out.println(c3.hashCode());
        
    }
}

class User {
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
}

class Students extends User{
    public Students(){
        setName("學生");
    }
}

class Teachers extends User{
    public Teachers(){
        setName("老師");
    }
}
           

main方法的輸出是:

學生
1554874502
1554874502
1554874502
           

可見一個類在記憶體中隻有一個class對象

2.3 所有類型的Class對象

哪些類型可以有Class對象:(隻要元素類型和次元是一樣的,就是同一個Class)

  1. 所有類型的類
  2. interface接口
  3. 數組(注意一個類隻有一個記憶體對象,不同長度的數組的hashcode依舊是一樣的。隻要元素類型和次元是一樣的,就是同一個Class)
  4. enum枚舉
  5. 注解
  6. 基本資料類型
  7. void

實際例子:

Class c1 = Object.class;  //類
Class c2 = Input.class; //接口
Class c3 = int[].class; // 一維數組
Class c4 = int[][].class; // 二維數組
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚舉
Class c7 = void.class;  //void
Class c8 = Integer.class; //基本資料類型
Class c9 = Class.class; //Class

System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
           

輸出為:

class java.lang.Object
interface com.sun.corba.se.spi.orbutil.fsm.Input
class [I
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
void
class java.lang.Integer
class java.lang.Class
           

2.4 擴充:類的加載和ClassLoader

三個階段:

  1. 加載: 将class檔案位元組碼内容加載到記憶體裡,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表這個類的java.lang.Class對象
  2. 連結: 将Java類的二進制代碼合并到JVM的運作狀态之中
  3. 初始化:
    • 執行類構造器clinit
    • 當初始化一個類的時候,如果發現其父類沒有進行初始化,則先初始化父類

2.4.1 類初始化

什麼時候類會初始化:

  • 類的主動引用(一定會發生類的初始化)
    • 虛拟機啟動時,先初始化main方法所在的類
    • new一個類的對象
    • 調用類的靜态成員(除了final常量)和靜态方法
    • 使用java.lang.reflect包的方法對類進行反射調用
    • 當初始化一個類的時候,如果發現其父類沒有進行初始化,則先初始化父類
  • 類的被動引用(不會發生類的初始化)
    • 通路一個靜态域時,隻有真正聲明這個域的類才會被初始化。(通過子類引用父類的靜态變量,不會導緻子類初始化,隻會初始化父類)如調用Son.a,不會觸發Son類的初始化,因為a是Father類的靜态變量
    • 通過數組定義類引用,不會觸發此類的初始化,如:User[] array=new User[5],不會觸發User類的初始化
    • 引用常量不會觸發此類的初始化,常量在連結階段就存入調用類的常量池中了

2.5 建立運作時類的對象

本節内容用到的User類為

class User {
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
}
           

2.5.1 擷取運作時類的完整結構

  1. 實作的全部接口
  2. 繼承的父類
  3. 全部的構造器
  4. 全部方法
  5. 全部Field
  6. 注解
  7. ……

執行個體:

  • 獲得類的名字:包名+類名
  • 獲得類的簡單名字:類名
  • 獲得類的所有屬性
    Field[] fieldsPublic=c1.getFields(); //找到所有public的屬性
    
    Field[] fieldsAll=c1.getDeclaredFields(); // 找到所有屬性
    
    for (int i = fieldsAll.length - 1; i >= 0; i--) {
        System.out.println(fieldsAll[i]);
    }
               
  • 獲得指定屬性
    // 獲得指定屬性,所有類型的都可以獲得
    Field name=c1.getDeclaredField("name");
    
    // 隻能獲得public的指定屬性
    Field name=c1.getField("name");
               
  • 獲得類的方法
    // 獲得本類及其父類的所有public方法
    Method[] methods=c1.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
    
    // 獲得本類的所有方法
    methods=c1.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
               
  • 獲得指定方法,第二個參數代表該方法有沒有參數,有參數的話要填入參數類型(之是以需要填入參數類型,是因為java中包含重載機制,加入參數類型才可以區分)
    Method method=c1.getMethod("getName",null);
    System.out.println(method);
    
    Method method1=c1.getMethod("setName",String.class);
    System.out.println(method1);
               
  • 獲得構造器
    // 獲得所有的public構造方法
    Constructor[] constructors=c1.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println();
    
    // 獲得所有的構造方法
    constructors=c1.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
               
  • 獲得指定的構造器
    Constructor constructor=c1.getDeclaredConstructor(String.class);
    System.out.println(constructor);
               

2.5.2 動态建立對象、執行方法

2.5.2.1 建立類的對象

調用Class對象的newInstance方法

  1. 類必須有一個無參構造器
  2. 類的構造器通路權限要夠
  3. eg:
    // 獲得Class對象
    Class c1 = Class.forName("reflection.User");
    
    // 構造一個對象,本質上調用了類的無參構造器
    User user=(User)c1.newInstance();
    System.out.println(user);
               
  4. 如果沒有無參構造器,則可以通過有參構造器建立對象
    //通過構造器建立對象
    Constructor constructor = c1.getDeclaredConstructor(String.class);
    User user=(User)constructor.newInstance("南京");
    System.out.println(user);
               

2.5.2.2 執行方法

  1. 通過反射調用普通方法,首先需要建立一個對象
  2. 通過反射擷取方法
  3. 執行該方法,第二個參數為該方法需要的參數
  4. 類似的也可以通過反射操作屬性(不能直接操作私有屬性),這裡需要使用name.setAccessible(true)關閉權限檢測,使得可以通路到User類的私有變量并進行修改。這樣做破壞了類的封裝性
    //通過反射操作屬性
    User user=(User)c1.newInstance();
    Field name=c1.getDeclaredField("name");
    
    // 關閉權限檢測,使得可以通路到User類的私有變量并進行修改
    name.setAccessible(true);
    name.set(user,"南京");
    System.out.println(user.getName());
               

2.6 性能對比分析

  1. 普通方式調用執行的時間比反射方式調用的時間少得多
  2. 關閉權限檢測的反射方式比普通的反射方式快

~~可以自己寫代碼測試,這裡就不贅述啦

2.7 擴充:反射操作泛型

  1. 首先寫兩個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;
    }
               
  2. 擷取test1方法參數的泛型的參數類型
    Method method = Test10.class.getMethod("test1", Map.class, List.class);
    
    // 獲得泛型的參數類型
    Type[] genericParameterTypes = method.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);
            }
        }
        System.out.println();
    }
               
    列印出來的結果是
    java.util.Map<java.lang.String, reflection.User>
    class java.lang.String
    class reflection.User
    
    java.util.List<reflection.User>
    class reflection.User
               
  3. 獲得test2方法傳回的泛型的參數類型
    Method method = Test10.class.getMethod("test2", null);
    
    // 獲得傳回值的泛型的參數類型
    Type genericReturnType = method.getGenericReturnType();
    // 判斷是否是一種參數化類型
    if (genericReturnType instanceof ParameterizedType) {
        // 獲得真實泛型參數資訊
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument);
        }
    }
               
    列印出來的結果是
    class java.lang.String
    class reflection.User
               

2.8 反射操作注解

  1. getAnnotations
  2. getAnnotation

這裡以ORM(對象關系映射)為例,利用注解和反射完成類和表結構的映射關系

  1. 首先建立一個實體類S,含有id、age、name三個屬性,對應資料庫表中的三列。
  2. 為資料庫的表名稱建立注解
    @Target(value = ElementType.TYPE)
    @Retention(value = RetentionPolicy.RUNTIME
    @interface TableS {
        String value();
    }
               
  3. 為資料庫中每一列的具體資訊建立注解,包括列名、資料類型、資料長度
    @Target(value = ElementType.FIELD)
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface FieldS {
        String columnName();
    
        String type();
    
        int length();
    
    }
               
  4. 為實體類S和類的每一個屬性加上注解
    @TableS("db_s")
    class S {
        @FieldS(columnName = "db_id", type = "int", length = 10)
        private int id;
      
        @FieldS(columnName = "db_age", type = "int", length = 10)
        private int age;
      
        @FieldS(columnName = "db_name", type = "varchar", length = 3)
        private String name;
    }		
               
  5. 通過反射getAnnotations()獲得類S的注解
    Class c1 = Class.forName("reflection.S");
    
    // 通過反射獲得注解
    Annotation[] annotations = c1.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }
               
    輸出為@reflection.TableS(value=db_s)
  6. 通過getAnnotation()獲得指定注解的value值
    TableS tableS = (TableS) c1.getAnnotation(TableS.class);
    System.out.println(tableS.value());
               
    輸出為db_s
  7. 獲得類S指定的屬性的注解的資訊
    Field field=c1.getDeclaredField("name");
    FieldS annotation = field.getAnnotation(FieldS.class);
    System.out.println(annotation.columnName());
    System.out.println(annotation.type());
    System.out.println(annotation.length());
               
    輸出為
    db_name
    varchar
    3
               
    所有的資訊都和自己賦給注解的值一一對應