反射機制(什麼是反射、反射操作構造器、方法、欄位、反射載入資源)

1、什麼是反射:

問題1:

1、對象有編譯類型和運行類型

Object obj = new java.util.Date();

  • 編譯類型:是聲明時的類型,Object 類型
  • 運行類型:是new 的類型,實例化類型 java.util.Date

需求:通過obj對象,調用java.util.Date類中的toLocaleString方法。

直接 obj.toLocaleString(); 此時編譯報錯,編譯時會檢查編譯類型中是否存在toLocaleString方法。

  • 解決方案:因為咱知道obj的真實類型是java.util.Date類,可以進行類型的強制轉化

    java.util.Date d = (java.util.Date)obj; d.toLocaleString();//此時編譯成功,成功調用方法

    但是,若是不知道obj的真實類型,就不能強轉(底層有一個方法-返回類型是Object類型的java.util.Date對象)

  • 底層程式碼:
    public static Object getObj(){
    ​     return new java.util.Date();
    }

//使用反射:
Object obj = new java.util.Date();
Method m = obj.getClass().getMethod("toLocaleString");
m.invoke();

問題2:

  • 面向對象思維:萬物皆對象,那麼:類這種事物是什麼對象呢?又需要用什麼類來描述這種對象呢?
    在這裡插入圖片描述

當類一旦被載入進記憶體,就會變成Class對象【位元組碼對象】

  • 類-用來描述對象,Class-用來描述類這種對象的類
  • 好比是元數據:描述數據的描述數據。

——- 反射:就是得到類(這種對象的)類【java.lang.Class】,得到類的元數據的過程。

  • 細緻描述:在運行時期,動態地區獲取某個類中的成員的資訊(構造器、方法、欄位、內部類、介面、父類等等)。

■ 還是根據萬物皆對象的思維,我們把類中的每一種成員,都描述成一個新的類:

  • Class:表示所有的類
  • Constructor:表示所有的構造器
  • Method:表示所有的方法
  • Field:表示所有的欄位

2、Class 類和 Class實例:

2-1、Class 類和 Class實例概念:

(1)Class類:用來描述類或介面的類型,描述類的類

(2)Class類的實例:在jvm中的一份份位元組碼,Class 實例表示在jvm中的類或者介面,枚舉是一種特殊的類,註解是一種特殊的介面。

  • Class 對象:是位元組碼對象

□ 當程式第一次使用某個類(例如:java.util.Date類)的時候,就會把該類的位元組碼(編譯生成的位元組碼),載入進行虛擬機,並創建一個Class對象【位元組碼對象】,此時的Class對象可以表示java.util.Date類的位元組碼,
但是Class 類可以表示N個類的位元組碼對象問題:要怎麼區分Class類此時表示的是哪一個類的位元組碼呢?

解決方案:Class類的設計者使用了泛型 Class

  • 例如: java.lang.String 類的位元組碼類型是 Class<java.lang.String>, 而 java.util.Date類的位元組碼類型是 Class<java.util.Date>

2-2、獲取Class實例的三種方式:

  • 使用最多的是第三種方式,通過Class類中的靜態方法forName(“類的全限定名稱”)獲取Class對象,在框架中大量使用。
//需求:獲取java.util.Date類的位元組碼對象
//方式1:通過class屬性
Class<java.util.Date> clazz1 = java.util.Date.class;
		
//方式2:通過對象的getClass方法,getClass方法是Object類的方法
java.util.Date date = new java.util.Date();
Class<?> clazz2 = date.getClass();
		
//方式3:通過Class類中的靜態方法forName(String className)
Class clazz3 = Class.forName("java.util.Date");

■ 為什麼創建Class對象不直接new呢?

image

■ 為什麼獲取Class實例的三種方式的結果是相同的呢?

  • 同一個類在jvm/記憶體中只有一份位元組碼對象。
    image

2-3、九大內置的Class實例:

■ 問題:上述的三種方式獲取Class對象,對於基本類型:不能表示為對象,不能使用getClass方式,基本類型也沒有類名的概念,不能使用Class.forName()方式,如何表示基本類型的位元組碼對象呢?—通過第一種方式

  • 所有的數據類型都有class屬性 Class clazz = 數據類型.class;

  • 九大內置Class實例:jvm中預先提供好的Class實例,分別是byte、short、int、long、float、double、char、void

    ​ 表示:byte.class,short.class,int.class……void.class

2-4、數組的Class實例:

  • 數組時引用類型,其實就是對象

  • 如何表示數組的Class實例呢? 方式1:數組類型.class; 方式2: 數組對象.getClass();

  • 注意:所有的具有相同的維數、相同的元素類型的數組共享同一份位元組碼對象,和元素沒有關係。

2-5、Class和Object的區別:

  • Class:描述所有的型,所以Class類中應該具有所有類型的相同方法
  • Object:描述所有的對象,所以Object類中應該具有所有對象的共同方法

3、操作構造器(獲取類中的構造器、使用反射創建對象)

✿ 需求:通過反射來獲取一個類的構造器

① 獲取該類的位元組碼(要獲取一個類的構造器,需要將該類先載入進虛擬機)

​ ② 從該位元組碼對象中去尋找需要獲取的構造器

(1)Class 類獲取構造器方法

  • Constructor 類:表示類中構造器的類型,Constructor的實例就是某一個類中的某一個構造器

  • 獲取所有構造器: getConstructors() 【所有,public修飾的】 getDeclaredConstructors()【所有,和訪問許可權無關】

    /* 獲取所有的構造器 */
    
    //1、獲取構造器所在類的位元組碼對象
    Class clazz = User.class;
    //2、獲取位元組碼對象中所有的構造器
    // getConstructors() 只能獲取當前Class對象所表示類的【public修飾的構造器】
    Constructor[] cs = clazz.getConstructors();
    //getDeclaredConstructors() 獲取當前Class所表示的所有的構造器【和訪問許可權無關】
    cs = clazz.getDeclaredConstructors();
    
  • 獲取指定的一個構造器

    • 公共無參構造器 getConstructor()
    • 公共有參構造器 getConstructor(Class<?>…parameterTypes) 參數parameterTypes表示構造器參數的Class類型
    • 任何類型的有參構造器 getDeclaredConstructor(Class<?>…parameterTypes) 【和訪問許可權無關】
    /* 獲取指定的一個構造器 */
    
    //1、獲取構造器所在類的位元組碼對象
    Class clazz = User.class;
    //2、獲取位元組碼對象中獲取指定的一個構造器
    
    //獲取public User() 無參構造器
    Constructor constructor = clazz.getConstructor();
    //獲取public User(String name) 參數是String 的有參構造器
    constructor = clazz.getConstructor(String.class);
    //獲取private User(String name, int age) 的私有有參構造器
    constructor = clazz.getDeclaredConstructor(String.class, int.class);
    

(2)使用反射創建對象–調用Constructor的方法來創建對象

✿ 構造器最大作用:創建對象

★ 為什麼需要使用反射來創建對象,不選擇直接new?

① 不知道obj的真實類型
② 在框架中,提供給我們的都是字元串(例如 spring框架的xml,對xml解析,得到元素的屬性值是字元串,需要通過Class.forName方法創建對象)
③ 解除硬編碼、消除耦合

■ 使用反射創建對象的步驟:

1)找到構造器所在類的位元組碼對象

2)獲取構造器對象

3)使用反射創建對象(調用構造器的方法創建對象)

✿ 調用構造器的方法創建對象:

  • 若一個類中的構造器可以被外界訪問同時沒有參數,那麼直接使用Class類的newInstance方法創建對象
  • 要調用私有成員(例如調用私有構造器方法):需要先設置當前構造器為可以訪問 Constructor對象.setAccessible(true)
    image
//類中的構造器可以被外界訪問同時沒有參數,直接使用Class類的newInstance方法創建對象
Class<Person> clazz = Person.class;
Constructor<Person> con= clazz.getConstructor();
con.newInstance();
System.out.println("========================");

//反射:調用構造器的方法創建對象[無參構造器]
clazz = Person.class;
con= clazz.getConstructor(String.class);
con.newInstance("shan");
System.out.println("========================");

//反射:調用構造器的方法創建對象[無參構造器]
clazz = Person.class;
con= clazz.getDeclaredConstructor(String.class, int.class);
//設置當前的構造器【私有的】可以訪問
con.setAccessible(true);
con.newInstance("shan", 10);		

4、操作方法(獲取類中的方法、使用反射調用方法)

——跟操作構造器差不多啦。。。

4-1、獲取類中的方法Method

■ 獲取類中的所有方法:
  • public Method getMethods():獲取包括自身和繼承過來的所有public 方法

  • public Method getDeclaredMethods():獲取自身所有方法(不包括繼承、和訪問許可權無關

■ 獲取類中指定的一個方法【方法簽名:方法名+參數列表】:
  • public Method getMethod(String methodName,Class<?>…parameterTypes):獲取指定名稱的一個public方法(包括繼承的)
  • public Method getDeclaredMethod(String methodName,Class<?>…parameterTypes):獲取指定名稱方法(不包括繼承、和訪問許可權無關)
    • 參數類型parameterTypes為泛型的時候,自動提升為Object類型

4-2、使用反射調用方法(調用Method的方法來執行方法)

■ 步驟:

1)獲取方法所在類的位元組碼對象

2)獲取方法對象

3)使用反射調用方法

■ 使用反射調用一個方法:

在Method類中有方法:public Object invoke(Object obj, Object…args): 表示調用當前Method所表示的方法

  • obj 表示被調用的方法底層所屬的對象【Class對象.newInstance() 當無參外界可訪問時】

  • 設置可訪問私有的成員 Method對象.setAccessible(true)

□ 使用反射調用靜態方法:
  • 靜態方法不屬於任何對象,靜態方法屬於類本身

    此時把invoke方法的第一個參數設置為null即可 public Object invoke(null, Object…args)

□ 使用反射調用數組參數(可變參數):
  • 王道:調用方法的時候把世界參數統統作為Object數組的元素即可 Method對象.invoke(Object obj, new Object[]{ 所有實參 })
    image

5、操作字

——跟操作構造器差不多啦。。。

6、反射機制—載入資源

載入資源文件路徑: db.properties

  • 注意:載入properties文件,只能使用Propertis類的load方法

■ 方式1:使用絕對路徑–new FileInputStream(“絕對路徑”) [不推薦]

Properties p = new Properties();
InputStream in = new FileInputStream("db.properties文件的絕對路徑");//絕對路徑
p.load(in);//載入資源

✿ 方式2:使用相對路徑–相對於classpath的根路徑(位元組碼輸出路徑) [推薦]

  • 此時得使用類載入器ClassLoader,類載入器默認就是從classpath根路徑去尋找文件的
Properties p = new Properties();
//ClassLoader loader = CreateObjDemo.class.getClassLoader();//可以通過Class類的getClassLoader()方法獲取
ClassLoader loader = Thread.currentThread().getContextClassLoader();//一般選擇通過執行緒獲取類載入器【因為可以不用寫類名啦】
InputStream in = loader.getResourceAsStream("db.properties");//相對路徑-相對於當前項目的輸出路徑
p.load(in);//載入資源

■ 方式3:使用相對路徑–相對於當前載入資源文件的位元組碼的根路徑

Properties p = new Properties();
InputStream in = CreateObjDemo.class.getResourceAsStream("db.properties");//相對路徑-相對於載入當前資源文件的位元組碼根路徑
p.load(in);//載入資源