反射:運行時的類信息
運行時類型信息使得你可以在程序運行時發現和使用類型信息
1. Class對象
通過
Class
對象可以在運行時發現一個對象完整的類繼承結構
類是程序的一部分,每一個類都會有一個Class
對象。換句話說既每編寫一個新的類,就會產生一個Class
對象。而這些Class
對象信息是保存在我們用javac 類名.java
進行編譯時產生的.class
文件中的。為了生成這個對象,運行這個程序的java虛擬機(JVM)會使用類加載器進行加載
1.1 什么是類加載器
Java類加載器(Java Classloader)是Java運行時環境(Java Runtime Environment)的一部分,負責動態加載Java類到Java虛擬機的內存空間中
即類加載器是Java虛擬機將描述類的數據,例如類的各種方法,構造參數之類的信息從Class
文件加載到內存中去,并且對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。
所有的類都是在對其進行第一次使用的時候,動態加載到JVM中的,即和編譯時需要進行連接工作的語言不通,在java中,類型的加載、連接和初始化都是在程序運行期間完成的。
類加載器在進行加載的時候會首先檢查這個類的Class對象是否已經進行了加載,如果沒有加載,那么默認的類加載器就會根據類名進行查找.class
文件。通過下面例子,我們可以清楚看出類是在第一次被使用的時候才會加載。
static靜態代碼塊的加載是在類加載的時候進行的
class Tom{
static {
System.out.println("Loading Tom");
}
}
class Jerry{
static {
System.out.println("Loading Jerry");
}
}
class Mary{
static {
System.out.println("Loading Mary");
}
}
public class Demo4 {
public static void main(String[] args) {
System.out.println("Inside Main");
new Tom();
System.out.println("After Loading Tom");
new Mary();
System.out.println("After Loading Mary");
new Jerry();
}
}
輸出結果如下:
Inside Main
Loading Tom
After Loading Tom
Loading Mary
After Loading Mary
Loading Jerry
類加載器在加載類的過程中會分為三個階段
- 加載:加載
.class
文件的字節碼文件 - 連接:為類分配靜態域,并為變量分配初始值
- 初始化:會真正的執行類中定義的java程序代碼,既初始化靜態域中的方法和變量
同一個類加載器下,一個類型只會初始化一次。
1.2 創建Class對象
- 根據對象的引用
.getClass()
方法獲取
Tom tom = new Tom();
Class class = tom.getClass();
- 根據類名
.class
獲取
Class class = Tom.class;
- 根據Class中的靜態方法
Class.forName
,其中的參數必須為帶包名的類路徑
Class c = Class.forName("Tom");
通常在反射中創建Class對象時使用的第二種方法,因為第一種已經有這個對象了,干嘛還需要反射,第三種的話會有局限性,需要導入所要創建對象的包路徑。
2. 使用反射
我們在上面獲取到Class
對象,而我們拿到了Class
對象以后就能對其進行操作
2.1 構造方法
Class
類和javalang.reflect
類庫一起對反射的概念進行了支持,該類庫包含了Field
,Method
以及Constructor
類(每個類都實現了Member接口).這些類型的對象是由JVM在運行時創建的,用以表示未知類里所對應的成員信息.這樣就可以用Constructor
創建新的對象。下面演示一下通過反射獲取構造方法。
2.1.1 獲取公有構造方法
public class A {
private String a;
private String b;
public String c;
public A(String a, String b) {
this.a = a;
this.b = b;
}
private A(String a){
this.a=a;
}
public A(){}
——————————get.set方法省略
}
Class a=Class.forName("Practice.Day05.A");
Constructor[] constructors = a.getConstructors();
for (Constructor constructor:constructors){
System.out.println(constructor);
}
此時我們發現打印出來的信息如下:
public Practice.Day05.A()
public Practice.Day05.A(java.lang.String,java.lang.String)
2.1.2 獲取所有構造方法
我們發現沒有私有的構造函數,因為getConstructors()
方法獲得是public
的構造函數,而getDeclaredFields()
方法獲得是包括public
, protected
, default
,private
的構造函數,此時如果我們將代碼改成如下:
Class a=Class.forName("Practice.Day05.A");
Constructor[] constructors = a.getDeclaredConstructors();
for (Constructor constructor:constructors){
System.out.println(constructor);
}
我們發現打印的參數就會多了一個private的構造函數
public Practice.Day05.A()
private Practice.Day05.A(java.lang.String)
public Practice.Day05.A(java.lang.String,java.lang.String)
2.1.3 獲得具體類型的構造方法
我們在上面都是獲得了一個構造方法的數組,如果想要獲得具體的構造方法的話,那么可以通過傳入構造方法的入參的類型,可以獲得這個構造方法,具體例子如下:
Class a=Class.forName("Practice.Day05.A");
Constructor constructor = a.getConstructor(null);
Constructor stringConstructor = a.getConstructor(String.class,String.class);
Constructor stringPrivateConstructor=a.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(stringConstructor);
System.out.println(stringPrivateConstructor);
打印的信息如下:
public Practice.Day05.A()
public Practice.Day05.A(java.lang.String,java.lang.String)
private Practice.Day05.A(java.lang.String)
2.1.4 通過構造方法實例化類
我們獲得了Constructor
對象以后,可以通過其中的newInstance
方法進行實例化對象。例子如下:
Class a=Class.forName("Practice.Day05.A");
Constructor constructor = a.getConstructor(null);
Constructor stringConstructor = a.getConstructor(String.class,String.class);
A nullA= (A) constructor.newInstance();
A stringA= (A) stringConstructor.newInstance("BuXueWuShu","BuXueWuShu");
nullA.setA("BuXueWuShu");
System.out.println("nullA:"+nullA.getA());
System.out.println("stringA:"+stringA.getA());
打印信息如下:
nullA:BuXueWuShu
stringA:BuXueWuShu
2.2 成員變量
還是上面的實體類的例子,其中有私有的兩個變量是a和b,公有的變量是c。
2.2.1 獲取成員變量
如果類中有屬性的話,兩個方法都是返回一個Field
數組.其中getDeclaredFields()
方法返回的是所有參數包括public
, protected
, default
,private
,而getFields()
只返回public
的參數。例如如下
Class a=Class.forName("Practice.Day05.A");
Field[] allDeclaredFields = a.getDeclaredFields();--獲得所有成員變量
Field[] fields = a.getFields();--只獲得public的成員變量
for (Field field:allDeclaredFields){
System.out.println(field);
}
System.out.println("-----------------------");
for (Field field:fields){
System.out.println(field);
}
打印的參數如下:
private java.lang.String Practice.Day05.A.a
private java.lang.String Practice.Day05.A.b
public java.lang.String Practice.Day05.A.c
-----------------------
public java.lang.String Practice.Day05.A.c
2.2.2 獲得指定成員變量
獲得指定的成員變量其實和上面的獲取指定的構造方法是一樣的。舉例如下:
Class a=Class.forName("Practice.Day05.A");
Field c1 = a.getField("c");
Field a1 = a.getDeclaredField("a");
System.out.println(c1);
System.out.println("---------------------");
System.out.println(a1);
打印參數如下:
public java.lang.String Practice.Day05.A.c
---------------------
private java.lang.String Practice.Day05.A.a
2.2.3 為成員變量賦值
獲得了Field的對象后,可以調用get()
和set()
方法對某個對象中的屬性進行取值和賦值的操作。例子如下
Class a=Class.forName("Practice.Day05.A");
Constructor nullClass=a.getDeclaredConstructor(null);
A nullA= (A) nullClass.newInstance();--獲得A的實例化對象
Field a1 = a.getDeclaredField("a");
a1.setAccessible(true);--變量a是private的,所以需要解除私有限定
a1.set(nullA,"BuXueWuShu");--為nullA對象中的變量a進行賦值操作
System.out.println("nullA="+a1.get(nullA));--取出nullA對象中的變量a
打印信息如下:
nullA=BuXueWuShu
注意在對私有的成員變量進行賦值操作時,要解除私有限定,調用
setAccessible()
方法,賦值為true
2.3 成員方法
2.3.1 獲取成員方法
通過獲得的Class
對象,調用它的getDeclaredMethods()
和getMethods()
方法可以獲得類中的方法的信息。例如有以下的一個類。
public class TestMethod {
private String playName;
public void show(String playName){
System.out.println("I Love "+playName);
}
private String returnPlayName(){
return playName;
}
}
做如下的調用:
Class a=Class.forName("Practice.Day05.TestMethod");
Method[] allDeclaredMethods = a.getDeclaredMethods();
Method[] methods = a.getMethods();
for (Method method:allDeclaredMethods){
System.out.println(method);
}
System.out.println("-----------------");
for (Method method:methods){
System.out.println(method);
}
可以發現打印如下的信息:
public void Practice.Day05.TestMethod.show(java.lang.String)
private java.lang.String Practice.Day05.TestMethod.returnPlayName()
-----------------
public void Practice.Day05.TestMethod.show(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()
getDeclaredMethods()
方法會獲得自身和實現的接口中所有的方法,但是不會獲得繼承來的方法,getMethods()
方法會獲得所有的無論是實現的接口還是繼承來的public
的方法
2.3.2 獲得指定的方法
通過方法名和方法中的參數類型就可以獲得指定的方法
Class a=Class.forName("Practice.Day05.TestMethod");
Method show = a.getDeclaredMethod("show", String.class);
System.out.println(show);
打印信息如下:
public void Practice.Day05.TestMethod.show(java.lang.String)
2.3.3 使用方法
可以通過調用Method
對象的invoke()
方法進行調用方法。例子如下:
Class a=Class.forName("Practice.Day05.TestMethod");
Constructor nullClass=a.getDeclaredConstructor(null);
TestMethod nullTestMethod= (TestMethod) nullClass.newInstance();
Method show = a.getDeclaredMethod("show",String.class);
show.invoke(nullTestMethod,"BasketBall");
打印參數如下:
I Love BasketBall
如果調用的是private的方法,那么在使用
invoke()
方法之前要先解除私有限定,即調用setAccessible()
方法,賦值為true