天天看點

JavaSE - 反射機制Reflection

反射機制

1- 靜态語言 OR 動态語言

靜态語言
  • 運作時結構不可變的語言就是靜态語言。如Java、C、C++。
  • Java不是動态語言,但是Java有一定的動态性,可以利用反射機制獲得類似動态語言的特性。
動态語言

主要動态語言:C#、JavaScript、Vue、Python。

在運作時可以改變其結構的語言,就是在運作時代碼可以根據某些條件改變自身結構。

// js代碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>arguments使用</title>
</head>
<body>
    <script>
        // 利用函數求任意個數的最大值
        function getMax() {
            // 定義最大值
            var max = arguments[0];
            for (var i = 1; i < arguments.length; i++) {
                if (arguments[i] > max) {
                    max = arguments[i];
                }
            }
            return max;
        }
        
        // 輸出結果
        console.log(getMax(1, 2, 3));
        console.log(getMax(1, 2, 3, 4, 5));
        console.log(getMax(11, 2, 34, 444, 5, 100));
    </script>
</body>
</html>
           

2- 反射機制

2.1 基本概念

JavaSE - 反射機制Reflection

反射機制允許程式在執行期借助于Reflection API取得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。

加載完類之後,在堆記憶體的方法區中就産生了一個Class類型的對象(一個類隻有一個Class對象),這個對象就包含了完整的類的結構資訊。可以通過這個對象看到類的結構。這個對象就像一面鏡子,通過鏡子看到類的結構,是以形象的稱之為:反射

基本特點

反射是Java的進階技術,是Java獨有的技術。反射的核心思想和關鍵就是得到編譯以後的class檔案對象。

反射是工作在運作時的技術,因為隻有運作之後才會有class類對象。

代碼示例

實體類
package cn.guardwhy.reflection01;
// 實體類
class User {
    // 成員屬性
    private int id;
    private int age;
    private String name;
    // 無參構造器
    public User() {

    }
    // 帶參構造
    public User(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 "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

           
反射技術實作
package cn.guardwhy.reflection01;
// 反射
public class Test1 extends Object{
    public static void main(String[] args) throws Exception {
        // 1.通過反射擷取類的Class對象
        Class c1 = Class.forName("cn.guardwhy.reflection01.User");

        System.out.println("c1的Class對象:" + c1); // c1的Class對象:class cn.guardwhy.reflection01.User

        Class c2 = Class.forName("cn.guardwhy.reflection01.User");
        System.out.println("c2的Class對象:" + c2); // c2的Class對象:class cn.guardwhy.reflection01.User

        //一個類被加載後,類的整個結構資訊會被放到對應的Class對象中
        // 一個類隻對應一個Class對象
        System.out.println("c1:" + c1.hashCode());  // c1:1735600054
        System.out.println("c2:" + c2.hashCode());  // c2:1735600054


    }
}
           
Java反射優點和缺點

優點:能實作動态建立對象和編譯,展現出很大的靈活性。

缺點:對性能有影響。使用反射基本上是一種解釋操作,這類操作總是慢于直接執行相同的操作。

2.2 Class類

JavaSE - 反射機制Reflection
源碼分析

在Object類中定義了以下的方法,此方法将被所有子類繼承,Object類是Java反射的源頭,可以通過對象反射求出類的名稱。

/**
  * Returns the runtime class of this {@code Object}. The returned
  * {@code Class} object is the object that is locked by {@code
  * static synchronized} methods of the represented class.
  *
  * <p><b>The actual result type is {@code Class<? extends |X|>}
  * where {@code |X|} is the erasure of the static type of the
  * expression on which {@code getClass} is called.</b> For
  * example, no cast is required in this code fragment:</p>
  *
  * <p>
  * {@code Number n = 0;                             }<br>
  * {@code Class<? extends Number> c = n.getClass(); }
  * </p>
  *
  * @return The {@code Class} object that represents the runtime
  *         class of this object.
  * @jls 15.8.2 Class Literals
*/
public final native Class<?> getClass();
           
Class類注意點
  • Class 本身也是一個類,Class 對象隻能由系統建立對象。
  • 一個加載的類在 JVM 中隻會有一個Class執行個體,一個Class對象對應的是一個加載到JVM中的一個.class檔案。
  • 每個類的執行個體都會記得自己是由哪個 Class 執行個體所生成,通過Class可以完整地得到一個類中的所有被加載的結構。
  • Class類是Reflection的根源,針對任何你想動态加載、運作的類,唯有先獲得相應的Class對象。
Class類的常用方法
方法名 方法作用
static ClassforName(String name) 傳回指定類名name的Class對象
Object newInstance( ) 調用預設構造函數,傳回Class對象的一個執行個體
getName( ) 傳回此Class對象所表示的實體(類,接口,數組類或 void)的名稱。
Class getSuperClass( ) 傳回目前Class對象的父類的Class對象。
Class[] getinterfaces( ) 擷取目前Class對象的接口。
ClassLoader getClassLoader( ) 傳回該類的類加載器。
Constructor[] getConstructors( ) 傳回一個包含某些Constructor對象的數組。
Method getMothed(String name,Class… T) 傳回一個Method對象,此對象的形參類型為paramType。
Field[] getDeclaredFields() 傳回Field對象的一個數組。
擷取Class類的執行個體

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

已知類的執行個體,調用該執行個體的getClass()方法擷取Class對象。

已知類的全類名,且該類在類路徑下,可通過Class類的靜态方法forName( )擷取,可能抛出ClassNotFoundException。

内置基本資料類型可以直接用類名.Type

代碼示例

package cn.guardwhy.reflection01;
/*
* 擷取Class類對象
*/

// 基類
class Person{
    // 姓名
    public String username;
    // 無參構造
    public Person() {

    }
    // 帶參構造
    public Person(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "Person{" +
                "username='" + username + '\'' +
                '}';
    }
}
// 學生類
class Student extends Person{
    public Student() {
        this.username = "Curry";
    }
}

// Teacher類
class Teacher extends Person{
    public Teacher() {
        this.username = "Kobe";
    }
}

public class ReflectTest02{
    public static void main(String[] args) throws Exception {
        // 1.擷取person對象
        Person person = new Student();
        System.out.println("姓名是:" + person.username);

        // 2.方式一: 通過對象獲得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        // 3.方式二: 類去加載class檔案對象,通過類的全限名找到。
        Class c2 = Class.forName("cn.guardwhy.reflection01.Student");
        System.out.println(c2.hashCode());

        // 4.方式三:通過類的靜态成員class獲得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        // 5.方式四:基本内置類型的包裝類都有一個Type屬性
        Class c4 = Integer.TYPE;
        System.out.println(c4);
        // 6.通過子類獲得父類Class類對象
        Class c5 = c2.getSuperclass();
        System.out.println("父類Class對象:" + c5);
    }
}
           

執行結果

JavaSE - 反射機制Reflection
哪些資料類型可以有Class對象?
  • class:外部類,成員(成員内部類,靜态内部類),局部内部類,匿名内部類。
  • interface:接口
  • []:數組
  • enum:枚舉
  • annotation:注解@interface
  • primitive type:基本資料類型
  • void

代碼示例

package cn.guardwhy.reflection01;

import java.lang.annotation.ElementType;

/*
所有類型的Class
*/
public class ReflectTest03 {
    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); // class java.lang.Object
        System.out.println(c2); // interface java.lang.Comparable
        System.out.println(c3); // class [Ljava.lang.String;
        System.out.println(c4); // class [[I
        System.out.println(c5); // interface java.lang.Override
        System.out.println(c6); // class java.lang.annotation.ElementType
        System.out.println(c7); // class java.lang.Integer
        System.out.println(c8); // void
        System.out.println(c9); // class java.lang.Class

        // 隻要元素類型與次元是一樣的,就是同一個Class
        int[] a1 = new int[10];
        int[] a2 = new int[100];
        System.out.println(a1.getClass().hashCode());   // 1735600054
        System.out.println(a2.getClass().hashCode());   // 1735600054
    }
}
           

3- Java記憶體分析

3.1 記憶體分析圖

JavaSE - 反射機制Reflection

3.2 類的加載過程

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

JavaSE - 反射機制Reflection

3.3 類加載器ClassLoader

加載:
  • 将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,

    然後生成一個代表這個類的java.lang.Class對象。

連結:将Java類的二進制代碼合并到JVM的運作狀态之中的過程。
  • 驗證:確定加載的類資訊符合JVM規範,沒有安全方面的問題。
  • 準備:正式為類變量(static)配置設定記憶體并設定類變量預設初始值的階段,這些記憶體都将在方法區中進行配置設定。
  • 解析:虛拟機常量池内的符号引用(常量名)替換為直接引用(位址)的過程。
初始化:
  • 執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變量的指派動作和靜态代碼塊中的語句合并産生的。(類構造器是構造類資訊的,不是構造該類對象的構造器)。
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
  • 虛拟機會保證一個類的( )方法在多線程環境中被正确加鎖和同步。

3.4 類初始化

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

代碼示例

package cn.guardwhy.reflection_02;
/**
 * 類什麼時候初始化
*/

// 基類
class Person{
    // 定義靜态變量a
    static int a = 10;
    static {
        System.out.println("父類被加載");
    }
}
// 派生類
class Student extends Person{
    static {
        System.out.println("子類被加載!!!");
        b = 30;
    }
    // 定義靜态變量
    static int b = 21;
    // 定義常量
    static final int C = 11;
}
public class ReflectTest01 {
    // 靜态方法
    static {
        System.out.println("main類被加載!!");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        // 1.1 主動引用
        //Student stu = new Student();
        // 1.2 反射也會産生主動調用
        // Class.forName("cn.guardwhy.reflection_02.Student");

        // 2.1 不會産生類的引用方法, 子類不會被加載
        System.out.println(Student.a);
        // 2.2 調用常量
        System.out.println(Student.C);
    }
}
           

3.5 類加載器作用

類加載的作用

将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中類資料的通路入口。

類緩存

标準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它将維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象

JavaSE - 反射機制Reflection

類加載器作用是用來把類(class)裝載進記憶體的。JVM 規範定義了如下類型的類的加載器。

JavaSE - 反射機制Reflection
引導類加載器

用C++編寫的,是JVM自帶的類加載器,負責Java平台核心庫。用來裝載核心類庫,該加載器無法直接擷取。

拓展類加載器

負責jre/lib/ext目錄下的jar包或者-D java.ext.dirs 指定目錄下的jar包裝入工作庫。

系統類加載器

負責java -classpath 或者 -D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的加載器。

代碼示例

package cn.guardwhy.reflection_02;

public class ReflectTest02 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1.擷取系統類的加載器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);  // [email protected]
        // 2.擷取系統類加載器的父類加載器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent); // [email protected]
        // 3.擷取拓展類加載器的父類加載器--> 跟加載器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);    // null

        // 4.測試目前類的是哪個加載器加載的
        ClassLoader classLoader = Class.
        forName("cn.guardwhy.reflection_02.ReflectTest02")
        .getClassLoader();
        System.out.println(classLoader);
        // 5.測試JDK内置的類是誰加載的
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);

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

4- 擷取類的結構

4.1 擷取Constructor構造器對象

代碼示例

實體類
package cn.guardwhy_03;
/**
 學生類
 */
public class Student {
    // 成員變量
    private String name;
    private int age;
    // 無參構造器
    public Student() {

    }
    // 帶參構造器
    public Student(String name, int age) {
        System.out.println("==有參構造器==");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
           
反射擷取Constructor構造器對象
package cn.guardwhy_03;

import org.junit.Test;

import java.lang.reflect.Constructor;

/**
反射擷取Constructor構造器對象.反射的第一步是先得到Class類對象。

反射中Class類型擷取構造器提供了很多的API:
     1. Constructor getConstructor(Class... parameterTypes)
        根據參數比對擷取某個構造器,隻能拿public修飾的構造器,幾乎不用!
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
        根據參數比對擷取某個構造器,隻要申明就可以定位,不關心權限修飾符,建議使用!
     3. Constructor[] getConstructors()
        擷取所有的構造器,隻能拿public修飾的構造器。幾乎不用!!太弱了!
     4. Constructor[] getDeclaredConstructors()
        擷取所有申明的構造器,隻要你寫我就能拿到,無所謂權限。建議使用!!
總結:
     擷取全部構造器,建議用getDeclaredConstructors();
     擷取某個構造器,建議用getDeclaredConstructor(Class... parameterTypes)
     他們都無所謂權限,隻要申明了就可以去取!!反射是破環封裝性的!!
 */
public class TestReflect01 {
     @Test
     public void getConstructors(){
         // 1.反射第一步先得到Class類對象
         Class clazz = Student.class;
         // 2.getConstructors():擷取全部的構造器,隻能拿public修飾的構造器。
         Constructor[] cons1 = clazz.getConstructors();
         // 周遊操作
         for(Constructor c : cons1){
             System.out.println(c.getName() + " ==" + c.getParameterCount());
         }
         /*
         * cn.guardwhy_03.Student ==0
         * cn.guardwhy_03.Student ==2
         */
         
         // 3.getDeclaredConstructors(): :擷取全部的構造器,主要你寫了就可以拿到,無所謂權限。
         Constructor[] cons2 = clazz.getDeclaredConstructors();
         // 4.周遊操作
         for(Constructor c : cons2){
             System.out.println(c.getName() + " ==" + c.getParameterCount());
         }
         
         /*
         * cn.guardwhy_03.Student ==0
         * cn.guardwhy_03.Student ==2
         */
     }

     @Test
    public void getConstructor() throws Exception{
         // 1.反射的第一步先得到Class對象
         Class clazz = Student.class;
         // 2.拿到兩個參數的構造器
         Constructor c1 = clazz.getDeclaredConstructor(String.class, int.class);
         System.out.println(c1.getName() + " ==" + c1.getParameterCount());  // cn.guardwhy_03.Student==2

         // 3.定位無參數構造器
         Constructor c2 = clazz.getDeclaredConstructor();   // 隻要申明了就可以定位擷取
         System.out.println(c2.getName() + " ==" + c1.getParameterCount()); // cn.guardwhy_03.Student ==2
     }
}
           
構造器建立對象
package cn.guardwhy_03;

import org.junit.Test;

import java.lang.reflect.Constructor;

/**
反射_擷取Constructor構造器然後通過這個構造器建立對象。

 Constructor的API:
     1. T newInstance(Object... initargs)
        建立對象,注入構造器需要的資料。
     2. void setAccessible(true)
        修改通路權限,true暴力攻破權限,false表示保留不可通路權限(暴力反射)
總結:
    Constructor對象可以調用newInstance得到一個類的對象。反射是破壞了封裝性!!!
 */
public class TestReflect02 {
    @Test
    public void createObj1() throws Exception{
        // 需求:使用反射調用無參數構造器建立一個對象。
        // 1.得到Class類對象
        Class clazz = Student.class;
        // 2.定位無參數構造器
        Constructor cons1 = clazz.getDeclaredConstructor();
        // 3.通過無參數構造器調用自己的newInstance方法建立一個對象
        cons1.setAccessible(true); // 暴力打開權限
        // 4.調用無參構造器
        Student s = (Student) cons1.newInstance();
        // 5.輸出對象
        System.out.println(s);  // Student{name='null', age=0}
    }

    @Test
    public void createObj2() throws Exception{
        // 需求:使用反射調用有參數構造器建立一個對象。
        // 1.得到Class對象
        Class clazz = Student.class;
        // 2.定義無參構造器
        Constructor cons2 = clazz.getDeclaredConstructor(String.class, int.class);
        // 3.通過無參數構造器調用自己的newInstance方法建立一個對象
        Student s = (Student)cons2.newInstance("curry", 10);
        // 輸出對象
        System.out.println("對象屬性:" + s);    // 對象屬性:Student{name='curry', age=10}
    }
}
           

4.2 擷取Field成員變量

實體類
package cn.guardwhy_04;

public class Student {
    // 成員變量
    private String name;
    private int age ;
    private String color ;
    public static String school1;
    public static final String school2 = "中山大學醫學院";

    // 無參構造
    public Student() {

    }
    // 帶參構造
    public Student(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    /**
     * set.get方法
     * @return
     */
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}
           
擷取Field成員變量
package cn.guardwhy_04;

import org.junit.Test;
import java.lang.reflect.Field;

/**
 反射_擷取Field成員變量.反射的第一步是先得到Class類對象。
 1、Field getField(String name);
    根據成員變量名獲得對應Field對象,隻能獲得public修飾
 2.Field getDeclaredField(String name);
    根據成員變量名獲得對應Field對象,隻要申明了就可以得到
 3.Field[] getFields();
    獲得所有的成員變量對應的Field對象,隻能獲得public的
 4.Field[] getDeclaredFields();
 獲得所有的成員變量對應的Field對象,隻要申明了就可以得到
 */
public class FieldDemo01 {
    @Test
    public void test01(){
        // 1.先擷取class類檔案,class檔案
        Class clazz = Student.class;
        // 2.擷取全部成員變量
        Field[] fields = clazz.getDeclaredFields();
        for(Field f : fields){
            System.out.println(f.getName() + "===>" + f.getType());
        }
        /*
        * name===>class java.lang.String
        * age===>int
        * color===>class java.lang.String
          *    school1===>class java.lang.String
         *   school2===>class java.lang.String
        */
    }

    @Test
    public void test02() throws Exception{
        // 1.先擷取Class類對象,class檔案
        Class clazz = Student.class;
        // 2.擷取某個成員變量:根據成員變量的名稱定位成員變量對象
        Field name = clazz.getDeclaredField("name");
        System.out.println(name.getName() + "===" + name.getType());    // name===class java.lang.String
    }
}
           
成員變量指派和取值
package cn.guardwhy_04;

import org.junit.Test;

import java.lang.reflect.Field;

/**
反射擷取成員變量,取值和指派。

 Field的方法:給成員變量指派和取值
     void  set(Object obj, Object value):給對象注入某個成員變量資料
     Object get(Object obj):擷取對象的成員變量的值。
     void setAccessible(true);暴力反射,設定為可以直接通路私有類型的屬性。
     Class getType(); 擷取屬性的類型,傳回Class對象。
     String getName(); 擷取屬性的名稱。
 */
public class FieldDemo02 {
    @Test
    public void test01(){
        try {
            // 1.建立對象
            Student stu = new Student();
            // 2.得到class對象
            Class clazz = Student.class;
            // 3.擷取成員變量
            Field name = clazz.getDeclaredField("name");
            name.setAccessible(true);
            Field age = clazz.getDeclaredField("age");
            age.setAccessible(true);
            Field color = clazz.getDeclaredField("color");
            color.setAccessible(true);
            // 4.指派操作
            name.set(stu, "curry");
            age.set(stu, 10);
            color.set(stu, "yellow");

            System.out.println(stu);    // Student{name='curry', age=10, color='yellow'}

            // 5.取值操作
            System.out.println(name.get(stu));
            System.out.println(age.get(stu));
            System.out.println(color.get(stu));
            /*
            * curry
            * 10
            * yellow 
            */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

4.3 擷取Method方法

代碼示例

Dog類
package cn.guardwhy_05;
/**
 * Dog類
 */
public class Dog {
    // 成員變量
    private String name;
    // 無參構造器
    public Dog() {

    }
    // 帶參構造器
    public Dog(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    // 成員方法
    public void run(){
        System.out.println("狗刨的很快...");
    }

    private void eat(String name){
        System.out.println("狗吃" + name);
    }

    // 靜态方法
    public static void inAddr(){
        System.out.println("在學校吃骨頭..");
    }
}
           
擷取Method方法并執行
package cn.guardwhy_05;

import org.junit.Test;

import java.lang.reflect.Method;

/**
 反射擷取類的Method方法對象:
    1、Method getMethod(String name,Class...args);
        根據方法名和參數類型獲得對應的方法對象,隻能獲得public的

    2、Method getDeclaredMethod(String name,Class...args);
        根據方法名和參數類型獲得對應的方法對象,包括private的

    3、Method[] getMethods();
        獲得類中的所有成員方法對象,傳回數組,隻能獲得public修飾的且包含父類的

    4、Method[] getDeclaredMethods();
        獲得類中的所有成員方法對象,傳回數組,隻獲得本類申明的方法。

Method的方法
     Object invoke(Object obj, Object... args)
     * 觸發的是哪個對象的方法執行。
     * args:調用方法時傳遞的執行個體參數
 */
public class MethodDemo01 {
    @Test
    public void getAllMethods() throws Exception{
        // 1.擷取全部方法
        System.out.println("======擷取全部方法============");
        Class clazz = Dog.class;
        Method[] ms = clazz.getDeclaredMethods();
        // 周遊操作
        for(Method m : ms){
            System.out.println(m.getName() + "==>" + m.getParameterCount());
        }
        /*
        * ======擷取全部方法============
        * run==>0
        * getName==>0
        * setName==>1
        * eat==>1
        * inAddr==>0
        */

        System.out.println("======擷取某個方法============");
        // 2.擷取某個方法
        Method eat = clazz.getDeclaredMethod("eat", String.class);
        System.out.println(eat.getName() + "==>" + eat.getParameterCount());  // eat==>1
    }

    @Test
    public void methodInvoke() throws Exception{
        // 1.定位方法的目的是為了執行
        Class clazz = Dog.class;
        Method eat = clazz.getDeclaredMethod("eat", String.class);

        // 暴力打開
        eat.setAccessible(true);
        // 建立對象
        Dog dog = new Dog("泰迪");
        Object rs = eat.invoke(dog, "骨頭");  // 狗吃骨頭
        System.out.println("rs:" + rs); // rs:null

        Method getName = clazz.getDeclaredMethod("getName");
        Object rs1 = getName.invoke(dog);
        System.out.println("rs1:" + rs1);   // rs1:泰迪
        
    }
}
           

4.4 Class對象,有啥用?

建立類的對象:調用Class對象的newInstance()方法
  • 類必須有一個無參數的構造器。
  • 類的構造器的通路權限需要足夠。
無參的構造器建立對象
  • 通過Class類的==getDeclaredConstructor(Class … parameterTypes)==取得本類的指定形參類型的構造器。
  • 向構造器的形參中傳遞一個對象數組進去,裡面包含了構造器中所需的各個參數,通過Constructor執行個體化對象。
調用指定的方法
  • 通過Class類的getMethod(String name,Class…parameterTypes)方法取得一個Method對象,并設定此方法操作時所需要的參數類型。
  • 然後使用Object invoke(Object obj, Object[] args)進行調用,并向方法中傳遞要設定的obj對象的參數資訊。
  • Object 對應原方法的傳回值,若原方法無傳回值,此時傳回null。若原方法若為靜态方法,此時形參Object obj可為null。
  • 如果原方法形參清單為空,則Object[] args為null。若原方法聲明為private,則需要在調用此invoke()方法前,顯式調用方法對象的setAccessible(true)方法,将可通路private的方法。

代碼示例

package cn.guardwhy.reflection_02;

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

/*
* 通過反射,動态建立對象
*/
public class ReflectTest04 {
    public static void main(String[] args) throws Exception {
        // 1.1獲得Class對象
        Class c1 = Class.forName("cn.guardwhy.reflection_02.User");
        // 1.2 構造一個對象
        User user1 = (User) c1.newInstance(); // 本質是調用了類的無參構造器
        System.out.println(user1);   // User{id=0, age=0, name='null'}
        System.out.println("===========");

        // 2.1 通過構造器建立對象
         Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
         User user2 = (User) constructor.newInstance("guardwhy", 1, 26);
         System.out.println(user2); // User{id=1, age=26, name='guardwhy'}
         System.out.println("===========");

        // 3.1 通過反射調用普通方法
        User user3 = (User) c1.newInstance();
        // 3.2 通過反射擷取一個方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        // 3.3 invoke: 激活的意思,(對象,"方法的值")
        setName.invoke(user3, "guardwhy");
        System.out.println(user3.getName());    // guardwhy

        // 4.1 通過反射操作屬性
        System.out.println("===========");
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        // 4.2 不能直接操作私有屬性,暴力破解
        name.setAccessible(true);
        name.set(user4, "Curry");
        System.out.println(user4.getName());
    }
}
           
setAccessible
  • Method和Field、Constructor對象都有setAccessible()方法。setAccessible作用是啟動和禁用通路安全檢查的開關。
  • 參數值為true則訓示反射的對象在使用時應該取消Java語言通路檢查。
  • 提高反射的效率。如果代碼中必須用反射,而該句代碼需要頻繁的被調用,那麼請設定為true。

代碼示例

package cn.guardwhy.reflection_02;

import java.lang.reflect.Method;

/*
* 性能測試
*/
public class ReflectTest05 {
    public static void main(String[] args) throws Exception {
        // 調用方法
        test01();
        test02();
        test03();
    }

    // 1.普通方法
    public static void test01(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        // 周遊操作
        for (int i = 0; i < 100000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("普通方式執行10億次數:" + (endTime - startTime) + "ms");
    }

    // 2.反射方法調用
    public static void test02() throws Exception {
        User user = new User();
        // 得到c1類對象
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();
        // 拿到方法
        Method getName = c1.getDeclaredMethod("getName", null);
        for (int i = 0; i < 100000000; i++) {
            // 激活方法
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式執行10億次數:" + (endTime - startTime) + "ms");
    }

    // 反射方式,關閉檢查
    public static void test03() throws Exception {
        User user = new User();
        // 得到c1類對象
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();
        // 拿到方法
        Method getName = c1.getDeclaredMethod("getName", null);
        // 暴力破解
        getName.setAccessible(true);
        for (int i = 0; i < 100000000; i++) {
            // 激活方法
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式(關閉檢查)執行10億次數:" + (endTime - startTime) + "ms");
    }
}
           

執行結果

JavaSE - 反射機制Reflection

繼續閱讀