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

  • 2019 年 11 月 11 日
  • 筆記

前言

很多講解反射的部落格文章並沒有詳細講解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基礎重點——反射機制入門、使用 ,寫的不怎好,不過也可以參照對比著看看,還是不錯的。

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

在這裡插入圖片描述