1. 程式人生 > 實用技巧 >深入剖析java反射原理

深入剖析java反射原理

Java高階之反射

Class類

從java世界理解Class

  • 問題一:類和物件的關係?

  • 答曰:類是抽象的概念,它是具有相同屬性和方法的一組物件集合,它代表著事物的模板;而物件是能夠真正“感覺的到、看得見,摸得著的”具體的實體。對物件的抽象便是,而的例項化結果便是物件

  • 問題二:有個可能不恰當的問法:物件的抽象是類,那類的抽象用什麼表示?

    • java API中有個類java.lang.Class,該類是用來描述類的類(比較拗口),為了幫助理解,直接上圖:

    • 物件是具體的例項:比如哈士奇、泰迪、正方形、三角形;
    • 類是物件的抽象:比如Dog類、Shape類;
    • 哈士奇、泰迪同屬於狗,具有小狗型別的一些屬性,比如吃飯、睡覺等,三角形和正方形同屬於形狀,有相同的一些屬性,比如邊長、面積等,那麼針對於哈士奇、泰迪,我們選擇了Dog來描述,我們可以說:哈士奇、泰迪的型別是Dog
      類,正方形、三角形的型別是Shape類;
    • 那麼請問:Dog類和Shape類的型別是什麼類?答案便是Class類,它用來描述類的型別

從JVM類載入過程理解Class類。

  • 以上圖Dog類入手,建立一個Dog類以及一個測試類。

    //Dog類,是用來描述各個品種的狗的的類,
    public class Dog {
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public void eat() {
            System.out.println("我的名字是:" + getName() + ",我開始吃飯了!");
        }
    }
    class DogTest{
        public static void main(String[] args) {
            //huskie、teddy是Dog類的具體例項。
            Dog huskie = new Dog();
            huskie.setName("哈士奇");
            huskie.eat();
            Dog teddy = new Dog();
            teddy.setName("泰迪");
            teddy.eat();
        }
    }
    
  • 過程解讀:

    1. Dog類經過javac.exe命令以後,會生成Dog.class位元組碼檔案;
    2. 之後使用java.exe對Dog.class位元組碼檔案進行解釋執行,即載入到記憶體中,該過程稱為類的載入;
    3. 載入Dog.class類時,JVM為其建立一個Class型別的例項,並將其關聯起來。也就是說:當Dog.class載入到記憶體中時,JVM為其建立了一個Class例項,這個Class例項包含了該class型別的所有完整資訊。那麼只要能得到某個類的Class例項,那麼便可以拿到該類的類名、包名、父類、實現的介面、所有方法、欄位等等。

    注意:

    • 類的載入過程中,載入到記憶體中的類,稱之為執行時類
      ,該執行時類,就作為Class的一個例項。也就是說:Class的例項對應著一個執行時的類,它會在記憶體中快取一定的時間,在此時間內,我們能通過不同方式獲取執行時類。
    • Class例項在JVM中是唯一的,因此無論通過何種方式獲取到的Class例項都是同一個例項。
    • 不同的class檔案會產生不同的Class例項,載入Dog.class時,JVM會為Dog.class建立一個Class型別的例項, 載入Shape.class時,JVM會為Shape.class建立一個Class型別的例項。

Class例項與類的例項

  • 方便個人理解,直接上圖。

  • 我們對Dog.class位元組碼檔案進行解釋執行,載入到記憶體中,並且將這些靜態資料轉換成方法區的執行時的資料結構,之後會在堆中生成一個代表這個類的java.lang.Class物件,用來封裝類在方法區內的資料結構,可以作為方法區中類資料的訪問入口(好像JDK版本不同,Class物件所在的位置也不同)。

反射概念及意義

基本理解

  • 在上面記憶體載入時建立了Class例項,Class例項上儲存了這個類的所有完整資訊,那麼,通過Class例項獲取class資訊的方法便稱為反射(Reflection);
  • 反射機制就是在程式的執行過程中被允許對程式本身進行操作,它是動態語言的關鍵;
  • 在程式執行時,系統始終為所有的物件維護一個被稱為執行時的型別標識。例如Dog.classDog類的Class型別的例項,Shape.classShapeClass型別的例項。這些Class型別的例項跟蹤著每個物件所屬的類。這些Class型別的例項儲存這些類的完整資訊。
  • class類的關係:class是描述類的一個關鍵字。Class卻是儲存著執行時資訊的類。
  • Class與反射配套使用,因為Class類能夠幫助我們在程式執行時分析類,獲取執行時類中的值。

反射能做到的事

  • 拿到Class例項後,我們在程式執行時便可以做到:
    • 判斷任意一個物件所屬的類以及構造任意一個類的物件;
    • 獲取任意一個類所具有的成員變數和方法;
    • 獲取泛型資訊(會在泛型章節詳細分析);
    • 呼叫任意一個物件的成員變數和方法;
    • 處理註解;
    • 動態代理。
  • 可以擁有Class物件的型別有:
    • primitive type:基本資料型別;
    • class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類;
    • interface:介面;
    • []:陣列;
    • enum:列舉;
    • annotation:註解@interface;
    • void。

反射的常用方法

獲取Class類的例項

  • 獲取Class類的例項的四種方法,以Dog類為例。
//方式一:直接呼叫執行時類的屬性:.class;
Class clazzOne = Dog.class;
System.out.println("方式一獲取到的Class:" + clazzOne);
//方式二:使用執行時類的物件,呼叫getClass()方法;
Dog dog1 = new Dog();
Class clazzTwo = dog1.getClass();
System.out.println("方式二獲取到的Class:" + clazzTwo);
//方式三:直接使用Class的靜態方法:forName(String classPath);
Class clazzThree = Class.forName("com.practice.reflect.Dog");
System.out.println("方式三獲取到的Class:" + clazzThree);
//方式四:使用類的載入器:ClassLoader
ClassLoader classLoader = DogTest.class.getClassLoader();
Class clazzFour = classLoader.loadClass("com.practice.reflect.Dog");
System.out.println("方式四獲取到的Class:" + clazzFour);
//比較獲取到的是否是同一個Class
Boolean bool = (clazzOne == clazzTwo) && (clazzTwo == clazzThree) && (clazzThree == clazzFour);
System.out.println(bool);

獲取類載入器

  • 類載入器有如下幾種型別
    • 自定義類載入器 ➡️ 系統類載入器 ➡️ 擴充套件類載入器 ➡️ 引導類載入器
    • 系統類載入器(System Classloader):負責java –classpath 或 –D java.class.path所指的目錄下的類與jar包裝入工作 ,是最常用的載入器;
    • 擴充套件類載入器(Extension Classloader):負責jre/lib/ext目錄下的jar包或 –D java.ext.dirs 指定目錄下的jar包裝入工作庫;
    • 引導類載入器(Bootstap Classloader):用C++編寫的,是JVM自帶的類載入器,負責Java平臺核心庫,用來裝載核心類庫。該載入器無法直接獲取。
public static void main(String[] args) {
        //拿到DogTest類的類載入器,自定義的類是系統類載入器進行載入
        ClassLoader classLoaderOne = DogTest.class.getClassLoader();
        System.out.println("系統類載入器classLoaderOne:" + classLoaderOne);
        //獲取一個系統類載入器
        ClassLoader classLoaderOne1 =  ClassLoader.getSystemClassLoader();
        System.out.println("系統類載入器classLoaderOne1:" + classLoaderOne1);
        System.out.println(classLoaderOne1 == classLoaderOne);
        //系統類載入器可以以流的方式建立一個資源  檔案目錄在當前src下
        InputStream is = classLoaderOne1.getResourceAsStream("jdbc.properties");
        //呼叫系統類載入器的getParent():獲取擴充套件類載入器
        ClassLoader classLoaderTwo = classLoaderOne.getParent();
        System.out.println("擴充套件類載入器classLoaderTwo:" + classLoaderTwo);
        //呼叫擴充套件類載入器的getParent():無法獲取引導類載入器
        //引導類載入器主要負責載入java的核心類庫,無法載入自定義類的。
        ClassLoader classLoaderThree = classLoaderTwo.getParent();
        System.out.println("引導類載入器classLoaderThree:" + classLoaderThree);
    }

建立執行時類的物件

  • 獲取到Class類的例項後,我們可以建立執行時類的物件;比如通過Dog.class獲取到對應的Class例項後,建立一個Dog物件名為Labrador拉布拉多犬。

     //通過Dog.class拿到Class物件後
     Class clazz = Dog.class;
     //使用Class的newInstance()方法建立物件,它實際上是呼叫了執行時類的空參的構造器
     //使用條件: 1.執行時類必須提供空參的構造器  2.空參的構造器的訪問許可權得夠,通常為public。
     Dog Labrador = (Dog)clazz.newInstance();
     //也可以通過構造器建立  有這樣的構造器:Dog(String name)
     Constructor constructor = clazz.getConstructor(String.class);
     Dog d = (Dog)constructor.newInstance("Labrador");
    

反射各方法梳理

類的屬性

  • 首先拿到Class例項。

    Class clazz = Dog.class;
    
  • 通過Class拿到類的屬性

    //獲取當前執行時類及其父類中宣告為public訪問許可權的屬性
    Field[] fields = clazz.getFields();
    //獲取當前執行時類中宣告的所有屬性。(不包含父類中宣告的屬性)
    Field[] declaredFields = clazz.getDeclaredFields();
    
  • 通過上面拿到的屬性進而拿到這個屬性的其他資訊

    • getModifiers():返回屬性的修飾符,是一個int
    • getType():返回欄位型別,也是一個Class例項;
    • getName():返回屬性名稱;
     Field[] fields1 = clazz.getDeclaredFields();
     for (Field field : fields1) {
         //以 private String name; 為例
         //返回許可權修飾符  private
         int m = field.getModifiers();
         System.out.print(Modifier.toString(m) + "\t");
         //返回該欄位的型別 String
         Class s = field.getType();
         System.out.print(s.getName() + "\t");
         //返回該欄位的名字  name
         String name = field.getName();
         System.out.print(name + "\t");
     }
    
  • 拿到指定屬性的值,並對指定屬性的值進行操作

    Dog teddy = new Dog();
    teddy.setName("泰迪");
    Class c = teddy.getClass();
    Field field = c.getDeclaredField("name");
    //如果不設定 會報錯:IllegalAccessException  因為name的屬性是private的。
    field.setAccessible(true);
    Object value = field.get(teddy);
    System.out.println("name屬性的值為" + value);
    //設定name的值  第一個引數是哪一個類的例項,第二個引數是要設定的值。
    field.set(teddy, "哈士奇");
    System.out.println("修改後的值為" + teddy.getName());
    
  • 方法總結

    1. Field[] getFields():獲取所有public的field(包括父類);
    2. Field[] getDeclaredFields():獲取當前類的所有field(不包括父類);
    3. Field getField(name):根據欄位名獲取某個public的field(包括父類);
    4. Field getDeclaredField(name):根據欄位名獲取當前類的某個field(不包括父類)。

類的方法

  • 通過Class例項可以獲取到方法Method資訊

    //getMethods():獲取當前執行時類及其所有父類中宣告為public許可權的方法
    Method[] methods = clazz.getMethods();
    //getDeclaredMethods():獲取當前執行時類中宣告的所有方法。(不包含父類中宣告的方法)
    Method[] methods1 = clazz.getDeclaredMethods();
    //獲取某個public的Method(包括父類)
    Method method1 = clazz.getMethod("play");
    //獲取當前類的某個Method(不包括父類) 無參
    Method method = clazz.getDeclaredMethod("sleep");
    
  • 通過上面拿到的方法,可以進一步拿到該方法的其他資訊。

    • getAnnotations():獲取方法宣告的註解;
    • getModifiers():返回方法的修飾符,它是一個int,不同的bit表示不同的含義;
    • getReturnType():返回方法返回值型別;
    • getName():返回方法名稱;
    • getParameterTypes():返回方法的引數型別,是一個Class陣列;
    • getExceptionTypes():丟擲的異常。
    //getDeclaredMethods():獲取當前執行時類中宣告的所有方法。(不包含父類中宣告的方法)
    Method[] methods1 = clazz.getDeclaredMethods();
    for (Method method : methods1) {
        //一、獲取該方法宣告的註解
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("方法的註解為:" + annotation);
        }
        //二、獲取許可權修飾符
        System.out.print(Modifier.toString(method.getModifiers()) + "\t");
        //三、獲取返回值型別
        System.out.print(method.getReturnType().getName() + "\t");
        //四、獲取方法名
        System.out.printf(method.getName());
        //五、獲取引數列表
        Class[] parameterTypes = method.getParameterTypes();
        //六、獲取丟擲的異常
        Class[]  exceptionTypes = method.getExceptionTypes();  
    }
    
  • 呼叫方法

    • Object invoke(Object instance, Object... parameters)呼叫某個物件的方法,第一個引數是物件例項,即在哪一個物件上呼叫該方法;第二個引數是引數列表。
    Class clazz = Dog.class;
    //建立執行時類的物件
    Dog dog1 = (Dog)clazz.newInstance();
    //以該方法為例:public String play(String toy),獲取指定的方法
    Method method = clazz.getDeclaredMethod("play",String.class);
    //確保當前方法可以訪問  可以呼叫private修飾的方法,
    //不加的話,private方法會報錯:IllegalAccessException
    method.setAccessible(true);
    //呼叫方法:呼叫方法的invoke,Object invoke(Object instance, Object... parameters) 
    // 引數1:物件例項-->dog1  引數2:形參列表;  呼叫靜態方法時,第一個引數傳null。
    //方法的返回值為呼叫方法的返回值
    Object returnObj = method.invoke(dog1, "籃球");
    System.out.println(returnObj);
    

類的構造器

  • 通過Class物件,可以拿到當前執行時類的構造資訊
    • getConstructor(Class...):獲取當前執行類中某個publicConstructor
    • getDeclaredConstructor(Class...):獲取當前執行類中某個Constructor
    • getConstructors():獲取當前執行類中所有publicConstructor
    • getDeclaredConstructors():獲取當前執行類中所有Constructor
//Dog類的例項化
//1、使用new關鍵字
Dog teddy = new Dog();
teddy.setName("teddy");
//2、使用反射的方式
//2.1:使用的是無參的構造方法;2.2:許可權修飾符為public
Dog dog1 = Dog.class.newInstance();
dog1.setName("田園犬");
//3、使用反射呼叫任意的構造方法  例:public Dog(String name);
Class clazz = Dog.class;
//3.1、取當前執行時類中宣告為public的構造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println(constructor);
}
System.out.println();
//3.2、獲取當前執行類中宣告的所有構造器的方法
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
    System.out.println(declaredConstructor);
}
//3.3、獲取當前執行類中的String型別引數的構造方法
Constructor constructor = clazz.getConstructor(String.class);
//3.4、保證此構造器是可訪問的
constructor.setAccessible(true);
//3.5、呼叫構造方法建立物件
Dog dog2 = (Dog)constructor.newInstance("哈士奇");

獲取類的其他資訊

  • 除下以上常用的方法之外,還可以通過Class例項獲取該類的其他資訊
    • Class getSuperclass()獲取執行時的父類;
    • Type getGenericSuperclass()獲取帶泛型父類;
      • Type[] getActualTypeArguments()獲取帶泛型的父類的泛型;
    • Class[] getInterfaces():獲取當前類實現的所有介面;
    • Package getPackage():獲取當前類所在包;
    • Annotation[] getAnnotations():獲取執行時類宣告的註解。

總結

  • 反射是框架的靈魂,學好反射才能深入理解框架。

原創不易,歡迎轉載,轉載時請註明出處,謝謝!
作者:瀟~蕭下
原文連結:https://www.cnblogs.com/manongxiao/p/13429889.html