Java-全網最詳細反射

yiwanbin發表於2023-10-28

Java-反射

前言

Java的反射(reflection)機制是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵

反射引出

一個需求

根據配置檔案 re.properties 指定資訊,建立 Cat 物件並呼叫方法 hi

classfullpath=com.jwt.Cat
method=hi

這樣的需求在學習框架時很多,即在透過外部檔案配置,在不修改原始碼的情況下,來控制程式

傳統方法

傳統的方法是先 new 一個物件,然後再呼叫它的方法。

Cat cat = new Cat();
cat.hi();

透過傳統方法,確實可以呼叫 hi() 方法,但是這和我們的需求不一樣,這裡我們是要根據配置檔案 re.properties 指定資訊來完成。可以使用 Properties 來讀取配置檔案。

Properties properties = new Properties();
properties.load(new FileInputStream("src//re.properties"));
String classfullpath = properties.getProperty("classfullpath");//"com.jwt.Cat"
String methodName = properties.getProperty("method");//"hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);

然後需要建立物件,怎麼建立物件呢?直接new classfullpath,這樣不就好了嘛?嗯,想法不錯,下回不要想了。現在的 classfullpath 可是字串型別,怎麼能去new呢。所以現有技術是做不到這個事情的。那麼這裡就要引入我們要將的重點——反射機制。

反射方法

public class ReflectionQuestion {
    public static void main(String[] args) throws Exception {
        //1. 使用Properties 類, 可以讀取配置檔案
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.getProperty("classfullpath");//"com.jwt.Cat"
        String methodName = properties.getProperty("method");//"hi"
        System.out.println("classfullpath=" + classfullpath);
        System.out.println("method=" + methodName);
      
        //2. 使用反射機制解決
        //(1) 載入類, 返回Class型別的物件cls
        Class cls = Class.forName(classfullpath);
        System.out.println("cls = " + cls);
        //(2) 透過cls得到你載入的類com.jwt.Cat的物件例項
        Object o = cls.newInstance();
        System.out.println("o 的執行型別=" + o.getClass()); //執行型別
        //(3) 透過cls得到你載入的類com.jwt.Cat的methodName "hi" 的方法物件
        Method method = cls.getMethod(methodName);
        //(4) 透過method 呼叫方法: 即透過方法物件來實現呼叫方法
        method.invoke(o); //傳統方法物件.方法() , 反射機制:方法.invoke(物件)
    }
}

反射機制還有一個優點,那就是可以透過外部檔案配置,在不修改原始碼的情況下,來控制程式。比如這裡,我在Cat 類下面再寫一個方法,cry()方法,如果我們使用傳統方法,要呼叫這個方法,是不是就要修改程式碼了,比如cat.cry(); 透過反射,只需在配置檔案 re.properties 中,將 method=hi 改為 method=cry 就可以了。

反射是什麼

簡介

  • 反射機制允許程式在執行時藉助於 Reflection API 取得任何類的內部資訊(比如成員變數,構造器,成員方法等等),並能操作物件的屬性及方法。
  • 在執行狀態中,物件可以透過反射獲取他的類,類可以透過反射拿到所有⽅法(包括私有),拿到的⽅法可以調⽤,總之透過“反射”,我們可以將Java這種靜態語⾔附加上動態特性。
  • 載入完類之後,在堆中就產生了一個 Class 型別的物件(一個類只有一個Class物件) ,這個物件包含了類的完整結構資訊。透過這個物件得到類的結構。這個 Class 物件就像一面鏡子,透過這個鏡子看到類的結構,所以,形象的稱之為反射

一句話總結:反射就是在執行時才知道要操作的類是什麼,並且可以在執行時獲取類的完整構造,並呼叫對應的方法。動態特性

反射機制原理示意

反射機制可以完成

  • 1.在執行時判斷任意個物件所屬的類
  • 2.在執行時構造任意一個類的物件
  • 3.在執行時得到任意一個類所具有的成員變數和方法
  • 4.在執行時呼叫任意一個物件的成員變數和方法
  • 5.生成動態代理

反射相關的主要類

  • java.lang.Class:代表一個類,Class 物件表示某 個類載入後在堆中的物件
  • java.lang.reflect.Method:代表類的方法,Method 物件表示某個類的方法
  • java.lang.reflect.Field:代表類的成員變數,Field 物件表示某個類的成員變數
  • java.lang.reflect.Constructor:代表類的構造方法,Constructor 物件表示構造器
package com.jwt.reflection;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

public class Reflection01 {
    public static void main(String[] args) throws Exception {
        //1. 使用Properties 類, 可以讀寫配置檔案
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        //2. 使用反射機制解決
        //(1) 載入類, 返回Class 型別的物件cls
        Class cls = Class.forName(classfullpath);
        //(2) 透過cls 得到你載入的類com.hspedu.Cat 的物件例項
        Object o = cls.newInstance();
        System.out.println("o 的執行型別=" + o.getClass()); //執行型別
        //java.lang.reflect.Method:代表類的方法,Method 物件表示某個類的方法
        Method method = cls.getMethod(methodName);
        //透過method 呼叫方法: 即透過方法物件來實現呼叫方法
        method.invoke(o);

        //java.lang.reflect.Field: 代表類的成員變數, Field 物件表示某個類的成員變數
        //getField 不能得到私有的屬性
        Field nameField = cls.getField("name");
        System.out.println(nameField.get(o)); // 傳統寫法物件.成員變數, 反射: 成員變數物件.get(物件)

        //java.lang.reflect.Constructor: 代表類的構造方法, Constructor 物件表示構造器
        Constructor constructor = cls.getConstructor(); //()中可以指定構造器引數型別, 返回無參構造器
        System.out.println(constructor);//Cat()
        Constructor constructor2 = cls.getConstructor(String.class); //這裡傳入的String.class 就是String 類的Class 物件
        System.out.println(constructor2);//Cat(String name)
    }
}

反射優點和缺點

  • 優點:可以動態的建立和使用物件(也是框架底層核心),使用靈活沒有反射機制,框架技術就失去底層支撐。
  • 缺點:使用反射基本是解釋執行,對執行速度有影響.

反射呼叫最佳化-關閉訪問檢查

  • Method、Field、 Constructor 物件都有 setAccessible() 方法
  • setAccessible 作用是啟動和禁用訪問安全檢查的開關
    • 引數值為 true 表示反射的物件在使用時取消訪問檢查,提高反射的效率。
    • 引數值為 false 則表示反射的物件執行訪問檢查
package com.jwt.reflection;

import com.jwt.Cat;
import java.lang.reflect.Method;

public class Reflection02 {
    public static void main(String[] args) throws Exception {
        m1();//傳統
        m2();//反射
        m3();//反射最佳化
    }

    //傳統方法來呼叫hi
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗時=" + (end - start)+"ms");
    }


    //反射機制呼叫方法hi
    public static void m2() throws Exception {
        Class cls = Class.forName("com.jwt.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射呼叫方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗時=" + (end - start)+"ms");
    }

    //反射呼叫最佳化+ 關閉訪問檢查
    public static void m3() throws Exception {
        Class cls = Class.forName("com.jwt.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true);//在反射呼叫方法時,取消訪問檢查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射呼叫方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m3() 耗時=" + (end - start)+"ms");
    }
}
/**
m1() 耗時=2ms
m2() 耗時=934ms
m3() 耗時=769ms
**/

Class 類

基本介紹

  1. Class 也是類,繼承 Object 類
  2. Class 類物件不是 new 出來的,而是系統建立的
  3. 對於某個類的 Class 類物件,在記憶體中只有一份,因為類只載入一 次
  4. 每個類的例項都會記得自己是由哪個 Class 例項所生成
  5. 透過 Class 物件可以完整地得到一個類的完整結構
  6. Class 物件是存放在堆的,類的位元組碼二進位制資料, 是放在方法區的,有的地方稱為類的後設資料(包括方法程式碼,變數名,方法名,訪問許可權等等)

Class類的常用方法

方法名 功能說明
static Class forName(String name) 返回指定類名name的Class物件
object newlnstance() 呼叫預設建構函式,返回該Class物件的一個例項
getName() 返回此Class物件所表示的實體(類、介面、陣列類、基本型別等)名稱
Class [] getInterfaces() 獲取當前Class物件的介面
ClassLoader getClassLoader() 返回該類的類載入器
Class getSuperclass() 返回表示此Class所表示的實體的超類的Class
Constructor[] getConstructors() 返回一個包含某些Constructor物件的陣列
Field[] getDeclaredFields() 返回Field物件的一個陣列
Method getMethod(String name,Class…<?> paramTypes) 返回一個Method物件,此物件的形參型別為paramType
package com.jwt.reflection;

import java.lang.reflect.Field;

public class Class02 {
    public static void main(String[] args) throws Exception {
        String classAllPath = "com.jwt.reflection.Car";
        //1 . 獲取到Car 類對應的Class 物件
        Class<?> cls = Class.forName(classAllPath);//<?> 表示不確定的Java 型別
        //2. 輸出cls
        System.out.println(cls); //顯示cls 物件, 是哪個類的Class 物件
        System.out.println(cls.getClass());//輸出cls 執行型別java.lang.Class
        //3. 得到包名
        System.out.println(cls.getPackage().getName());//包名
        //4. 得到全類名
        System.out.println(cls.getName());
        //5. 透過cls 建立物件例項
        Car car = (Car) cls.newInstance();
        System.out.println(car);//car.toString()
        //6. 透過反射獲取屬性brand
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));//寶馬
        //7. 透過反射給屬性賦值
        brand.set(car, "賓士");
        System.out.println(brand.get(car));//賓士
        //8. 得到所有的屬性(欄位)
        System.out.println("=======所有的欄位屬性====");
        Field[] fields = cls.getFields();
        for (Field f : fields) {
            System.out.println(f.getName());//名稱
        }
    }

}

class Car {
    public String brand = "寶馬";
    public int price = 500000;
    public String color = "白色";

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", color='" + color + '\'' +
                '}';
    }
}

/**
class com.jwt.reflection.Car
class java.lang.Class
com.jwt.reflection
com.jwt.reflection.Car
Car{brand='寶馬', price=500000, color='白色'}
寶馬
賓士
=======所有的欄位屬性====
brand
price
color
**/

獲取Class類物件

1、Class.forName

前提:已知一個類的全名稱,且該類路徑下,可透過 Class 類的靜態方法 forName() 獲取,可能丟擲ClassNotFoundException 異常

例項:Class cls1 = Class.forName(“java.lang.Cat”);

應用場景:多用於配置檔案,讀取類全路徑,載入類

2、類名.class

前提:若已知具體的類,透過類的 class 獲取,該方式最為安全可靠,程式效能最高

例項:Class cls2 = Car.class;

應用場景:多用於引數傳遞,比如透過反射得到對應構造器物件

3、物件.getClass()

前提:已知某個類的例項,呼叫該例項的 getClass() 方法獲取 Class 物件

例項:Class cls3 = 物件.getClass();//執行型別

應用場景:透過建立好的物件,獲取 Class 物件

4、透過類載入器

ClassLoader classLoader = 物件.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(“類的全類名”);

5、基本資料(int,char,boolean,float,double,byte,long,short)按如下方式得到Class類物件

Class cls5 = 基本資料型別.class

6、基本資料型別對應的包裝類,可以透過 .TYPE 得到 Class 類物件

Class cls6 = 包裝類.TYPE
package com.jwt.reflection;

public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class.forName
        String classAllPath = "com.jwt.reflection.Car"; //透過讀取配置檔案獲取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);
        //2. 類名.class , 應用場景: 用於引數傳遞
        Class cls2 = Car.class;
        System.out.println(cls2);
        //3. 物件.getClass(), 應用場景,有物件例項
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);
        //4. 透過類載入器【4 種】來獲取到類的Class 物件
        //(1)先得到類載入器car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)透過類載入器得到Class 物件
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);
        //5. 基本資料(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class 類物件
        Class<Integer> cls5 = int.class;
        System.out.println(cls5);//int
        //6. 基本資料型別對應的包裝類,可以透過.TYPE 得到Class 類物件
        Class<Integer> cls6 = Integer.TYPE;
        System.out.println(cls6);
    }
}
/**
class com.jwt.reflection.Car
class com.jwt.reflection.Car
class com.jwt.reflection.Car
class com.jwt.reflection.Car
int
int
**/

哪些型別有Class物件

  1. 外部類,成員內部類,靜態內部類,區域性內部類,匿名內部類
  2. interface:介面
  3. 陣列
  4. enum:列舉
  5. annotation:註解
  6. 基本資料型別
  7. void
package com.jwt.reflection;

import java.io.Serializable;

public class AllTypeClass {
    public static void main(String[] args) {
        Class<String> aClass1 = String.class;//外部類
        Class<Serializable> aClass2 = Serializable.class;//介面
        Class<Integer[]> aClass3 = Integer[].class;//陣列
        Class<Integer[][]> aClass4 = Integer[][].class;//二維陣列
        Class<Deprecated> aClass5 = Deprecated.class;//註解
        Class<Thread.State> aClass6 = Thread.State.class;//列舉
        Class<Long> aClass7 = long.class;//基本資料型別
        Class<Void> aClass8 = void.class;//void資料型別
        Class<Class> aClass9 = Class.class;//Class類

        System.out.println(aClass1);
        System.out.println(aClass2);
        System.out.println(aClass3);
        System.out.println(aClass4);
        System.out.println(aClass5);
        System.out.println(aClass6);
        System.out.println(aClass7);
        System.out.println(aClass8);
        System.out.println(aClass9);
    }
}

類載入

靜態載入和動態載入

反射機制是 Java 實現動態語言的關鍵,也就是透過反射實現類動態載入

  • 靜態載入:編譯時載入相關的類,如果沒有則報錯
Dog dog = new Dog();
  • 動態載入:執行時載入需要的類,編譯不報錯,降低了依賴性
Class cls = Class.forName("Person");
Object o = cls.newInstance();
Method method = cls.getMethod("hi");
method.invoke(o);

類載入時機

  • 當建立物件時(new) //靜態載入
  • 當子類被載入時,父類也載入 //靜態載入
  • 呼叫類中的靜態成員時 //靜態載入
  • 透過反射 //動態載入

類載入過程圖

類載入各階段完成任務

類載入五個階段

1、載入階段

JVM在改階段的主要目的是將位元組碼從不同的資料來源(可能是 class 檔案、也可能是 jar 包,甚至網路)轉化為二進位制位元組流載入到記憶體中,並生成一個代表該類的 java.lang.Class 物件

2、連線階段——驗證

  • 目的是為了確保 Class 檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
  • 包括:檔案格式驗證(是否以魔數 oxcafebabe開頭)、後設資料驗證、位元組碼驗證和符號引用驗證。
  • 可以考慮使用 -Xverify:none 引數來關閉大部分的類驗證措施,縮短虛擬機器類載入的時間。

3、連線階段——準備

JVM 會在該階段對靜態變數,分配內容並初始化(對應資料型別的預設初始值,如0、0L、null、false等)。這些變數所使用的記憶體都將在方法區中進行分配

class A {
//分析類載入的連結階段-準備屬性是如何處理
//1. n1 是例項屬性, 不是靜態變數,因此在準備階段,是不會分配記憶體
//2. n2 是靜態變數,分配記憶體n2 預設初始化0 ,而不是20
//3. n3 是static final 是常量, 他和靜態變數不一樣, 因為一旦賦值就不變n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}

4、連線階段——解析

虛擬機器將常量池內的符號引用替換為直接引用的過程。

5、初始化階段

到初始化階段,才真正開始執行類中定義的 Java 程式程式碼,此階段是執行()方法的過程

  • () 方法是由編譯器按語句在原始檔中出現的順序,依次自動收集類中的所有靜態變數的賦值動作和靜態程式碼塊中的語句,並進行合併。
  • 虛擬機器會保證一個類的 () 方法在多執行緒環境中被正確的加鎖、同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的 () 方法,其他執行緒都需要阻塞等待,直到活動執行緒執行 () 方法完畢

反射獲取類的結構資訊

1、java.lang.Class類

  • getName:獲取全類名
  • getSimpleName:獲取簡單類名
  • getFields:獲取所有public修飾的屬性,包含本類以及父類的
  • getDeclaredFields:獲取本類中所有屬性
  • getMethods:獲取所有public修飾的方法,包含本類以及父類的
  • getDeclaredMethods:獲取本類中所有方法
  • getConstructors:獲取所有public修飾的構造器,包含本類以及父類的
  • getDeclaredConstructors:獲取本類中所有構造器
  • getPackage:以Package形式返回父類資訊
  • getSuperClass:以Class形式返回父類資訊
  • getInterfaces:以Class[]形式返回介面資訊
  • getAnnotations:以Annotation[]形式返回註解資訊
@Test
public void api_01() throws ClassNotFoundException, NoSuchMethodException {
    //得到Class 物件
    Class<?> personCls = Class.forName("com.jwt.reflection.Person");

    //getName:獲取全類名
    System.out.println(personCls.getName());//com.hspedu.reflection.Person
    //getSimpleName:獲取簡單類名
    System.out.println(personCls.getSimpleName());//Person
    //getFields:獲取所有public 修飾的屬性,包含本類以及父類的
    Field[] fields = personCls.getFields();
    for (Field field : fields) {//增強for
      System.out.println("本類以及父類的屬性=" + field.getName());
    }
    System.out.println("==========================");
    //getDeclaredFields:獲取本類中所有屬性
    Field[] declaredFields = personCls.getDeclaredFields();
    for (Field declaredField : declaredFields) {
      System.out.println("本類中所有屬性=" + declaredField.getName());
    }
    System.out.println("==========================");
    //getMethods:獲取所有public 修飾的方法,包含本類以及父類的
    Method[] methods = personCls.getMethods();
    for (Method method : methods) {
      System.out.println("本類以及父類的方法=" + method.getName());
    }
    System.out.println("==========================");
    //getDeclaredMethods:獲取本類中所有方法
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
      System.out.println("本類中所有方法=" + declaredMethod.getName());
    }
    System.out.println("==========================");
    //getConstructors: 獲取所有public 修飾的構造器,包含本類
    Constructor<?>[] constructors = personCls.getConstructors();
    for (Constructor<?> constructor : constructors) {
      System.out.println("本類的構造器=" + constructor.getName());
    }
    System.out.println("==========================");
    //getDeclaredConstructors:獲取本類中所有構造器
    Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
      System.out.println("本類中所有構造器=" + declaredConstructor.getName());//這裡老師只是輸出名
    }
    System.out.println("==========================");
    //getPackage:以Package 形式返回包資訊
    System.out.println(personCls.getPackage());//com.hspedu.reflection
    //getSuperClass:以Class 形式返回父類資訊
    Class<?> superclass = personCls.getSuperclass();
    System.out.println("父類的class 物件=" + superclass);
    System.out.println("==========================");
    //getInterfaces:以Class[]形式返回介面資訊
    Class<?>[] interfaces = personCls.getInterfaces();
    for (Class<?> anInterface : interfaces) {
      System.out.println("介面資訊=" + anInterface);
    }
    System.out.println("==========================");
    //getAnnotations:以Annotation[] 形式返回註解資訊
    Annotation[] annotations = personCls.getAnnotations();
    for (Annotation annotation : annotations) {
      System.out.println("註解資訊=" + annotation);//註解
    }
}
/**
com.jwt.reflection.Person
Person
本類以及父類的屬性=name
本類以及父類的屬性=hobby
==========================
本類中所有屬性=name
本類中所有屬性=age
本類中所有屬性=job
本類中所有屬性=sal
==========================
本類以及父類的方法=m1
本類以及父類的方法=hi
本類以及父類的方法=wait
本類以及父類的方法=wait
本類以及父類的方法=wait
本類以及父類的方法=equals
本類以及父類的方法=toString
本類以及父類的方法=hashCode
本類以及父類的方法=getClass
本類以及父類的方法=notify
本類以及父類的方法=notifyAll
==========================
本類中所有方法=m1
本類中所有方法=m2
本類中所有方法=m4
本類中所有方法=m3
==========================
本類的構造器=com.jwt.reflection.Person
本類的構造器=com.jwt.reflection.Person
==========================
本類中所有構造器=com.jwt.reflection.Person
本類中所有構造器=com.jwt.reflection.Person
本類中所有構造器=com.jwt.reflection.Person
==========================
package com.jwt.reflection
父類的class 物件=class com.jwt.reflection.A
==========================
介面資訊=interface com.jwt.reflection.IA
介面資訊=interface com.jwt.reflection.IB
==========================
**/

測試程式碼

class A {
    public String hobby;
    public void hi() {}
    public A() {}
    public A(String name) {}
}


interface IA { }

interface IB { }

@Deprecated
class Person extends A implements IA, IB {
    //屬性
    public String name;
    protected static int age; // 4 + 8 = 12
    String job;
    private double sal;
    //構造器
    public Person() {}
    public Person(String name) {}
    //私有的
    private Person(String name, int age) {}
    //方法
    public void m1(String name, int age, double sal) {}
    protected String m2() {
        return null;
    }
    void m3() {}
    private void m4() {}
}

2、java.lang.reflect.Field類

  • getModifiers:以int形式返回修飾符
    • 說明:預設修飾符是0,public是1,private是2,protected是4,static是8,final是16
  • getType:以Class形式返回型別
  • getName:返回屬性名

3、java.lang.reflect.Mehod類

  • getModifiers:以int形式返回修飾符
    • 說明:預設修飾符是0,public是1,private是2,protected是4,static是8,final是16
  • getReturnType:以Class形式獲取返回型別
  • getName:返回方法名
  • getParameterTypes:以Class[]返回引數型別陣列

4、java.lang.reflect.Constructor類

  • getModifiers:以int形式返回修飾符
  • getName:返回構造器名(全類名)
  • getParameterTypes:以Class[]返回引數型別陣列
@Test
public void api_02() throws ClassNotFoundException, NoSuchMethodException {
  //得到Class 物件
  Class<?> personCls = Class.forName("com.jwt.reflection.Person");
  
  //java.lang.reflect.Field
  //規定說明: 預設修飾符是0 , public 是1 ,private 是2 ,protected 是4 , static 是8 ,final 是16
  Field[] declaredFields = personCls.getDeclaredFields();
  for (Field declaredField : declaredFields) {
    System.out.println("本類中所有屬性=" + declaredField.getName()
                       + " 該屬性的修飾符值=" + declaredField.getModifiers()
                       + " 該屬性的型別=" + declaredField.getType()
                      );
  }
  System.out.println("==========================");

  //java.lang.reflect.Mehod
  Method[] declaredMethods = personCls.getDeclaredMethods();
  for (Method declaredMethod : declaredMethods) {
    System.out.println("本類中所有方法=" + declaredMethod.getName()
                       + " 該方法的訪問修飾符值=" + declaredMethod.getModifiers()
                       + " 該方法返回型別" + declaredMethod.getReturnType()
                      );
  }
  for (Method declaredMethod : declaredMethods) {
    //輸出當前這個方法的形引數組情況
    Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
      System.out.println("該方法的形參型別=" + parameterType);
    }
  }
  System.out.println("==========================");


  //java.lang.reflect.Constructor
  Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
  for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println("本類中所有構造器=" + declaredConstructor.getName());//只是輸出名

  }
  for (Constructor<?> declaredConstructor : declaredConstructors) {
    Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
      System.out.println("該構造器的形參型別=" + parameterType);
    }
  }
}
/**
本類中所有屬性=name 該屬性的修飾符值=1 該屬性的型別=class java.lang.String
本類中所有屬性=age 該屬性的修飾符值=12 該屬性的型別=int
本類中所有屬性=job 該屬性的修飾符值=0 該屬性的型別=class java.lang.String
本類中所有屬性=sal 該屬性的修飾符值=2 該屬性的型別=double
==========================
本類中所有方法=m1 該方法的訪問修飾符值=1 該方法返回型別void
本類中所有方法=m2 該方法的訪問修飾符值=4 該方法返回型別class java.lang.String
本類中所有方法=m4 該方法的訪問修飾符值=2 該方法返回型別void
本類中所有方法=m3 該方法的訪問修飾符值=0 該方法返回型別void
該方法的形參型別=class java.lang.String
該方法的形參型別=int
該方法的形參型別=double
==========================
本類中所有構造器=com.jwt.reflection.Person
本類中所有構造器=com.jwt.reflection.Person
本類中所有構造器=com.jwt.reflection.Person
該構造器的形參型別=class java.lang.String
該構造器的形參型別=int
該構造器的形參型別=class java.lang.String
**/

反射建立物件

  • 方式一:呼叫類中的public修飾的無參構造器建立物件
  • 方式二:呼叫類中的指定構造器建立物件

Class類相關方法

  • newInstance() 呼叫類中的無參構造器,獲取對應類的物件
  • getConstructor(Class..clazz) 根據引數列表,獲取對應的public構造器物件
  • getDecalaredConstructor(Class.cazz) 根據引數列表,獲取對應的所有構造器物件

Constructor類相關方法

  • setAccessible:爆破(形參填入true,即可訪問私有構造方法、變數、方法等)
  • newInstance(Object…obj):呼叫構造器
package com.jwt.reflection;

import java.lang.reflect.Constructor;

public class ReflecCreateInstance {
    public static void main(String[] args) throws Exception {
        //1. 先獲取到User 類的Class 物件
        Class<?> userClass = Class.forName("com.jwt.reflection.User");
        //2. 透過public 的無參構造器建立例項
        Object user1 = userClass.newInstance();
        System.out.println(user1);
        //3. 透過public 的有參構造器建立例項
        //3.1 先得到對應構造器
        Constructor<?> constructor = userClass.getConstructor(String.class);
        //3.2 建立例項,並傳入實參
        Object user2 = constructor.newInstance("小簡");
        System.out.println(user2);
        //4. 透過非public 的有參構造器建立例項
        //4.1 得到private 的構造器物件
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
        //4.2 建立例項
        //暴破【暴力破解】, 使用反射可以訪問private 構造器/方法/屬性
        constructor1.setAccessible(true);
        Object user3 = constructor1.newInstance(100, "張三丰");
        System.out.println(user3);
    }
}


class User {
    private int age = 10;
    private String name = "小明";
    public User() {//無參public
    }
    public User(String name) {//public 的有參構造器
        this.name = name;
    }
    private User(int age, String name) {//private 有參構造器
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

反射訪問類中的成員

訪問屬性

  • 1.根據屬性名獲取Field物件
    • Field f = class物件.getDeclaredField(屬性名);
  • 2.暴破
    • f.setAccessible(true); //f是Field
  • 3.訪問
    • f.set(o,值); //o表示物件
    • f.get(o); //o表示物件
  • 4.注意:如果是靜態屬性,則set和get中的引數o,可以寫成null
package com.jwt.reflection;

import java.lang.reflect.Field;
public class ReflecAccessProperty {
    public static void main(String[] args) throws Exception {
        //1. 得到Student 類對應的Class 物件
        Class<?> stuClass = Class.forName("com.jwt.reflection.Student");
        //2. 建立物件
        Object o = stuClass.newInstance();//o 的執行型別就是Student
        System.out.println(o.getClass());//Student
        System.out.println(o);
        //3. 使用反射得到age 屬性物件
        Field age = stuClass.getField("age");
        System.out.println(age.get(o));//返回age 屬性的值
        age.set(o, 88);//透過反射來操作屬性
        System.out.println(o);
        System.out.println(age.get(o));
        //4. 使用反射操作name 屬性
        Field name = stuClass.getDeclaredField("name");
        //對name 進行暴破, 可以操作private 屬性
        name.setAccessible(true);
        name.set(o, "小明");
        System.out.println(name.get(o)); //獲取屬性值
        System.out.println(o);
        
        name.set(null, "小紅");//因為name 是static屬性,因此o 也可以用null
        System.out.println(name.get(null));//獲取屬性值, 要求name 是static
        System.out.println(o);
    }
}

class Student {//類
    public int age;
    private static String name;
    public Student() {//構造器
    }
    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}

訪問方法

  • 1.根據方法名和引數列表獲取Method方法物件
    • Method m = class物件.getDeclaredMethod(方法名,XX.class);
  • 2.獲取物件: Object o = class物件.newlnstance();
  • 3.暴破: m.setAccessible(true);
  • 4.訪問: Object returnValue = m.invoke(o,實參列表);
  • 5.注意:如果是靜態方法,則invoke的引數o,可以寫成null!
package com.jwt.reflection;

import java.lang.reflect.Method;

public class ReflecAccessMethod {
    public static void main(String[] args) throws Exception {
        //1. 得到Boss 類對應的Class 物件
        Class<?> bossCls = Class.forName("com.jwt.reflection.Boss");
        //2. 建立物件
        Object o = bossCls.newInstance();
        //3. 呼叫public 的hi 方法
        //3.1 得到hi 方法物件
        Method hi = bossCls.getMethod("hi", String.class);//OK
        //Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
        //3.2 呼叫
        hi.invoke(o, "小明");
        //4. 呼叫private static 方法
        //4.1 得到say 方法物件
        Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
        //4.2 因為say 方法是private, 所以需要暴破,原理和前面講的構造器和屬性一樣
        say.setAccessible(true);
        System.out.println(say.invoke(o, 100, "張三", '男'));
        //4.3 因為say 方法是static 的,還可以這樣呼叫,可以傳入null
        System.out.println(say.invoke(null, 200, "李四", '女'));
        //5. 在反射中,如果方法有返回值,統一返回Object , 但是他執行型別和方法定義的返回型別一致
        Object reVal = say.invoke(null, 300, "王五", '男');
        System.out.println("reVal 的執行型別=" + reVal.getClass());//String
        //在演示一個返回的案例
        Method m1 = bossCls.getDeclaredMethod("m1");
        Object reVal2 = m1.invoke(o);
        System.out.println("reVal2 的執行型別=" + reVal2.getClass());//Monster
    }
}

class Monster {}
class Boss {//類
    public int age;
    private static String name;

    public Boss() {//構造器
    }

    public Monster m1() {
        return new Monster();
    }

    private static String say(int n, String s, char c) {//靜態方法
        return n + " " + s + " " + c;
    }

    public void hi(String s) {//普通public 方法
        System.out.println("hi " + s);
    }
}

本章練習

練習1

透過反射修改私有成員變數

  • 1.定義 PrivateTest 類, 有私有 name 屬性,屬性值為 hellokitty
  • 2.提供 getName 的公有方法
  • 3.利用 Class 類得到私有的 name 屬性,修改私有的 name 屬性值,並呼叫 getName() 的方法列印 name 屬性值
package com.jwt.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Homework01 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        Class<?> aClass = Class.forName("com.jwt.reflection.PrivateTest");
        Object o = aClass.newInstance();
        Field name =  aClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(o,"小明");
        Method getName = aClass.getMethod("getName");
        Object returnValue = getName.invoke(o);
        System.out.println("returnValue = " + returnValue);
    }
}

class PrivateTest{
    private String name = "hellokitty";
    public String getName() {
        return name;
    }
}

練習2

利用反射和 File 完成以下功能

  • 1.利用 Class 類的 forName 方法得到 File 類的 class 物件
  • 2.在控制檯列印 File 類的所有構造器
  • 3.透過 newInstance 的方法建立 File 物件, 並建立 /Users/jianjian/Downloads/mynew.txt 檔案
  • 提示:建立檔案的正常寫法如下:
    • File file = new File(“路徑”);
    • file.createNewFile();
package com.jwt.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Homework02 {
    public static void main(String[] args) throws Exception, {
        //得到 File 類的 class 物件
        Class<?> aClass = Class.forName("java.io.File");
          //列印 File 類的所有構造器
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor<?> i:declaredConstructors){
            System.out.println("File構造器 = " + i);
        }
        //指定構造器建立File物件
        Constructor<?> dc = aClass.getDeclaredConstructor(String.class);
        Object fileObj = dc.newInstance("/Users/jianjian/Downloads/mynew.txt");
        //得到createNewFile的方法物件
        Method createNewFile = aClass.getMethod("createNewFile");
        createNewFile.invoke(fileObj);
        System.out.println("建立成功");
    }
}

相關文章