Java內功修煉系列:Java反射入門進階到使用

未經允許禁止轉載,轉載請聯繫作者。

目錄

一 反射(Reflect)初識

二 反射的基本使用和常用API

2.1 基本使用

2.2 反射獲取一個對象的步驟

2.3 反射常用API

2.3.1 獲取反射中的Class對象

2.3.2 通過反射創建類對象

2.3.3 通過反射獲取類屬性、方法、構造器(初步)

三 反射小結

3.1 小結

3.2 反射中類加載器、構造器、Method、Field的進階操作

3.2.1 對類加載器的操作:

3.2.2 對構造器的操作:

3.2.3 對Method、Field的操作:

3.2.4 對Field的操作:

四 反射進階之深入剖析

4.1 Java反射機制的起源和入口:Class類

4.1.1 Class類簡介

4.1.2 Java中類的加載過程

4.2 反射源碼解析

五 反射方法的使用

5.1 通過反射運行配置文件內容

5.2 反射方法的其它使用之---通過反射越過泛型檢查


一 反射(Reflect)初識

反射之中包含了一個「反」字,所以瞭解反射我們先從「正」開始。一般情況下,我們使用某個類時必定知道它是具體的什麼類,然後再直接對這個類進行實例化後操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(3);

反射則是一開始並不知道我要初始化的類對象是什麼,自然也無法使用 new 關鍵字來創建對象了。這時候,我們使用 JDK 提供的反射 API 進行反射調用。

反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。

Class clz = Class.forName("com.test.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 3);

上面兩段代碼的執行結果一樣,但思路完全不一樣:

第一段代碼在未運行時就已經確定了要運行的類:Apple;

第二段代碼則是在運行時通過字符串值才得知要運行的類:com.test.reflect.Apple。

這就是反射。


二 反射的基本使用和常用API

2.1 基本使用

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的調用
        Apple apple = new Apple();
        apple.setPrice(3);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射調用
        Class clz = Class.forName("com.test.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 3);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

從代碼中可以看到我們使用反射調用了 setPrice 方法,並傳遞了 14 的值。之後使用反射調用了 getPrice 方法,輸出其價格。上面的代碼整個的輸出結果是:

Apple Price:3
Apple Price:3

2.2 反射獲取一個對象的步驟

通過上面這個簡單的例子可以看出,一般情況下我們使用反射獲取一個對象的步驟:

  1:獲取類的 Class 對象實例

Class clz = Class.forName("com.zhenai.api.Apple");

   2:根據 Class 對象實例獲取 Constructor 對象

Constructor appleConstructor = clz.getConstructor();

   3:使用 Constructor 對象的 newInstance 方法獲取反射類對象

Object appleObj = appleConstructor.newInstance();

   4:而如果要調用某一個方法,則需要經過下面的步驟:

                獲取方法的 Method 對象:

Method setPriceMethod = clz.getMethod("setPrice", int.class);

                利用 invoke 方法調用方法:

setPriceMethod.invoke(appleObj, 14);

2.3 反射常用API

2.3.1 獲取反射中的Class對象

在反射中,要獲取一個類或調用一個類的方法,我們首先需要獲取到該類的 Class 對象。

在 Java API 中,獲取 Class 類對象有三種方法:

第一種,使用 Class.forName 靜態方法。當你知道該類的全路徑名時,你可以使用該方法獲取 Class 類對象。

Class clz = Class.forName("java.lang.String");

第二種,使用 .class 方法。

這種方法只適合在編譯前就知道操作的 Class。

Class clz = String.class;

第三種,使用類對象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

2.3.2 通過反射創建類對象

通過反射創建類對象主要有兩種方式:通過 Class 對象的 newInstance() 方法、通過 Constructor 對象的 newInstance() 方法。

第一種:通過 Class 對象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二種:通過 Constructor 對象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通過 Constructor 對象創建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數構造方法。下面的代碼就調用了一個有參數的構造方法進行了類對象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("紅富士", 15);

2.3.3 通過反射獲取類屬性、方法、構造器(初步)

我們通過 Class 對象的 getFields() 方法可以獲取 Class 類的屬性,但無法獲取私有屬性。

Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

輸出結果是:

price

而如果使用 Class 對象的 getDeclaredFields() 方法則可以獲取包括私有屬性在內的所有屬性:

Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

輸出結果是:

name
price

與獲取類屬性一樣,當我們去獲取類方法、類構造器時,如果要獲取私有方法或私有構造器,則必須使用有 declared 關鍵字的方法。


三 反射小結

3.1 小結

反射是什麼:

通過上面例子,我們可以大致總結到:反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。

Reflection(反射)是Java被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。

反射提供了什麼功能:

  • 在運行時構造任意一個類的對象
  • 在運行時獲取任意一個類所具有的成員變量和方法
  • 在運行時調用任意一個對象的方法/屬性

反射爲什麼慢:

其實反射的效率並不慢,在某些情況下可能達到和直接調用基本相同的效率,但是在首次執行或者沒有緩存的情況下還是會有性能上的開銷,主要在以下方面

  1. Class.forName()會調用本地方法,我們用到的method和field都會在此時加載進來,雖然會進行緩存,但是本地方法免不了有JAVA到C++在到JAVA得轉換開銷(詳情可見4.2:MethodAccessor的native版本和java版本)。
  2. class.getMethod(),會遍歷該class所有的公用方法,如果沒匹配到還會遍歷父類的所有方法,並且getMethods()方法會返回結果的一份拷貝,所以該操作不僅消耗CPU還消耗堆內存,在代碼中應該儘量避免,或者進行緩存。
  3. invoke參數是一個object數組,而object數組不支持java基礎類型,而自動裝箱也是很耗時的。

3.2 反射中類加載器、構造器、Method、Field的進階操作

下面舉個例子進一步介紹反射中對類加載器、構造器、Method、Field的操作。

首先定義一個自定義對象Person:

public class Person {
    String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("this is setName()!");
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        System.out.println("this is setAge()!");
    }

    //包含一個帶參的構造器和一個不帶參的構造器
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person() {
        super();
    }

    //私有方法
    private void privateMethod(){
        System.out.println("this is private method!");
    }
}

3.2.1 對類加載器的操作:

public class TestClassLoader {
    /*類加載器相關*/
    public static void testClassLoader() throws ClassNotFoundException,
            FileNotFoundException {
        //1. 獲取一個系統的類加載器(系統類加載器)(可以獲取,當前這個類PeflectTest就是它加載的)
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);


        //2. 獲取系統類加載器的父類加載器(擴展類加載器,可以獲取).
        classLoader = classLoader.getParent();
        System.out.println(classLoader);


        //3. 獲取擴展類加載器的父類加載器(引導類加載器,不可獲取).
        classLoader = classLoader.getParent();
        System.out.println(classLoader);


        //4. 測試當前類由哪個類加載器進行加載(系統類加載器):
        classLoader = Class.forName("cn.enjoyedu.refle.more.ReflectionTest")
                .getClassLoader();
        System.out.println(classLoader);


        //5. 測試 JDK 提供的 Object 類由哪個類加載器負責加載(引導類)
        classLoader = Class.forName("java.lang.Object")
                .getClassLoader();
        System.out.println(classLoader);

    }
}


public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        TestClassLoader.testClassLoader();
    }
}

運行結果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@61bbe9ba
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null

3.2.2 對構造器的操作:

public class TestConstructor {
    /*構造器相關*/
    public void testConstructor() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class<Person> clazz = (Class<Person>) Class.forName(className);

        System.out.println("獲取全部Constructor對象-----");
        Constructor<Person>[] constructors
                = (Constructor<Person>[]) clazz.getConstructors();
        for(Constructor<Person> constructor: constructors){
            System.out.println(constructor);
        }


        System.out.println("獲取某一個Constructor 對象,需要參數列表----");
        Constructor<Person> constructor
                = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);

        //2. 調用構造器的 newInstance() 方法創建對象
        System.out.println("調用構造器的 newInstance() 方法創建對象-----");
        Person obj = constructor.newInstance("Lucas", 25);
        System.out.println(obj.getName());
    }
}


public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestConstructor().testConstructor();
    }
}

 操作結果:

獲取全部Constructor對象-----
public cn.enjoyedu.refle.more.Person(java.lang.String,int)
public cn.enjoyedu.refle.more.Person()
獲取某一個Constructor 對象,需要參數列表----
public cn.enjoyedu.refle.more.Person(java.lang.String,int)
調用構造器的 newInstance() 方法創建對象-----
Lucas

3.2.3 對Method、Field的操作:

public class TestMethod {
    /*方法相關*/
    public void testMethod() throws Exception{
        Class clazz = Class.forName("cn.enjoyedu.refle.more.Person");

        System.out.println("獲取clazz對應類中的所有方法," +
                "不能獲取private方法,且獲取從父類繼承來的所有方法");
        Method[] methods = clazz.getMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");


        System.out.println("獲取所有方法,包括私有方法," +
                "所有聲明的方法,都可以獲取到,且只獲取當前類的方法");
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");


        System.out.println("獲取指定的方法," +
                "需要參數名稱和參數列表,無參則不需要寫");
        //  方法public void setName(String name) {  }
        Method method = clazz.getDeclaredMethod("setName", String.class);
        System.out.println(method);
        System.out.println("---------------------------");


        //  方法public void setAge(int age) {  }
        /* 這樣寫是獲取不到的,如果方法的參數類型是int型
        如果方法用於反射,那麼要麼int類型寫成Integer: public void setAge(Integer age) {  }
        要麼獲取方法的參數寫成int.class*/
        method = clazz.getDeclaredMethod("setAge", int.class);
        System.out.println(method);
        System.out.println("---------------------------");


        System.out.println("執行方法,第一個參數表示執行哪個對象的方法" +
                ",剩下的參數是執行方法時需要傳入的參數");
        Object obje = clazz.newInstance();
        method.invoke(obje,18);

        /*私有方法的執行,必須在調用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("privateMethod");
        System.out.println(method);
        System.out.println("---------------------------");
        System.out.println("執行私有方法");
        method.setAccessible(true);
        method.invoke(obje);
    }
}

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestMethod().testMethod();
    }
}

運行結果:

獲取clazz對應類中的所有方法,不能獲取private方法,且獲取從父類繼承來的所有方法
 getName() setName() getAge() setAge() wait() wait() wait() equals() toString() hashCode() getClass() notify() notifyAll()
---------------------------
獲取所有方法,包括私有方法,所有聲明的方法,都可以獲取到,且只獲取當前類的方法
 getName() setName() getAge() setAge() privateMethod()
---------------------------
獲取指定的方法,需要參數名稱和參數列表,無參則不需要寫
public void cn.enjoyedu.refle.more.Person.setName(java.lang.String)
---------------------------
public void cn.enjoyedu.refle.more.Person.setAge(int)
---------------------------
執行方法,第一個參數表示執行哪個對象的方法,剩下的參數是執行方法時需要傳入的參數
this is setAge()!
private void cn.enjoyedu.refle.more.Person.privateMethod()
---------------------------
執行私有方法
this is private method!

3.2.4 對Field的操作:

public class TestField {
    /*域相關*/
    public void testField() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class clazz = Class.forName(className);

        System.out.println("獲取公用和私有的所有字段,但不能獲取父類字段");
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            System.out.print(" "+ field.getName());
        }
        System.out.println();
        System.out.println("---------------------------");



        System.out.println("獲取指定字段");
        Field field = clazz.getDeclaredField("name");
        System.out.println(field.getName());
        System.out.println("---------------------------");

        Person person = new Person("ABC",12);
        System.out.println("獲取指定字段的值");
        Object val = field.get(person);
        System.out.println(field.getName()+"="+val);
        System.out.println("---------------------------");

        System.out.println("設置指定對象指定字段的值");
        field.set(person,"DEF");
        System.out.println(field.getName()+"="+person.getName());
        System.out.println("---------------------------");

        System.out.println("字段是私有的,不管是讀值還是寫值," +
                "都必須先調用setAccessible(true)方法");
        //     比如Person類中,字段name字段是非私有的,age是私有的
        field = clazz.getDeclaredField("age");
        field.setAccessible(true);
        System.out.println(field.get(person));
    }
}

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestField().testField();
    }
}

運行結果:

獲取公用和私有的所有字段,但不能獲取父類字段
 name age
---------------------------
獲取指定字段
name
---------------------------
獲取指定字段的值
name=ABC
---------------------------
設置指定對象指定字段的值
name=DEF
---------------------------
字段是私有的,不管是讀值還是寫值,都必須先調用setAccessible(true)方法
12

 


四 反射進階之深入剖析

4.1 Java反射機制的起源和入口:Class類

4.1.1 Class類簡介

Java 是一門面向對象的語言,我們寫的每一個類都可以看成一個對象,是 java.lang.Class 類的對象,那每一個類對應的Class放在哪裏呢?當我們寫完一個類的Java文件,編譯成class文件的時候,編譯器都會將這個類的對應的class對象放在class文件的末尾。裏面都保存了類的元數據信息。一個類的元數據信息包括什麼?有哪些屬性,方法,構造器,實現了哪些接口等等,這些信息在Java裏都有對應的類來表示,這就是Class類!

Class是用來描述類的類,封裝了當前對象所對應的類的信息。

一個類中有屬性,方法,構造器等,一個Person類,一個Order類,一個Book類,這些都是不同的類,現在需要一個類,用來描述類,這就是Class。Class類繼承自Object類,用於獲取與類相關的各種信息, 並提供了獲取類信息的相關方法,能夠通過對應的方法取出相應的信息:類的名字、屬性、方法、構造方法、父類和接口。


對於每個類而言,JRE 都爲其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關信息。 

對象只能由系統建立對象,它是在加載類時由 Java 虛擬機以及通過調用類加載器中的defineClass 方法自動構造的。

一個類(而不是一個對象)在 JVM 中只會有一個Class實例

獲取Class對象的三種方式:

  1.通過類名獲取      類名.class   

  2.通過對象獲取      對象名.getClass()

  3.通過全類名獲取    Class.forName(全類名)

Class類的常用方法

4.1.2 Java中類的加載過程

4.2 反射源碼解析

當我們懂得了如何使用反射後,今天我們就來看看 JDK 源碼中是如何實現反射的。或許大家平時沒有使用過反射,但是在開發 Web 項目的時候會遇到過下面的異常:

java.lang.NullPointerException 
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

可以看到異常堆棧指出了異常在 Method 的第 497 的 invoke 方法中,其實這裏指的 invoke 方法就是我們反射調用方法中的 invoke。

Method method = clz.getMethod("setPrice", int.class); 
method.invoke(object, 4);   //就是這裏的invoke方法

例如我們經常使用的 Spring 配置中,經常會有相關 Bean 的配置:

<bean class="com.chenshuyi.Apple">
</bean>

當我們在 XML 文件中配置了上面這段配置之後,Spring 便會在啓動的時候利用反射去加載對應的 Apple 類。而當 Apple 類不存在或發生啓發異常時,異常堆棧便會將異常指向調用的 invoke 方法。

從這裏可以看出,我們平常很多框架都使用了反射,而反射中最最終的就是 Method 類的 invoke 方法了。

下面我們來看看 JDK 的 invoke 方法到底做了些什麼。

進入 Method 的 invoke 方法我們可以看到,一開始是進行了一些權限的檢查,最後是調用了 MethodAccessor 類的 invoke 方法進行進一步處理,如下圖紅色方框所示。

那麼 MethodAccessor 又是什麼呢?

其實 MethodAccessor 是一個接口,定義了方法調用的具體操作,而它有三個具體的實現類:

  • sun.reflect.DelegatingMethodAccessorImpl
  • sun.reflect.MethodAccessorImpl
  • sun.reflect.NativeMethodAccessorImpl

而要看 ma.invoke() 到底調用的是哪個類的 invoke 方法,則需要看看 MethodAccessor 對象返回的到底是哪個類對象,所以我們需要進入 acquireMethodAccessor() 方法中看看。

從 acquireMethodAccessor() 方法我們可以看到,代碼先判斷是否存在對應的 MethodAccessor 對象,如果存在那麼就複用之前的 MethodAccessor 對象,否則調用 ReflectionFactory 對象的 newMethodAccessor 方法生成一個 MethodAccessor 對象。

在 ReflectionFactory 類的 newMethodAccessor 方法裏,我們可以看到首先是生成了一個 NativeMethodAccessorImpl 對象,再這個對象作爲參數調用 DelegatingMethodAccessorImpl 類的構造方法。

這裏的實現是使用了代理模式,將 NativeMethodAccessorImpl 對象交給 DelegatingMethodAccessorImpl 對象代理。我們查看 DelegatingMethodAccessorImpl 類的構造方法可以知道,其實是將 NativeMethodAccessorImpl 對象賦值給 DelegatingMethodAccessorImpl 類的 delegate 屬性。

所以說ReflectionFactory 類的 newMethodAccessor 方法最終返回 DelegatingMethodAccessorImpl 類對象。所以我們在前面的 ma.invoke() 裏,其將會進入 DelegatingMethodAccessorImpl 類的 invoke 方法中。

進入 DelegatingMethodAccessorImpl 類的 invoke 方法後,這裏調用了 delegate 屬性的 invoke 方法,它又有兩個實現類,分別是:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。按照我們前面說到的,這裏的 delegate 其實是一個 NativeMethodAccessorImpl 對象,所以這裏會進入 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法裏,其會判斷調用次數是否超過閥值(numInvocations)。如果超過該閥值,那麼就會生成另一個MethodAccessor 對象,並將原來 DelegatingMethodAccessorImpl 對象中的 delegate 屬性指向最新的 MethodAccessor 對象。

到這裏,其實我們可以知道 MethodAccessor 對象其實就是具體去生成反射類的入口。通過查看源碼上的註釋,我們可以瞭解到 MethodAccessor 對象的一些設計信息。

"Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster).Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves.

Inflation 機制。初次加載字節碼實現反射,使用 Method.invoke() 和 Constructor.newInstance() 加載花費的時間是使用原生代碼加載花費時間的 3 - 4 倍。這使得那些頻繁使用反射的應用需要花費更長的啓動時間。

To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations. Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl.

爲了避免這種痛苦的加載時間,我們在第一次加載的時候重用了 JVM 的入口,之後切換到字節碼實現的實現。

就像註釋裏說的,實際的 MethodAccessor 實現有兩個版本,一個是 Native 版本,一個是 Java 版本。

Native 版本一開始啓動快,但是隨着運行時間邊長,速度變慢。Java 版本一開始加載慢,但是隨着運行時間邊長,速度變快。正是因爲兩種存在這些問題,所以第一次加載的時候我們會發現使用的是 NativeMethodAccessorImpl 的實現,而當反射調用次數超過 15 次之後,則使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 對象去實現反射。

Method 類的 invoke 方法整個流程可以表示成如下的時序圖:

講到這裏,我們瞭解了 Method 類的 invoke 方法的具體實現方式。知道了原來 invoke 方法內部有兩種實現方式,一種是 native 原生的實現方式,一種是 Java 實現方式,這兩種各有千秋。而爲了最大化性能優勢,JDK 源碼使用了代理的設計模式去實現最大化性能。


五 反射方法的使用

5.1 通過反射運行配置文件內容

student類:

public class Student {
    public void show(){
        System.out.println("is show()");
    }
}

配置文件以txt文件爲例子(pro.txt):

className = cn.fanshe.Student
methodName = show

測試類:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
 
/*
 * 我們利用反射和配置文件,可以使:應用程序更新時,對源碼無需進行任何修改
 * 我們只需要將新類發送給客戶端,並修改配置文件即可
 */
public class Demo {
    public static void main(String[] args) throws Exception {
        //通過反射獲取Class對象
        Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
        //2獲取show()方法
        Method m = stuClass.getMethod(getValue("methodName"));//show
        //3.調用show()方法
        m.invoke(stuClass.getConstructor().newInstance());
        
    }
    
    //此方法接收一個key,在配置文件中獲取相應的value
    public static String getValue(String key) throws IOException{
        Properties pro = new Properties();//獲取配置文件的對象
        FileReader in = new FileReader("pro.txt");//獲取輸入流
        pro.load(in);//將流加載到配置文件對象中
        in.close();
        return pro.getProperty(key);//返回根據key獲取的value值
    }
}

控制檯輸出:

is show()

需求:
當我們升級這個系統時,不要Student類,而需要新寫一個Student2的類時,這時只需要更改pro.txt的文件內容就可以了。代碼就一點不用改動

要替換的student2類:

public class Student2 {
    public void show2(){
        System.out.println("is show2()");
    }
}

配置文件更改爲:

className = cn.fanshe.Student2
methodName = show2

控制檯輸出:

is show2();

5.2 反射方法的其它使用之---通過反射越過泛型檢查

泛型用在編譯期,編譯過後泛型擦除(消失掉)。所以是可以通過反射越過泛型檢查的
測試類:

import java.lang.reflect.Method;
import java.util.ArrayList;
 
/*
 * 通過反射越過泛型檢查
 * 
 * 例如:有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值?
 */
public class Demo {
    public static void main(String[] args) throws Exception{
        ArrayList<String> strList = new ArrayList<>();
        strList.add("aaa");
        strList.add("bbb");
        
    //    strList.add(100);
        //獲取ArrayList的Class對象,反向的調用add()方法,添加數據
        Class listClass = strList.getClass(); //得到 strList 對象的字節碼 對象
        //獲取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //調用add()方法
        m.invoke(strList, 100);
        
        //遍歷集合
        for(Object obj : strList){
            System.out.println(obj);
        }
    }
}

控制檯輸出:

aaa
bbb
100


 

參考文章

https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

https://blog.csdn.net/weixin_42724467/article/details/84311385

https://www.cnblogs.com/yougewe/p/10125073.html

https://blog.csdn.net/sinat_38259539/article/details/71799078

https://juejin.im/post/5da6be7a6fb9a04dee182727

https://www.cnblogs.com/yougewe/p/10125073.html

https://juejin.im/post/5c528abce51d45706845a458#heading-2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章