1. 程式人生 > >【java基礎】程式設計師你真的理解反射機制嗎?

【java基礎】程式設計師你真的理解反射機制嗎?

目錄

  • 前言
  • 1、反射的概述
  • 2、正式使用反射之前很有必要了解的Class類
  • 3、反射的使用

前言

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

參考資料:

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

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

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

@

1、反射的概述

一句話定義反射就是在執行時才知道要操作的類是什麼,並且可以在執行時獲取類的完整構造,並呼叫對應的方法,所謂反射其實是獲取類的位元組碼檔案,也就是.class檔案。平時我們要呼叫某個類中的方法的時候都需要建立該類的物件,通過物件去呼叫類裡面的方法,反射則是一開始並不知道我要初始化的類物件是什麼,自然也無法使用 new 關鍵字來建立物件了,在這種情況下(沒有建立物件)我們都能夠對它的方法和屬性進行呼叫,我們把這種動態獲取物件資訊和呼叫物件方法的功能稱之為反射機制。

反射才體現出java是如何建立物件的。當java虛擬機器(JVM)載入一個class類檔案時,就建立這個類的class物件,以後的物件都是由這個class物件建立的,所以同一個類的所有物件的class物件都是一個,比如A a=new A(); A b=new A(); a.class()==b.class()返回true.

2、正式使用反射之前很有必要了解的Class類

很多講解反射的部落格文章並沒有詳細講解Class類,~當然包括之前的我也一樣~,這樣的文章並不能讓大多數初學者真正理解反射,而恰恰反射的原理就在於Class物件!可見他的重要性,這篇文章我將總結一下關於Class類的知識,可能還不是很全面,各位擔待點哈~

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

大家到知道,一個類擁有成員變數、方法、構造方法、所在包、欄位屬性等等成分組成,而反射就是把java類中的各種成分對映成一個個的Java物件,可以理解為利用反射技術對一個類進行“解剖”,把各個組成部分對映成一個個的物件。其實,一個類中這些成員方法、構造方法、在加入類中都有一個Class類來描述,在正式使用反射之前,很有必要先來了解了解這個Class類!

反射的原理就在於Class物件

2.1、 普通類的載入過程

熟悉一下載入的時候:Class物件的由來是將class檔案讀入記憶體,併為之建立一個Class物件。反射的本質理解就是得到Class物件後反向獲取Student物件的各種成分資訊(成分資訊包括成員變數、方法、構造方法、所在包、欄位屬性等等),下面就以Student物件為例,圖解Student類的正常載入過程~

可以看出圖中這個Class物件很特殊。我們進一步瞭解一下這個Class類!

2.2、 分析Class類的API(1.7的API)

Class 類的例項表示正在執行的 Java應用程式中的類和介面。也就是jvm中有N多的例項每個類都有該Class物件。(包括基本資料型別) Class 沒有公共構造方法。Class物件是在載入類時由 Java 虛擬機器以及通過呼叫類載入器中的defineClass方法自動構造的。也就是這不需要我們自己去處理建立Class物件,JVM已經幫我們建立好了。

2.3、 Class類的常用方法

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

1、getName() : 返回此 Class物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱

一個Class物件描述了一個特定類的屬性,Class類中最常用的方法getName以 String 的形式返回此 Class
物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱。

2、newInstance(): 為類建立一個例項,但只能呼叫預設構造器(無引數構造器)

Class還有一個有用的方法可以為類建立一個例項,這個方法叫做newInstance()。例如:
x.getClass.newInstance(),建立了一個同x一樣型別的新例項。newInstance()方法只能呼叫預設構造器(無引數構造器)初始化新建物件。

3、getClassLoader()

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

4、getComponentType()

getComponentType() 方法主要返回表示陣列元件型別的 Class。

5、getSuperclass()

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

6、isArray()

isArray() 判定此 Class 物件是否表示一個數組類。

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

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

另外虛擬機器為每種型別管理一個獨一無二的Class物件,也就是說Class物件是惟一的。因此可以使用==操作符來比較類物件。例如:

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

2.4、 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.5、關於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()方法的時候,就必須保證:

1、這個類已經載入;
2、這個類已經連線了。

而完成上面兩個步驟的正是Class的靜態方法forName()所完成的,這個靜態方法呼叫了啟動類載入器,即載入 java API的那個載入器。 現在可以看出,newInstance()實際上是把new這個方式分解為兩步,即首先呼叫Class載入方法載入某個類,然後例項化。這樣分步的好處是顯而易見的。我們可以在呼叫class的靜態載入方法forName時獲得更好 的靈活性,提供給了一種降耦的手段。

4、載入資料庫驅動的時候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);就可以了。

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

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

到這裡,Class類就差不多了,可以開始學習使用反射了。

3、反射的使用

JDK 中,反射相關的 API 可以分為下面幾個方面:獲取反射的 Class 物件、通過反射建立類物件、通過反射獲取類屬性方法及構造器。

3.1、獲取Class物件的三種方式

對於為什麼第一步是獲取Class物件,是因為我在前面講到過反射的本質理解就是得到Class物件後反向獲取Student物件的各種成分資訊(成分資訊包括成員變數、方法、構造方法、所在包、欄位屬性等等),所以反射的第一步是獲取需要被反射的類的Class物件。

1、使用Class.forName 靜態方法。
當你知道該類的全路徑名時,你可以使用該方法獲取 Class 類物件【最常用,必須掌握】
2、使用 .class方法。
這種方法只適合在編譯前就知道操作的 Class,但是這種方法需要匯入類的包,依賴性太強,所以用的比第一種稍微要少 【重點】
3、使用類物件的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.2、反射獲取構造方法並使用

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.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.4、反射獲取成員方法並呼叫

建立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.5、 反射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 {
            //1、獲取Student物件的位元組碼
            Class clazz = Class.forName("fanshe.main.Student");
            
            //2、獲取main方法
             Method methodMain = clazz.getMethod("main", String[].class);//第一個引數:方法名稱,第二個引數:方法形參的型別,
            //3、呼叫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.6、反射方法的其它使用 ---通過反射執行配置檔案內容

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.7、反射方法的其它使用 ---通過反射越過泛型檢查

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

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基礎重點——反射機制入門、使用 ,寫的不怎好,不過也可以參照對比著看看,還是不錯的。

最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

相關推薦

java基礎程式設計師真的理解反射機制

目錄 前言 1、反射的概述 2、正式使用反射之前很有必要了解的Class類 3、反射的使用 前言 很多講解反射的部落格文章並沒有詳細講解Class類,~當然包括之前的我也一樣~,

Java基礎關於列舉類可能不知道的事

目錄 談談列舉 1. 列舉類的定義 2. 列舉類的底層實現 3. 列舉類的序列化實現 4. 用列舉實現單列 5. 列舉例項的建立過程是執行緒安全的

Java基礎讓編碼問題不再困惑

目錄 1. ASCII編碼 2. Unicode編碼 3. UTF-8編碼 4. UTF8、UTF16和UTF32之間的區別 5. GBK、GB2312和GB18030之間的區別

Java基礎聽說過JMX麼

目錄 什麼是JMX 相關概念 MBean程式碼示例 MBean本地連線 MBean遠端連線 通過Spring釋出MBean 訊息訂閱釋出 參考

從今天開始好好學資料結構04程式設計師心中就沒點“樹”

目錄 樹(Tree) 二叉樹(Binary Tree) 前面我們講的都是線性表結構,棧、佇列等等。今天我們講一種非線性表結構,樹。樹這種資料結構比線性表的資料結構要複雜得多,內容也比較多,首先我們先從樹(Tre

Java基礎RTTI與反射Java

start auth try dword star sse from tac sed 1 ; Example assembly language program -- 2 ; Author: Karllen 3 ; Date: revised 05/2014

轉載:Java基礎InputStream 、 InputStreamReader和BufferedReader

gen 結果 取字符 sys try eight string font buffer 來源:http://blog.csdn.net/zgljl2012/article/details/47267609 在Java中,上述三個類經常用於處理數據流,下面介紹一下三個類的

Java基礎Java常見的異常

java exception Java常見的異常1. java.lang.NullPointerException (空指針異常)調用了未經初始化的對象或者是不存在的對象2. java.lang.ClassNotFoundException 指定的類不存在3. java.lang.NumberForm

Java基礎Java基本數據類型與位運算

右移 數據 bits 類型 span 網上 height 使用 常數 1.賦值運算符 賦值使用操作符“=”。它的意思是“取右邊的值(即右值),把它復制給左邊(即左值)”。右值可以是任何 常數、變量或者表達式 (只要它能 生成

Java基礎Java運算符優先級

() logs 分享 body rowspan 單元 ima idt string 序列號 符號 名稱 結合性(與操作數) 目數 說明 1 . 點 從左到右

java基礎重寫equals()方法的同時要重寫hashCode()方法

而且 通過 才會 默認 什麽 需要 現在 ash 字段 1、 為什麽要重寫equals方法? 因為Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存,地址則相等,否則不相等;如果你現在需要利用對象裏面字段的值來判斷是否相等,則重寫equals方法。

Java基礎基礎精華總結

  一:java概述(快速瀏覽): 1991 年Sun公司的James Gosling等人開始開發名稱為 Oak 的語言,希望用於控制嵌入在有線電視交換盒、PDA等的微處理器; 1994年將Oak語言更名為Java;   Java的三種技術架構: JAVA

Java基礎註解(Annotation)

  Annotation,程式碼裡的特殊標記,在編譯、類載入、執行時被讀取,並執行相應的處理。 使用註解,在不改變原有邏輯的情況下,在原始檔中嵌入一些補充資訊。 Annotation提供了一種為程式元素設定元資料的方法。 Ann

Java基礎-多重For迴圈的兩種跳出方式

先來小段Demo,自己跑一下就能看到效果了: public static void main(String[] args) { List<String> listA = new ArrayList<String>(); List<String> lis

讀書筆記程式設計師面試筆記

第二部分 面試筆試技術篇 第10章 C++程式設計基礎 (2018年11月6日閱讀) 10.1 程式的編譯和執行 小結: 【面試題1】 【面試題2】 【面試題3】 10.2 變數 小結: 【面試題1】 【面試題2】 【面試題3】 10.3 條件語句和迴圈語句 小結: 【面試題1】

Java基礎程式碼塊,構造程式碼塊,靜態程式碼塊

程式碼塊 public class Demo { public static void main(String[] args){ { int a = 1; System.out.println(a); }

Java基礎static關鍵字

通過static宣告的屬性或者方法可以通過類名稱直接呼叫 static方法只能呼叫static屬性或者方法,不能呼叫非static屬性或者方法 非static方法則不受限,可以任意呼叫static宣告的屬性或者方法 注意:main方法本身就是一個static方法 示例1:static方法呼叫

Java基礎Java 基本資料型別

Java 的兩大資料型別: 內建資料型別 Java語言提供了八種基本型別。六種數字型別(四個整數型,兩個浮點型),一種字元型別,還有一種布林型。 byte: byte 資料型別是8位、有符號的,以二進位制補碼錶示的整數; 最小值是 -1

躍遷之路585天程式設計師高效學習方法論探索系列(實驗階段342-2018.09.13)

@(躍遷之路)專欄 【躍遷之路】獎勵金計劃正式開始 從2018.7.1起,【躍遷之路】獎勵金計劃正式起航,從今以後,, 每月1日,我會將自己個人上月收入的1%計入【躍遷之路】獎勵金池,積累到足夠金額後,將適時用於獎勵那些雖然身處困境,卻依然不放棄努力,通過堅持,不斷

躍遷之路586天程式設計師高效學習方法論探索系列(實驗階段343-2018.09.14)

@(躍遷之路)專欄 【躍遷之路】獎勵金計劃正式開始 從2018.7.1起,【躍遷之路】獎勵金計劃正式起航,從今以後,, 每月1日,我會將自己個人上月收入的1%計入【躍遷之路】獎勵金池,積累到足夠金額後,將適時用於獎勵那些雖然身處困境,卻依然不放棄努力,通過堅持,不斷