1. 程式人生 > 遊戲 >Epic再發提示 週四或送《無人深空》

Epic再發提示 週四或送《無人深空》

註解和反射

註解

java.Annotation

註解入門

什麼是註解

  • Annotation是從JDK5.0開始引入的新技術

  • Annotation的作用:

    • 不是程式本身,可以對程式作出解釋(這一點和註釋comment沒什麼區別)
    • 可以被其他程式(比如:編譯器等)讀取
  • Annotation的格式:

    • 註解是以“@註釋名”在程式碼中存在,還可以新增一些引數值,例如:@SuppressWarnings(value="unchecked")
  • Annotation在哪裡使用?

    • 可以附加在package,class,method,field等上面,相當於給他們添加了額外的輔組資訊,我們可以通過反射機制程式設計實現對這些元資料的訪問

      package com.tang.annotation;
      
      //什麼是註解
      public class Test01 {
          //@Override  重寫的註解
          @Override
          public String toString() {
              return super.toString();
          }
      }
      

內建註解

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

  • @Deprecated:定義在java.lang.Deprecated中,此註釋可以用於修辭方法,屬性,類,表示不鼓勵程式設計師使用這樣的元素,通常是因為它很危險或者存在更好的選擇

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

    • 與前兩個註釋有所不同,你需要新增一個引數才能正確使用,這些引數都是已經定義好了的,我們選擇性的使用就好了
    1. @SuppressWarnings("all")

    2. @SuppressWarnings("unchecked")

    3. @SuppressWarnings(value={"unchecked","deprecation"})

    4. 等等……

      package com.tang.annotation;
      
      import java.util.ArrayList;
      import java.util.Date;
      import java.util.List;
      
      //什麼是註解
      public class Test01 {
          //@Override  重寫的註解
          @Override
          public String toString() {
              return super.toString();
          }
      
          //@Deprecated  不推薦程式設計師使用,或者存在更好的方式
          @Deprecated
          public static void test(){
              System.out.println("Deprecated");
          }
      
          //@SuppressWarnings("all")//警告正壓,類、方法等等
          @SuppressWarnings("all")
          public static void test02(){
              List list = new ArrayList();
          }
      
          public static void main(String[] args) {
              test();
              new Date().toLocaleString();
          }
      }
      

自定義註解,元註解

元註解

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

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

    • @Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)

    • @Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的宣告週期

      • (SOURCE < CLASS < RUNTIME)
    • @Document:說明該註解將被包含在javadoc中

    • @Inherited:說明子類可以繼承父類中的該註解

      package com.tang.annotation;
      
      import java.lang.annotation.*;
      
      //測試元註解
      @MyAnnotation
      public class Test02 {
      
          @MyAnnotation
          public void test(){
          }
      }
      
      //定義一個註解:
      //Target  表示我們的註解可以用在哪些地方
      @Target(value = {ElementType.METHOD,ElementType.TYPE})
      //Retention  表示我們的註解在什麼地方還有效,
      //runtime>class>source
      @Retention(value = RetentionPolicy.RUNTIME)
      //Documented  表示是否將我們的註解生成在javadoc中
      @Documented
      //Inherited  子類可以繼承父類的註解
      @Inherited
      @interface MyAnnotation {
      }
      

自定義註解

  • 使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation介面

  • 分析:

    • @interface用來宣告一個註解,格式:public @interface 註解名{定義內容}

    • 其中的每一個方法實際上是聲明瞭一個配置引數

    • 方法的名稱就是引數的名稱

    • 返回值型別就是引數的型別(返回值只能是基本型別,Class,String,enum)

    • 可以通過default來宣告引數的預設值

    • 如果只有一個引數成員,一般引數名為value

    • 註解元素必須要有值,我們定義註解元素時,經常使用空字串,0作為預設值

      package com.tang.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      //自定義註解
      public class Test03 {
          //註解可以顯示賦值,如果沒有預設值,我們就必須給註解賦值
          //@MyAnnotation2(name = "糖果")
          @MyAnnotation2()
          public void test(){}
          @MyAnnotation3(value = "糖果")
          public void test2(){}
      }
      
      @Target({ElementType.TYPE,ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @interface MyAnnotation2{
          //註解的引數:引數型別 + 引數名();
          String name() default "";
          int age() default 0;
          int id() default -1; //如果預設值為-1,代表不存在
      
          String[] schools() default {"糖果,鄭州航院"};
      }
      
      @Target({ElementType.TYPE,ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @interface MyAnnotation3{
          //註解的引數:引數型別 + 引數名();
          String value();
      }
      

反射機制

java.Reflection

Java反射機制概述

靜態VS動態語言

動態語言
  • 是一類在執行時可以改變其結構的語言:例如新的函式、物件、甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構

  • 主要動態語言:Object-C、C#、JavaScript、PHP、Python等

    function f() {
        var x = "var a=3;var b=5;alert(a+b)";
        eval(x);
    }
    
靜態語言
  • 與動態語言相對應的,執行時結構不可變的語言就是靜態語言。如Java、C、C++

  • Java不是動態語言,但Java可以稱之為“準動態語言”。即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。Java的動態性讓程式設計的時候更加靈活!

Java Reflection

  • Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法

    • Class c = Class.forName("java.lang.String")
  • 載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射

    • 正常方式:引入需要的”包類“名稱→→通過new例項化→→取得例項化物件

    • 反射方式:例項化物件→→getClass()方法→→得到完整的”包類“名稱

      package com.tang.reflection;
      
      //什麼叫反射
      public class Test02 {
          public static void main(String[] args) throws ClassNotFoundException {
              //通過反射獲取類的Class物件
              Class<?> c1 = Class.forName("com.tang.reflection.User");
              System.out.println(c1);
      
              Class<?> c2 = Class.forName("com.tang.reflection.User");
              Class<?> c3 = Class.forName("com.tang.reflection.User");
              Class<?> c4 = Class.forName("com.tang.reflection.User");
      
              //一類在記憶體中只有一個Class物件
              //一個類被載入後,類的整個結構都會被封裝在Class物件中
              System.out.println(c2.hashCode());
              System.out.println(c3.hashCode());
              System.out.println(c4.hashCode());
      
          }
      }
      //實體類:pojo, entity
      class User{
          private String name;
          private int id;
          private int age;
      
          public User() {
          }
      
          public User(String name, int id, int age) {
              this.name = name;
              this.id = id;
              this.age = age;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.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;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "name='" + name + '\'' +
                      ", id=" + id +
                      ", age=" + age +
                      '}';
          }
      }
      

Java反射機制研究及應用

Java反射機制提供的功能

  • 在執行時判斷任意一個物件所屬的類

  • 在執行時構造任意一個類的物件

  • 在執行時判斷任意一個類所具有的成員變數和方法

  • 在執行時獲取泛型資訊

  • 在執行時呼叫任意一個物件的成員變數和方法

  • 在執行時處理註解

  • 生成動態代理

  • ………

Java反射優點和缺點

優點:

  • 可以實現動態建立物件和編譯,體現出很大的靈活性

缺點:

  • 對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作

反射相關的主要API

  • java.lang.Class:代表一個類

  • java.lang.reflect.Method:代表類的方法

  • java.lang.reflect.Field:代表類的成員變數

  • java.lang.reflect.Constructor:代表類的構造器

  • …………

理解Class類並獲取Class例項

Class類

在Object類中定義了以下的方法,此方法將被所有子類繼承

  • public final Class getClass()

  • 以上的方法返回值的型別是一個Class類,此類是Java反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,即:可以通過物件反射求出類的名稱

物件照鏡子後可以得到的資訊:某個類的屬性、方法、和構造器、某個類到底實現了哪些介面。對於每個類而言,JRE都為其保留一個不變的Class型別的物件。一個Class物件包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關資訊。

  • Class本身也是一個類

  • Class 物件只能由系統建立物件

  • 一個載入的類在JVN中只會有一個.class檔案

  • 每個類的例項都會記得自己是由哪個Class例項所生成

  • 通過Class可以完整地得到一個類中的所有被載入的結構

  • Class類是Reflection的根源,針對任何你想動態載入、執行的類,唯有先獲得相應的Class物件

Class類的常用方法

獲取Class類的例項

  • 若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式效能最高

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

    • Class class = person.getClass();
  • 已知一個類的 全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能丟擲ClassNotFoundException

    • Class class = Class.forName("demo01.Student")
  • 內建基本資料型別可以直接用類名.Type

  • 還可以利用ClassLoader

    package com.tang.reflection;
    
    //測試Class類的建立方式有哪些
    public class Test03 {
        public static void main(String[] args) throws ClassNotFoundException {
            Person person = new Student();
            System.out.println("這個人是:"+person.name);
    
            //方式一: 通過物件獲得
            Class c1 = person.getClass();
            System.out.println(c1.hashCode());
    
            //方式二: forName獲得
            Class<?> c2 = Class.forName("com.tang.reflection.Student");
            System.out.println(c2.hashCode());
    
            //方式三: 通過類名.class獲得
            Class<Student> c3 = Student.class;
            System.out.println(c3.hashCode());
    
            //方式四: 基本內建型別的包裝類都有一個Type屬性
            Class<Integer> c4 = Integer.TYPE;
            System.out.println(c4);
    
            //獲得父類型別
            Class c5 = c1.getSuperclass();
            System.out.println(c5);
        }
    }
    
    class Person{
        public String name;
    
        public Person() {
        }
    
        public Person(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    class Student extends Person{
        public Student(){
            this.name = "學生";
        }
    }
    
    class Teacher extends Person{
        public Teacher(){
            this.name = "老師";
        }
    }
    

哪些型別可以有Class物件

  • class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類

  • interface:介面

  • []:陣列

  • enum:列舉

  • annotation:註解@interface

  • primitive type:基本資料型別

  • void

    package com.tang.reflection;
    
    import java.lang.annotation.ElementType;
    
    //所有型別的class
    public class Test04 {
        public static void main(String[] args) {
            Class c1 = Object.class; //類
            Class c2 = Comparable.class; //介面
            Class c3 = String[].class; //一維陣列
            Class c4 = int[][].class; //二維陣列
            Class c5 = Override.class; //註解
            Class c6 = ElementType.class; //列舉
            Class c7 = Integer.class; //基本資料型別
            Class c8 = void.class; //void
            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
            int[] a = new int[10];
            int[] b = new int[100];
            System.out.println(a.getClass().hashCode());
            System.out.println(b.getClass().hashCode());
        }
    }
    

類的載入與ClassLoader

Java記憶體分析

瞭解:類的載入過程

類的載入與ClassLoader的理解

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

  • 連結:將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程

    • 驗證:確保載入的類資訊符號JVM規範,沒有安全方面的問題

    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配

    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程

  • 初始化:

    • 執行類構造器<clinit>()方法的過程。類構造<clinit>()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)

    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化

    • 虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確加鎖和同步

      package com.tang.reflection;
      
      public class Test05 {
          public static void main(String[] args) {
              A a = new A();
              System.out.println(A.m);
              /*
              1.載入到記憶體, 會產生一個類對應Class物件
              2.連結, 連結結束後 m = 0
              3.初始化
                  <clinit>(){
                      System.out.println("A類靜態程式碼塊初始化");
                      m = 300;
                      m =100;
                  }
                  m = 100
               */
          }
      }
      
      class A{
          /*
          靜態程式碼塊與靜態屬性,誰在前誰先執行
           */
          static {
              System.out.println("A類靜態程式碼塊初始化");
              m = 300;
          }
      
          static int m =100;
      
          public A(){
              System.out.println("A類的無參構造初始化");
          }
      }
      

什麼時候會發生類初始化

  • 類的主動引用(一定會發生類的初始化)

    • 當虛擬機器啟動,先初始化main方法所在的類

    • new一個類的物件

    • 呼叫類的靜態成員(除了final常量)和靜態方法

    • 使用java.lang.reflect包的方法對類進行反射呼叫

    • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類

  • 類的被動引用(不會發生類的初始化)

    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化

    • 通過陣列定義類引用,不會觸發此類的初始化

    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

      package com.tang.reflection;
      
      //測試類什麼時候會初始化
      public class Test06 {
          static {
              System.out.println("main類被載入");
          }
          //main方法所在類被初始化: Test06
          public static void main(String[] args) throws ClassNotFoundException {
              //主動引用:
              //new物件: ->Father->Son
              //Son son = new Son();
      
              //反射: ->Father->Son
              //Class.forName("com.tang.reflection.Son");
      
              //呼叫類的靜態成員: ->Father
              //System.out.println(Father.b);
      
              //呼叫類的final常量: 不發生類的初始化
              //System.out.println(Son.M);
      
              //呼叫類的靜態方法: ->Father->Son
              //Son.s();
      
              //被動引用:
              //通過子類引用父類的靜態變數: ->Father,Son不初始化
              //System.out.println(Son.b);
      
              //通過陣列定義類引用,不會觸發此類的初始化
              //Son[] array = new Son[4];
      
              //呼叫類的final常量: 不發生類的初始化
              //System.out.println(Son.M);
          }
      }
      
      class Father{
          static int b = 2;
          static {
              System.out.println("父類被載入");
          }
      }
      
      class Son extends Father{
          static {
              System.out.println("子類被載入");
              m = 300;
          }
          public static void s(){}
          public final void so(){}
          static int m = 100;
          static final int M = 1;
      }
      

類載入器的作用

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

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

類載入器作用是用來把類(class)裝載進記憶體的,JVM規範定義瞭如下型別的類的載入器

package com.tang.reflection;

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {

        //獲取系統類的載入器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        //獲取系統類載入器的父類載入器-->擴充套件類載入器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        //獲取擴充套件類載入器的父類載入器-->根載入器(c/c++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        //測試當前類是哪個載入器載入的
        ClassLoader classLoader = Class.forName("com.tang.reflection.Test07").getClassLoader();
        System.out.println(classLoader);

        //測試JDK內建的類是誰載入的
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);

        //如何獲得系統類載入器可以載入的路徑
        System.out.println(System.getProperty("java.class.path"));

        //雙親委派機制-->多重檢測,保證安全性
        /*
            D:\IT\JavaSE-狂神說\out\production\註解和反射
         */
    }
}

獲取執行時類的完整結構

通過反射獲取執行時類的完整結構

Field、Method、Constructor、Superclass、Interface、Annotation

  • 實現的全部介面

  • 所繼承的父類

  • 全部的構造器

  • 全部的方法

  • 全部的Field

  • 註解

  • 。。。

    package com.tang.reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    //獲得類資訊
    public class Test08 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
            Class c1 = Class.forName("com.tang.reflection.User");
    
            //獲得類的名字
            System.out.println(c1.getName()); //獲得包名 + 類名
            System.out.println(c1.getSimpleName()); //獲得類名
    
            System.out.println("==================獲得類的屬性==================");
            //獲得類的屬性
            Field[] fields = c1.getFields(); //只能找到public屬性
    
            fields = c1.getDeclaredFields(); //找到全部的屬性
            for (Field field : fields) {
                System.out.println(field);
            }
    
            //獲得指定屬性
            Field age = c1.getDeclaredField("age");
            System.out.println(age);
    
            System.out.println("==================本類及其父類的全部public方法==================");
            //獲得類的方法
            Method[] methods = c1.getMethods(); //獲得本類及其父類的全部public方法
            for (Method method : methods) {
                System.out.println(method);
            }
            System.out.println("==================本類的所有方法==================");
            methods = c1.getDeclaredMethods(); //獲得本類的所有方法
            for (Method method : methods) {
                System.out.println(method);
            }
    
            //獲得指定方法
            //過載,所以需要引數型別
            Method getName = c1.getMethod("getName",null);
            Method setName = c1.getMethod("setName",String.class);
            System.out.println(getName);
            System.out.println(setName);
    
            System.out.println("==================獲得指定的構造器==================");
            //獲得構造器
            Constructor[] constructors = c1.getConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            constructors = c1.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
    
            //獲得指定的構造器
            Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
            System.out.println(declaredConstructor);
        }
    }
    

小結

  • 在實際的操作中,取得類的資訊的操作程式碼,並不會經常開發

  • 一定要熟悉java.lang.reflect包的作用,反射機制

  • 如何取得屬性、方法、構造器的名稱,修飾符等

有了Class物件, 能做什麼?

獲取執行時類的完整結構(詳情看上面章節)

建立執行時類的物件

  • 建立類的物件:呼叫Class物件的newInstance()方法

    • 類必須有一個無引數的構造器

    • 類的構造器的訪問許可權需要足夠

思考?難道沒有無參的構造器就不能建立物件了嗎?只要在操作的時候明確的呼叫類中的構造器,並將引數傳遞進去之後,就可以例項化操作

  • 步驟如下:

    • 通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參型別的構造器

    • 向構造器的形參中傳遞一個物件陣列進去,裡面包含了構造器中所需的各個引數

    • 通過Constructor例項化物件

      package com.tang.reflection;
      
      import java.lang.reflect.Field;
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;
      
      //動態的建立物件,通過反射
      public class Test09 {
          public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
              //獲得Class物件
              Class<?> c1 = Class.forName("com.tang.reflection.User");
      
              //構造一個物件
              // User user = (User) c1.newInstance(); //本質上是呼叫了類的無參構造器, 已過時
              // User user = (User) c1.getDeclaredConstructor().newInstance(); //本質上是呼叫了類的無參構造器
              // System.out.println(user);
      
              //通過構造器建立物件
              // Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
              // User user2 = (User) constructor.newInstance("糖果", 001, 18);
              // System.out.println(user2);
      
              //通過反射呼叫普通方法
              User user3 = (User) c1.getDeclaredConstructor().newInstance();
              //通過反射獲取一個方法
              Method setName = c1.getDeclaredMethod("setName", String.class);
              //invoke: 啟用
              //(物件,"方法的值")
              setName.invoke(user3,"糖果");
              System.out.println(user3.getName());
              System.out.println(user3.getAge());
      
              System.out.println("=======================================");
              //通過反射操作屬性
              User user4 = (User) c1.getDeclaredConstructor().newInstance();
              Field name = c1.getDeclaredField("name");
      
              //不能直接操作私有屬性, 我們需要關閉程式的安全檢測,屬性或者方法的setAccessible(true)
              name.setAccessible(true);
              name.set(user4,"糖果2");
              System.out.println(user4.getName());
          }
      }
      

呼叫執行時類的指定結構

呼叫指定的方法

通過反射,呼叫類中的方法,通過Method類完成

  • 通過Class類的getMethod(String name,Class...parameterTypes)方法取得一個Method物件,並設定此方法操作時所需要的引數型別

  • 之後使用Object invoke(Object obj, Object[] args)進行呼叫,並向方法中傳遞要設定的obj物件的引數資訊

  • Object invoke(Object obj, Object[] args)

    • Object對應原方法的返回值,若原方法無返回值,此鎖返回null

    • 若原方法為靜態方法,此時形參Object obj可為null

    • 若原方法形參列表為空,則Object[] args為null

    • 若原方法宣告private,則需要在呼叫此invoke()方法前,顯式呼叫方法物件的setAccessible(true)方法,將可訪問private的方法

  • setAccessible

    • Method和Field、Constructor物件都有setAccessible()方法

    • setAccessible作用是啟動和禁用訪問安全檢查的開關

    • 引數值為true則指示反射的物件在使用時應該取消Java語言訪問檢查

      • 提高反射的效率,如果程式碼中必須用反射,而該句程式碼需要頻繁的被呼叫,那麼請設定為true

      • 使得原本無法訪問的私有成員也可以訪問

    • 引數值為false則指示反射的物件應該實施Java語言訪問檢查

      package com.tang.reflection;
      
      import java.lang.reflect.Field;
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;
      
      //分析效能問題
      public class Test10 {
          //普通方式呼叫
          public static void test1(){
              User user = new User();
              long startTime = System.currentTimeMillis();
              for (int i = 0; i < 10_0000_0000; i++) {
                  user.getName();
              }
              long endTime = System.currentTimeMillis();
              System.out.println("普通方式執行10億次: "+(endTime-startTime)+"ms");
          }
          //反射方式呼叫
          public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
              User user = new User();
              Class c1 = user.getClass();
              Method getName = c1.getDeclaredMethod("getName");
              long startTime = System.currentTimeMillis();
              for (int i = 0; i < 10_0000_0000; i++) {
                  getName.invoke(user);
              }
              long endTime = System.currentTimeMillis();
              System.out.println("反射方式執行10億次: "+(endTime-startTime)+"ms");
          }
          //反射方式呼叫 關閉檢測
          public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
              User user = new User();
              Class c1 = user.getClass();
              Method getName = c1.getDeclaredMethod("getName");
              getName.setAccessible(true);
              long startTime = System.currentTimeMillis();
              for (int i = 0; i < 10_0000_0000; i++) {
                  getName.invoke(user);
              }
              long endTime = System.currentTimeMillis();
              System.out.println("關閉檢測執行10億次: "+(endTime-startTime)+"ms");
          }
      
          public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
              test1();
              test2();
              test3();
          }
      }
      

反射操作泛型

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

  • 為了通過反射操作這些型別,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別

  • ParameterizedType:表示一種引數化型別,比如Collection

  • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別

  • TypeVariable:是各種型別變數的公共父介面

  • WildcardType:代表一種萬用字元型別表示式

    package com.tang.reflection;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.List;
    import java.util.Map;
    
    //通過反射獲取泛型
    public class Test11 {
        public void test01(Map<String,User> map, List<User> list,User user){
            System.out.println("test01");
        }
    
        public Map<String,User> test02(){
            System.out.println("test02");
            return null;
        }
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method method = Test11.class.getMethod("test01", Map.class, List.class,User.class);
            //獲取引數型別
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("test01: "+genericParameterType); // Map<String,User> ...
                //獲取引數化型別(泛型)
                if (genericParameterType instanceof ParameterizedType){
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println(actualTypeArgument); // String,User
                    }
                }
            }
    
            method = Test11.class.getMethod("test02");
            //獲取返回型別
            Type genericReturnType = method.getGenericReturnType();
            System.out.println("test02: "+genericReturnType); // Map<String,User>
            //獲取引數化型別(泛型)
            if (genericReturnType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument); // String,User
                }
            }
        }
    }
    

反射操作註解

  • getAnnotations

  • getAnnotation

練習:ORM
  • 瞭解什麼是ORM

    • Object relationship Mapping --> 物件關係對映

    • 類和表結構對應

    • 屬性和欄位對應

    • 物件和記錄對應

  • 要求:利用註解和反射完成類和表結構的對映關係

    package com.tang.reflection;
    
    import java.lang.annotation.*;
    import java.lang.reflect.Field;
    
    //練習反射操作註解
    public class Test12 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class<?> c1 = Class.forName("com.tang.reflection.Student2");
    
            //通過反射獲得類的註解
            Annotation[] annotations = c1.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
            //獲得註解的value的值
            TableTang tableTang = c1.getAnnotation(TableTang.class);
            String value = tableTang.value();
            System.out.println(value);
    
            //獲得屬性指定的註解
            Field f = c1.getDeclaredField("name");
            Annotation[] annotations1 = f.getAnnotations();
            for (Annotation annotation : annotations1) {
                System.out.println(annotation);
            }
            FieldTang annotation = f.getAnnotation(FieldTang.class);
            System.out.println(annotation.columnName());
            System.out.println(annotation.type());
            System.out.println(annotation.length());
        }
    }
    
    @TableTang("db_student")
    class Student2{
        @FieldTang(columnName = "db_id",type = "int",length = 10)
        private int id;
        @FieldTang(columnName = "db_age",type = "int",length = 10)
        private int age;
        @FieldTang(columnName = "db_name",type = "varchar",length = 10)
        private String name;
    
        public Student2() {
        }
    
        public Student2(int id, int age, String name) {
            this.id = id;
            this.age = age;
            this.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;
        }
    
        @Override
        public String toString() {
            return "Student2{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    //類名的註解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TableTang{
        String value() default "";
    }
    
    //屬性的註解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FieldTang{
        String columnName();//列名
        String type();//型別
        int length();//長度
    }