1. 程式人生 > >java的反射機制(重要)

java的反射機制(重要)

ati 接收 模式 stack 判斷 try per image declare

1,反射的概念

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

java程序的加載過程:源文件 .java --- 經過編譯(javac.exe)--- 得到一個或多個 .class文件 --- 再運行(java.exe) --- 就會對需要用到的類進行加載(通過JVM的類加載器)--- 把 .class 加載到內存後 JVM會自動根據.class給java.lang.Class實例化一個Class對象,該對象和.class一一對應(即一個類就一個Class對象)--- 然後再根據 Class對象創建該類的對象。

技術分享圖片

因此:java.lang.Class就是反射的源頭。

1)java.lang.Class的實例表示正在運行的 Java 應用程序中的類和接口。也就是jvm中有N多的實例每個類都有該Class對象。(包括基本數據類型)

2)java.lang.Class沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的defineClass 方法自動構造的。也就是這不需要我們自己去處理創建,JVM已經幫我們創建好了。

2,反射的優缺點

靜態編譯:在編譯時確定類型,綁定對象,即通過。
動態編譯:運行時確定類型,綁定對象。動態編譯最大限度發揮了Java的靈活性,體現了多態的應用,有以降低類之間的藕合性。

反射機制的優點:可以實現動態創建對象和編譯,體現出很大的靈活性(特別是在J2EE的開發中它的靈活性就表現的十分明顯)。通過反射機制我們可以獲得類的各種內容,進行反編譯。

反射機制的缺點:對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麽並且讓它滿足我們的要求。這類操作總是慢於直接執行相同的操作。

3,反射的用法

3.1,獲取java.lang.Class對象的三種方法

  1. 通過運行時類的對象,調用其getClass()方法
  2. 調用運行時類的.class屬性
  3. 調用Class的靜態方法forName(String className)。此方法報ClassNotFoundException
public class Student {
}
public class Reflect {
    public static void main(String[] args) {
        // (1)通過運行時類的對象,調用其getClass()方法
        Student stu1 = new Student();// 這一new 產生一個Student對象,一個Class對象。
        Class stuClass = stu1.getClass();// 獲取Class對象
        System.out.println(stuClass.getName());

        // (2)調用運行時類的.class屬性
        Class stuClass2 = Student.class;
        System.out.println(stuClass == stuClass2);// 判斷第一種方式獲取的Class對象和第二種方式獲取的是否是同一個

        // (3)調用Class的靜態方法forName(String className)。此方法報ClassNotFoundException
        try {
            Class stuClass3 = Class.forName("com.jp.Student");// 註意此字符串必須是真實路徑
            System.out.println(stuClass3 == stuClass2);// 判斷三種方式是否獲取的是同一個Class對象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

說明:

1)在運行期間,一個類,只有一個Class對象產生。上面三種方法都是去內存獲取這個Student這個類(即java.lang.Class的一個實例)

2)如果Student類寫錯了,第一二種編譯時就報錯(編譯器會對java源碼進行詞法分析、語法分析、語義分析等步驟才編譯為字節碼); 第三種編譯時會通過,但是運行時會報錯;這就說明了第三種是動態的編譯

為了說明 2)來做個試驗

第一步:把Student類改成錯的

public class Student {
    int a = gg;//很明顯錯的
}

第二步:分別單獨運行Reflect類裏的三種獲取Class實例對象的方法

運行前涉及到的兩個類

技術分享圖片

單獨運行第一種方法的結果:編譯錯誤。說明也對Student.java進行了編譯,如果Student類沒有錯誤也會生成.class(單獨運行第二種方法和第一種結果一樣)

技術分享圖片技術分享圖片

單獨運行第三種方法的結果:編譯成功,說明編譯時並不會去編譯Class.forName()裏的類

技術分享圖片技術分享圖片

3.2,有了java.lang.Class實例(即除Class類的其他類的運行時類)後可以做什麽

3.2.1 應用一:可以創建對應的運行時類的對象

//方法一:Class的newInstanc()方法
@Test
  public void test1() throws Exception{
      Class clazz = Class.forName("com.jp.Animal");//拿到Class實例(即運行時類)
      Object obj = clazz.newInstance();
      Animal a = (Animal)obj;
      System.out.println(a);
  }
//方法二:Class的getDeclaredConstructor()方法,拿到指定的構造器,再用構造器的newInstance()方法
    @Test
    public void test2() throws Exception{
        Class clazz = Animal.class;//拿到Class實例(即運行時類)
        Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);//這裏和上面說的一致,對象都有Class實例,包括基本數據類型、字符串等等
        cons.setAccessible(true);
        Animal a = (Animal)cons.newInstance("Tom",10);
        System.out.println(a);
    }

3.2.2 應用二:調用對應的運行時類中指定的結構(某個指定的屬性、方法、構造器等)

1)獲取構造方法

批量的方法:

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

獲取單個的方法,並調用:

public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:

public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"可以是私有的,或受保護、默認、公有;

2)獲取成員變量

批量的

Field[] getFields():獲取所有的"公有字段"

Field[] getDeclaredFields():獲取所有字段,包括:私有、受保護、默認、公有;

獲取單個的

public Field getField(String fieldName):獲取某個"公有的"字段;

public Field getDeclaredField(String fieldName):獲取某個字段(可以是私有的)

3)獲取方法

批量的:

public Method[] getMethods():獲取所有"公有方法";(包含了父類的方法也包含Object類) public Method[] getDeclaredMethods():獲取所有的成員方法,包括私有的(不包括繼承的) 獲取單個的: 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:調用方式時所傳遞的實參
Method m = stuClass.getMethod("show1", String.class);//拿到運行時類Method的實例對象
System.out.println(m);
//實例化一個Student對象
Object obj = stuClass.getConstructor().newInstance();
Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(反射得到),一個是實參(m方法需要的參數)

如果獲得的Method的實例對象 是 靜態方法的,則運行該靜態方法時,.invoke(null,20),對象類型可以寫null。

4,典型的應用

1)代理設計模式中的動態代理

動態代理:在程序運行時,根據被代理類及其實現的接口,動態的創建一個代理類。當調用代理類的實現的抽象方法時,就發起對被代理類同樣 方法的調用。

2)通過反射運行配置文件內容(這就是IOC容器的基本原理)

Student類:

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

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

className = 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"));// "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值
    }
}

控制臺輸出

技術分享圖片

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

要替換的student2類:

技術分享圖片

配置文件更改為:

技術分享圖片

控制臺輸出: is show2();

3)通過反射越過泛型檢查

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

package fanXing;

import java.lang.reflect.Method;
import java.util.ArrayList;

/*
 * 通過反射越過泛型檢查
 * 
 * 例如:有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值?
 */
public class FanXing {
    public static void main(String[] args) throws Exception {
        ArrayList<String> strList = new ArrayList<>();
        strList.add("aaa");
        strList.add("bbb");

        // strList.add(100);//如果直接在這添加 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);
        }
    }
}

技術分享圖片

參考:

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

https://blog.csdn.net/fuzhongmin05/article/details/61614873

java的反射機制(重要)