【java基礎】程序員你真的理解反射機制嗎?

前言

不少講解反射的博客文章並無詳細講解Class類,~固然包括以前的我也同樣~,這樣的文章只會讓反射徒有其表,並不能讓大多數初學者真正理解反射,而偏偏反射的原理就在於Class對象!可見他的重要性,這篇文章我將總結一下關於Class類的知識,可能還不是很全面,各位擔待點哈QnQ,我以前也寫過幾篇關於反射的文章,主要是反射真的過重要了,如今從新總結一篇~主要是前面總結的太潦草了~,對反射從新認識順道再結合一些優秀文章再總結一下。html

參考資料:java

JDK1.8_API.../docs/api/java/lang/Class.htmlsql

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/數據庫

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

@數組

一、反射的概述

一句話定義反射就是在運行時才知道要操做的類是什麼,而且能夠在運行時獲取類的完整構造,並調用對應的方法,所謂反射實際上是獲取類的字節碼文件,也就是.class文件。平時咱們要調用某個類中的方法的時候都須要建立該類的對象,經過對象去調用類裏面的方法,反射則是一開始並不知道我要初始化的類對象是什麼,天然也沒法使用 new 關鍵字來建立對象了,在這種狀況下(沒有建立對象)咱們都可以對它的方法和屬性進行調用,咱們把這種動態獲取對象信息和調用對象方法的功能稱之爲反射機制jvm

反射才體現出java是如何建立對象的。當java虛擬機(JVM)加載一個class類文件時,就建立這個類的class對象,之後的對象都是由這個class對象建立的,因此同一個類的全部對象的class對象都是一個,好比A a=new A(); A b=new A(); a.class()==b.class()返回true.ide

二、正式使用反射以前頗有必要了解的Class類

不少講解反射的博客文章並無詳細講解Class類,~固然包括以前的我也同樣~,這樣的文章並不能讓大多數初學者真正理解反射,而偏偏反射的原理就在於Class對象!可見他的重要性,這篇文章我將總結一下關於Class類的知識,可能還不是很全面,各位擔待點哈~函數

首先,我要給初學者或者小白定位一下對Class類的理解。經常使用類有String類、Math類等等,這裏的Class也是一個相似於String類、Math類等等的類,和咱們隨便建立的類的概念是有本質區別的,Class類位於java.lang包下!

你們到知道,一個類擁有成員變量、方法、構造方法、所在包、字段屬性等等成分組成,而反射就是把java類中的各類成分映射成一個個的Java對象,能夠理解爲利用反射技術對一個類進行「解剖」,把各個組成部分映射成一個個的對象。其實,一個類中這些成員方法、構造方法、在加入類中都有一個Class類來描述,在正式使用反射以前,頗有必要先來了解了解這個Class類!

反射的原理就在於Class對象

2.一、 普通類的加載過程

熟悉一下加載的時候:Class對象的由來是將class文件讀入內存,併爲之建立一個Class對象。反射的本質理解就是獲得Class對象後反向獲取Student對象的各類成分信息(成分信息包括成員變量、方法、構造方法、所在包、字段屬性等等),下面就以Student對象爲例,圖解Student類的正常加載過程~
在這裏插入圖片描述
能夠看出圖中這個Class對象很特殊。咱們進一步瞭解一下這個Class類!

2.二、 分析Class類的API(1.7的API)

在這裏插入圖片描述

Class 類的實例表示正在運行的 Java應用程序中的類和接口。也就是jvm中有N多的實例每一個類都有該Class對象。(包括基本數據類型) Class 沒有公共構造方法。Class對象是在加載類時由 Java 虛擬機以及經過調用類加載器中的defineClass方法自動構造的。也就是這不須要咱們本身去處理建立Class對象,JVM已經幫咱們建立好了。

2.三、 Class類的經常使用方法

Class類沒有公共的構造方法,JVM會自動幫咱們建立好,但方法卻共有64個,這裏主要講一下經常使用的方法。

一、getName() : 返回此 Class對象所表示的實體(類、接口、數組類、基本類型或 void)名稱

一個Class對象描述了一個特定類的屬性,Class類中最經常使用的方法getName以 String 的形式返回此 Class
對象所表示的實體(類、接口、數組類、基本類型或 void)名稱。

二、newInstance(): 爲類建立一個實例,但只能調用默認構造器(無參數構造器)

Class還有一個有用的方法能夠爲類建立一個實例,這個方法叫作newInstance()。例如:
x.getClass.newInstance(),建立了一個同x同樣類型的新實例。newInstance()方法只能調用默認構造器(無參數構造器)初始化新建對象。

三、getClassLoader()

getClassLoader() 方法主要返回該類的類加載器。

4、getComponentType()

getComponentType() 方法主要返回表示數組組件類型的 Class。

五、getSuperclass()

getSuperclass() 返回表示此 Class 所表示的實體(類、接口、基本類型或 void)的超類的 Class。

六、isArray()

isArray() 斷定此 Class 對象是否表示一個數組類。

須要注意一點的是,forNamenewInstance結合起來使用【 Class.forName()方法下面會單獨講解】,能夠根據存儲在字符串中的類名建立對象。例如

Object obj = Class.forName(s).newInstance();

另外虛擬機爲每種類型管理一個獨一無二的Class對象,也就是說Class對象是唯一的。所以可使用==操做符來比較類對象。例如:

if(e.getClass() == Employee.class)…

2.四、 Class.forName()方法

Class.forName()是一種獲取Class對象的方法,並且是靜態方法。

Class.forName()是一個靜態方法,一樣能夠用來加載類,Class.forName()返回與給定的字符串名稱相關聯類或接口的Class對象。注意這是一種獲取Class對象的方法

官方給出的API文檔以下

publicstatic Class<?> forName(String className)

Returns the Class object associated withthe class or interface with the given string name. Invokingthis method is equivalent to:

Class.forName(className,true, currentLoader)

where currentLoader denotes the definingclass loader of the current class.

For example, thefollowing code fragment returns the runtime Class descriptor for theclass named java.lang.Thread:

Class t =Class.forName("java.lang.Thread")

A call to forName("X") causes theclass named X to beinitialized.

Parameters:

className - the fully qualifiedname of the desired class.

Returns:

the Class object for the classwith the specified name.

能夠看出,Class.forName(className)其實是調用Class.forName(className,true, this.getClass().getClassLoader())。第二個參數,是指Classloading後是否是必須被初始化。能夠看出,使用Class.forName(className)加載類時則已初始化。因此Class.forName()方法能夠簡單的理解爲:得到字符串參數中指定的類,並初始化該類。

2.五、關於Class類值得思考的問題

1.在初始化一個類,生成一個實例的時候,newInstance()方法和new關鍵字除了一個是方法,一個是關鍵字外,最主要有什麼區別?

它們的區別在於建立對象的方式不同,前者是使用類加載機制,後者是建立一個新類。

2.那麼爲何會有兩種建立對象方式?

這主要考慮到軟件的可伸縮、可擴展和可重用等軟件設計思想。 Java中工廠模式常用newInstance()方法來建立對象,所以從爲何要使用工廠模式上能夠找到具體答案。例以下面代碼

class c = Class.forName(「Example」);  

factory = (ExampleInterface)c.newInstance();

其中ExampleInterfaceExample的接口,能夠寫成以下形式:

String className = 「Example」;  

  class c = Class.forName(className);  

  factory = (ExampleInterface)c.newInstance();

進一步能夠寫成以下形式:

String className = readfromXMlConfig;//從xml 配置文件中得到字符串

         class c = Class.forName(className);  

         factory = (ExampleInterface)c.newInstance();

上面代碼已經不存在Example的類名稱,它的優勢是,不管Example類怎麼變化,上述代碼不變,甚至能夠更換Example的兄弟類Example2 , Example3 , Example4……,只要他們繼承ExampleInterface就能夠。
3.從JVM的角度看,咱們使用關鍵字new建立一個類的時候,這個類能夠沒有被加載。 可是使用newInstance()方法的時候,就必須保證:

一、這個類已經加載;
二、這個類已經鏈接了。

而完成上面兩個步驟的正是Class的靜態方法forName()所完成的,這個靜態方法調用了啓動類加載器,即加載 java API的那個加載器。 如今能夠看出,newInstance()其實是把new這個方式分解爲兩步,即首先調用Class加載方法加載某個類,而後實例化。這樣分步的好處是顯而易見的。咱們能夠在調用class的靜態加載方法forName時得到更好 的靈活性,提供給了一種降耦的手段。

四、加載數據庫驅動的時候Class.forName的一個很常見的用法是在加載數據庫驅動的時候,代碼以下:

Class.forName("com.gx.sqlserver.jdbc.SQLServerDriver");
Connection con=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;DatabaseName==NP","jph","jph");

爲何在咱們加載數據庫驅動包的時候有的卻沒有調用newInstance( )方法呢?即有的jdbc鏈接數據庫的寫法裏是Class.forName(xxx.xx.xx);而有一些:Class.forName(xxx.xx.xx).newInstance(),爲何會有這兩種寫法呢?

剛纔提到,Class.forName(" ")的做用是要求JVM查找並加載指定的類,若是在類中有靜態初始化器的話,JVM必然會執行該類的靜態代碼段。而在JDBC規範中明確要求這個Driver類必須向DriverManager註冊本身,即任何一個JDBCDriverDriver類的代碼都必須相似以下:

public classMyJDBCDriver implements Driver {
    static{
         DriverManager.registerDriver(new MyJDBCDriver());
       }
  }

既然在靜態初始化器的中已經進行了註冊,因此咱們在使用JDBC時只須要Class.forName(XXX.XXX);就能夠了。

五、最後用最簡單的描述來區分new關鍵字和newInstance()方法的區別:

  1. newInstance: 弱類型。低效率。只能調用無參構造。
  2. new: 強類型。相對高效。能調用任何public構造。

到這裏,Class類就差很少了,能夠開始學習使用反射了。

三、反射的使用

JDK 中,反射相關的 API 能夠分爲下面幾個方面:獲取反射的 Class 對象、經過反射建立類對象、經過反射獲取類屬性方法及構造器。

3.一、獲取Class對象的三種方式

對於爲何第一步是獲取Class對象,是由於我在前面講到過反射的本質理解就是獲得Class對象後反向獲取Student對象的各類成分信息(成分信息包括成員變量、方法、構造方法、所在包、字段屬性等等),因此反射的第一步是獲取須要被反射的類的Class對象。

一、使用Class.forName 靜態方法。
當你知道該類的全路徑名時,你可使用該方法獲取 Class 類對象【最經常使用,必須掌握】
二、使用 .class方法。
這種方法只適合在編譯前就知道操做的 Class,可是這種方法須要導入類的包,依賴性太強,因此用的比第一種稍微要少 【重點】
三、使用類對象的getClass() 方法。
這種方法已經建立了對象,那麼這個時候就不須要去進行反射了,顯得有點畫蛇添足。【不經常使用,瞭解便可

//第一種,使用Class.forName 靜態方法。
Class Student= Class.forname("com.FanSe.Student");//類的全路徑名

//第二種,使用 .class方法。
Class Student= 類名.class;//這種方法須要導入類的包,依賴性太強

//第三種,使用類對象的 getClass() 方法。
Student str = new Student();
Class clz = str.getClass();

小結:開發中通常都用第一種Class.forName 靜態方法,能夠一個字符串傳入(類的全路徑名)也可寫在配置文件中等多種方法。並且須要注意的是在運行期間,一個類,只有一個Class對象產生。

3.二、反射獲取構造方法並使用

1).批量獲取構造方法:
public Constructor[] getConstructors():全部"公有的"構造方法

public Constructor[] getDeclaredConstructors():獲取全部的構造方法(包括私有、受保護、默認、公有)

2).獲取單個的方法,並調用:
public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"能夠是私有的,或受保護、默認、公有;

3)調用構造方法:
Constructor-->newInstance(Object... initargs)

newInstanceConstructor類的方法(管理構造函數的類),api的解釋爲:newInstance(Object... initargs),使用此 Constructor 對象表示的構造方法來建立該構造方法的聲明類的新實例,並用指定的初始化參數初始化該實例。它的返回值是T類型,因此newInstance是建立了一個構造方法的聲明類的新實例對象。併爲之調用。

反射獲取構造方法總結:當咱們去獲取類構造器時,若是要獲取私有方法或私有構造器,則必須使用有 declared 關鍵字的方法。【固然不止構造器,獲取類方法、類屬性也是同樣使用 declared 關鍵字的方法】

下面開始進入實踐代碼階段

建立一個普通Student 類

package fanshe;
 
public class Student {
    
    //---------------構造方法-------------------
    //(默認的構造方法)
    Student(String str){
        System.out.println("(默認)的構造方法 s = " + str);
    }
    
    //無參構造方法
    public Student(){
        System.out.println("調用了公有、無參構造方法執行了。。。");
    }
    
    //有一個參數的構造方法
    public Student(char name){
        System.out.println("姓名:" + name);
    }
    
    //有多個參數的構造方法
    public Student(String name ,int age){
        System.out.println("姓名:"+name+"年齡:"+ age);//這的執行效率有問題,之後解決。
    }
    
    //受保護的構造方法
    protected Student(boolean n){
        System.out.println("受保護的構造方法 n = " + n);
    }
    
    //私有構造方法
    private Student(int age){
        System.out.println("私有的構造方法   年齡:"+ age);
    }
 
}

編寫測試類

package fanshe;
 
import java.lang.reflect.Constructor;
 
 
/*
 * 經過Class對象能夠獲取某個類中的:構造方法、成員變量、成員方法;並訪問成員;
 * 
 * 1.獲取構造方法:
 *      1).批量的方法:
 *          public Constructor[] getConstructors():全部"公有的"構造方法
            public Constructor[] getDeclaredConstructors():獲取全部的構造方法(包括私有、受保護、默認、公有)
     
 *      2).獲取單個的方法,並調用:
 *          public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:
 *          public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"能夠是私有的,或受保護、默認、公有;
 *      
 *          調用構造方法:
 *          Constructor-->newInstance(Object... initargs)
 */
public class Constructors {
 
    public static void main(String[] args) throws Exception {
        //1.加載Class對象
        Class clazz = Class.forName("fanshe.Student");
        
        
        //2.獲取全部公有構造方法
        System.out.println("**********************全部公有構造方法*********************************");
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        
        
        System.out.println("************全部的構造方法(包括:私有、受保護、默認、公有)***************");
        conArray = clazz.getDeclaredConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        
        System.out.println("*****************獲取公有、無參的構造方法*******************************");
        Constructor con = clazz.getConstructor(null);
        //1>、由於是無參的構造方法因此類型是一個null,不寫也能夠:這裏須要的是一個參數的類型,切記是類型
        //2>、返回的是描述這個無參構造函數的類對象。
    
        System.out.println("con = " + con);
        //調用構造方法
        Object obj = con.newInstance();
    //  System.out.println("obj = " + obj);
    //  Student stu = (Student)obj;
        
        System.out.println("******************獲取私有構造方法,並調用*******************************");
        con = clazz.getDeclaredConstructor(char.class);
        System.out.println(con);
        //調用構造方法
        con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)
        obj = con.newInstance('男');
    }
    
}

測試結果

**********************全部公有構造方法*********************************
public fanshe.Student(java.lang.String,int)
public fanshe.Student(char)
public fanshe.Student()
************全部的構造方法(包括:私有、受保護、默認、公有)***************
private fanshe.Student(int)
protected fanshe.Student(boolean)
public fanshe.Student(java.lang.String,int)
public fanshe.Student(char)
public fanshe.Student()
fanshe.Student(java.lang.String)
*****************獲取公有、無參的構造方法*******************************
con = public fanshe.Student()
調用了公有、無參構造方法執行了。。。
******************獲取私有構造方法,並調用*******************************
public fanshe.Student(char)
姓名:男

3.三、反射獲取成員變量並調用

建立Student 類

package fanshe.field;
 
public class Student {
    public Student(){
        
    }
    //**********字段*************//
    public String name;
    protected int age;
    char sex;
    private String phoneNum;
    
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
    }
    
    
}

測試類

package fanshe.field;
import java.lang.reflect.Field;
/*
 * 獲取成員變量並調用:
 * 
 * 1.批量的
 *      1).Field[] getFields():獲取全部的"公有字段"
 *      2).Field[] getDeclaredFields():獲取全部字段,包括:私有、受保護、默認、公有;
 * 2.獲取單個的:
 *      1).public Field getField(String fieldName):獲取某個"公有的"字段;
 *      2).public Field getDeclaredField(String fieldName):獲取某個字段(能夠是私有的)
 * 
 *   設置字段的值:
 *      Field --> public void set(Object obj,Object value):
 *                  參數說明:
 *                  1.obj:要設置的字段所在的對象;
 *                  2.value:要爲字段設置的值;
 * 
 */
public class Fields {
 
        public static void main(String[] args) throws Exception {
            //1.獲取Class對象
            Class stuClass = Class.forName("fanshe.field.Student");
            //2.獲取字段
            System.out.println("************獲取全部公有的字段********************");
            Field[] fieldArray = stuClass.getFields();
            for(Field f : fieldArray){
                System.out.println(f);
            }
            System.out.println("************獲取全部的字段(包括私有、受保護、默認的)********************");
            fieldArray = stuClass.getDeclaredFields();
            for(Field f : fieldArray){
                System.out.println(f);
            }
            System.out.println("*************獲取公有字段**並調用***********************************");
            Field f = stuClass.getField("name");
            System.out.println(f);
            //獲取一個對象
            Object obj = stuClass.getConstructor().newInstance();//產生Student對象--》Student stu = new Student();
            //爲字段設置值
            f.set(obj, "劉德華");//爲Student對象中的name屬性賦值--》stu.name = "劉德華"
            //驗證
            Student stu = (Student)obj;
            System.out.println("驗證姓名:" + stu.name);
            
            
            System.out.println("**************獲取私有字段****並調用********************************");
            f = stuClass.getDeclaredField("phoneNum");
            System.out.println(f);
            f.setAccessible(true);//暴力反射,解除私有限定
            f.set(obj, "18888889999");
            System.out.println("驗證電話:" + stu);
            
        }
    }

測試效果

************獲取全部公有的字段********************
public java.lang.String fanshe.field.Student.name
************獲取全部的字段(包括私有、受保護、默認的)********************
public java.lang.String fanshe.field.Student.name
protected int fanshe.field.Student.age
char fanshe.field.Student.sex
private java.lang.String fanshe.field.Student.phoneNum
*************獲取公有字段**並調用***********************************
public java.lang.String fanshe.field.Student.name
驗證姓名:劉德華
**************獲取私有字段****並調用********************************
private java.lang.String fanshe.field.Student.phoneNum
驗證電話:Student [name=劉德華, age=0, sex=

因而可知,調用字段時:須要傳遞兩個參數:
Object obj =stuClass.getConstructor().newInstance();//產生Student對象--》Student stu = new Student(); //爲字段設置值 f.set(obj, "劉德華");//爲Student對象中的name屬性賦值--》stu.name = "劉德華"
第一個參數:要傳入設置的對象,第二個參數:要傳入實參

3.四、反射獲取成員方法並調用

建立student類

package fanshe.method;
 
public class Student {
    //**************成員方法***************//
    public void show1(String s){
        System.out.println("調用了:公有的,String參數的show1(): s = " + s);
    }
    protected void show2(){
        System.out.println("調用了:受保護的,無參的show2()");
    }
    void show3(){
        System.out.println("調用了:默認的,無參的show3()");
    }
    private String show4(int age){
        System.out.println("調用了,私有的,而且有返回值的,int參數的show4(): age = " + age);
        return "abcd";
    }
}

編寫測試類

package fanshe.method;
 
import java.lang.reflect.Method;
 
/*
 * 獲取成員方法並調用:
 * 
 * 1.批量的:
 *      public Method[] getMethods():獲取全部"公有方法";(包含了父類的方法也包含Object類)
 *      public Method[] getDeclaredMethods():獲取全部的成員方法,包括私有的(不包括繼承的)
 * 2.獲取單個的:
 *      public Method getMethod(String name,Class<?>... parameterTypes):
 *                  參數:
 *                      name : 方法名;
 *                      Class ... : 形參的Class類型對象
 *      public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 * 
 *   調用方法:
 *      Method --> public Object invoke(Object obj,Object... args):
 *                  參數說明:
 *                  obj : 要調用方法的對象;
 *                  args:調用方式時所傳遞的實參;
):
 */
public class MethodClass {
 
    public static void main(String[] args) throws Exception {
        //1.獲取Class對象
        Class stuClass = Class.forName("fanshe.method.Student");
        //2.獲取全部公有方法
        System.out.println("***************獲取全部的」公有「方法*******************");
        stuClass.getMethods();
        Method[] methodArray = stuClass.getMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        System.out.println("***************獲取全部的方法,包括私有的*******************");
        methodArray = stuClass.getDeclaredMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        System.out.println("***************獲取公有的show1()方法*******************");
        Method m = stuClass.getMethod("show1", String.class);
        System.out.println(m);
        //實例化一個Student對象
        Object obj = stuClass.getConstructor().newInstance();
        m.invoke(obj, "劉德華");
        
        System.out.println("***************獲取私有的show4()方法******************");
        m = stuClass.getDeclaredMethod("show4", int.class);
        System.out.println(m);
        m.setAccessible(true);//解除私有限定
        Object result = m.invoke(obj, 20);//須要兩個參數,一個是要調用的對象(獲取有反射),一個是實參
        System.out.println("返回值:" + result);
        
        
    }
}

測試結果:

***************獲取全部的」公有「方法*******************
public void fanshe.method.Student.show1(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************獲取全部的方法,包括私有的*******************
public void fanshe.method.Student.show1(java.lang.String)
private java.lang.String fanshe.method.Student.show4(int)
protected void fanshe.method.Student.show2()
void fanshe.method.Student.show3()
***************獲取公有的show1()方法*******************
public void fanshe.method.Student.show1(java.lang.String)
調用了:公有的,String參數的show1(): s = 劉德華
***************獲取私有的show4()方法******************
private java.lang.String fanshe.method.Student.show4(int)
調用了,私有的,而且有返回值的,int參數的show4(): age = 20
返回值:abcd

因而可知:
m = stuClass.getDeclaredMethod("show4", int.class);//調用制定方法(全部包括私有的),須要傳入兩個參數,第一個是調用的方法名稱,第二個是方法的形參類型,切記是類型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//須要兩個參數,一個是要調用的對象(獲取有反射),一個是實參
System.out.println("返回值:" + result);

3.五、 反射main方法

Student類

package fanshe.main;
 
public class Student {
 
    public static void main(String[] args) {
        System.out.println("main方法執行了。。。");
    }
}

編寫測試類

package fanshe.main;
 
import java.lang.reflect.Method;
 
/**
 * 獲取Student類的main方法、不要與當前的main方法搞混了
 */
public class Main {
    
    public static void main(String[] args) {
        try {
            //一、獲取Student對象的字節碼
            Class clazz = Class.forName("fanshe.main.Student");
            
            //二、獲取main方法
             Method methodMain = clazz.getMethod("main", String[].class);//第一個參數:方法名稱,第二個參數:方法形參的類型,
            //三、調用main方法
            // methodMain.invoke(null, new String[]{"a","b","c"});
             //第一個參數,對象類型,由於方法是static靜態的,因此爲null能夠,第二個參數是String數組,這裏要注意在jdk1.4時是數組,jdk1.5以後是可變參數
             //這裏拆的時候將  new String[]{"a","b","c"} 拆成3個對象。。。因此須要將它強轉。
             methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
            // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        
    }
}

測試結果

main方法執行了。。。

3.六、反射方法的其它使用 ---經過反射運行配置文件內容

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

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

className = cn.fanshe.Student
methodName = show

demo類

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();

3.七、反射方法的其它使用 ---經過反射越過泛型檢查

泛型用在編譯期,編譯事後泛型擦除(消失掉)。因此是能夠經過反射越過泛型檢查的
測試類:

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

個人反射的另外一篇文章Java基礎重點——反射機制入門、使用 ,寫的不怎好,不過也能夠參照對比着看看,仍是不錯的。

最後,歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

在這裏插入圖片描述

相關文章
相關標籤/搜索