java基礎之反射
1. 類的加載、連接和初始化
1.1 類的加載
當程序主動使用某個類時,如果該類還沒有被加載到內存中,則系統會通過加載、連接、初始化這三個步驟對該類進行初始化。有時會把這一整個流程統稱為類加載或類初始化。
類加載指的是將類的class文件讀入內存中,並為之創建一個 java.lang.Class 對象,也就是說程序使用任何類的時候,都會為其創建一個class對象。
1.2 類的連接
類被加載之后,系統會為之生成一個Class對象,接着會進入連接階段,連接階段負責把類的二進制數據合並到JRE中。類的連接又分為下面三個階段:
- 驗證:確保被加載類的正確性
- 准備:負責為類的靜態成員分配內存,並設置默認初始化值
- 解析:將類中的符號引用替換為直接引用
1.3 類的初始化
在java中對類變量指定初始值得方法有兩種:1. 聲明類變量時指定初始值;2. 使用靜態初始化塊為類變量指定初始值。
- 類加載的時機
- 創建類的實例的時候
- 訪問類的靜態變量的時候
- 調用類的靜態方法的時候
- 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
- 初始化某個類的子類的時候
- 直接使用java.exe命令來運行某個主類
1.4 類加載器
類加載器負責將.class文件加載到內存中,並為之生成對應的Class對象。類加載器負責加載所有的類,系統為所有加載到內存中的類生成一個java.lang.Class 的實例。
類加載器的組成:
- Bootstrap ClassLoader 根類加載器 : 也被稱為引導類加載器,負責Java核心類的加載,比如System類,在JDK中JRE的lib目錄下rt.jar文件中的類
- Extension ClassLoader 擴展類加載器 : 負責JRE的擴展目錄中jar包的加載,在JDK中JRE的lib目錄下ext目錄
- System ClassLoader 系統類加載器 : 負責在JVM啟動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑,主要是我們開發者自己寫的類
2. 反射
Java反射就是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;並且能改變它的屬性。
反射機制允許程序在運行時取得任何一個已知名稱的class的內部信息,包括包括其modifiers(修飾符),fields(屬性),methods(方法)等,並可於運行時改變fields內容或調用methods。那么我們便可以更靈活的編寫代碼,代碼可以在運行時裝配,無需在組件之間進行源代碼鏈接,降低代碼的耦合度;還有動態代理的實現等等。
2.1 反射基本信息
java程序中許多對象在運行時會出現兩種類型:運行時類型和編譯時類型,例如Person p = new Student();這句代碼中p在編譯時類型為Person,運行時類型為Student。程序需要在運行時發現對象和類的真實信心。而通過使用反射程序就能判斷出該對象和類屬於哪些類。
2.1.1 Class對象
Java文件被編譯后,生成了.class文件,JVM此時就要去解讀.class文件。當程序主動去使用某個類時,JVM會通過前面提到的三個步驟:加載、連接和初始化三個步驟對類進行初始化。被編譯后的Java文件.class也被JVM解析為一個對象,這個對象就是java.lang.Class。這樣當程序在運行時,每個java文件就最終變成了Class類對象的一個實例。我們通過Java的反射機制應用到這個實例,就可以去獲得甚至去添加改變這個類的屬性和動作,使得這個類成為一個動態的類。
Class類的概念盡管很抽象,但是無疑,它是反射機制的起源,是Java語言中一個精巧美妙地設計。
下面是翻譯后的中文文檔的描述:
Class類的實例表示正在運行的Java應用程序的類和接口。枚舉是一種類,注釋(注解)是一種接口。每個數組屬於被映射為Class對象的一個類,所有具有相同元素類型和維數的數組都共享該Class對象。基本的Java類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也表示為 Class 對象。Class沒有公用構造方法。Class對象是在加載類時由JVM以及通過調用類加載器中的defineClass方法自動構造的。
2.1.2 Java反射機制的類庫支持
在深入到反射機制之前,先探析一下反射機制的定義和應用。反射機制定義:Java反射機制是在運行狀態時,對於任意一個類,都能夠直到這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性。
在Java中,Class類和java.lang.reflect類庫一起構成了對Java反射機制的支持。其中最常使用到的類是Constructor,Field,Method,而這三個類都繼承了一個接口java.lang.reflect.Member。
2.2 反射的基本實現
實驗類
//因篇幅原因,實驗代碼未展示set,get等等常用方法
public class Cat {
private String name = "";
private String master = "";
private int age = 0;
public Cat() { }
private Cat(String name, String master) { }
public Cat(String name, String master, int age) { }
public void eat() {
System.out.println("小魚干真好吃~");
}
private void play() {
System.out.println("快來陪我玩~");
}
@Override
public String toString() {
return "Cat [name=" + name + ", master=" + master + ", age=" + age + "]";
}
}
2.2.1 獲取Class對象
在java中獲取Class對象有三種方法:
- 通過類名獲取:Class c1 = Student.class; 調用某各類的class屬性來獲取Class對象。
- 通過對象獲取:Class c2 = stu.getClass(); 通過getClass方法,該方法是Object類下的一個方法。
- 通過全類名獲取:Class c3 = Class.forName("全限定類名"); 會有一個ClassNotFoundException異常
public static void main(String[] args) throws Exception {
Cat c = new Cat();
Class cat1 = Class.forName("algorithms.sort.Cat");
Class cat2 = Cat.class;
Class cat3 = c.getClass();
System.out.println(cat1 == cat2);
System.out.println(cat2 == cat3);
}
//輸出結果
true
true
2.2.2 獲取構造器並創建對象
- getConstructors():返回了表示此類公共構造方法的Constructor對象數組。
- getDeclaredConstructors():這個方法返回Constructor對象的所有構造方法。
獲取構造器
public static void main(String[] args) throws Exception {
Class cat = Cat.class;
//獲取所有公共構造方法
Constructor<?> cons[] = cat.getConstructors();
for (Constructor<?> con : cons) {
System.out.println("getConstructors-----------" + con);
}
System.out.println("**************************************");
//獲取所有構造方法
Constructor<?> cons2[] = cat.getDeclaredConstructors();
for (Constructor<?> con2 : cons2) {
System.out.println("getDeclaredConstructors---" + con2);
}
}
//輸出結果
getConstructors-----------public algorithms.sort.Cat(java.lang.String,java.lang.String,int)
getConstructors-----------public algorithms.sort.Cat()
**************************************
getDeclaredConstructors---public algorithms.sort.Cat(java.lang.String,java.lang.String,int)
getDeclaredConstructors---private algorithms.sort.Cat(java.lang.String,java.lang.String)
getDeclaredConstructors---public algorithms.sort.Cat()
創建對象
public static void main(String[] args) throws Exception {
Class cat = Cat.class;
//使用公共構造器實例化對象
Constructor<?> cons1 = cat.getConstructor();
//使用私有構造器實例化對象
Constructor<?> cons2 = cat.getDeclaredConstructor(String.class,String.class);
Cat cat1 = (Cat)cons1.newInstance();
//私有的構造方法反射后要打開權限才能進行相應操作
cons2.setAccessible(true);
Cat cat2 = (Cat)cons2.newInstance("tom","denny");
System.out.println(cat1);
System.out.println(cat2);
}
//輸出結果
Cat [name=, master=, age=0]
Cat [name=tom, master=denny, age=0]
在創建對象的過程中,值得注意的是如果反射的構造方法是私有的,那么要打開訪問權限才能進行對象的實例化;也就是使用cons2.setAccessible(true);語句的原因。
2.2.3 獲取成員變量和成員方法
獲取成員變量
public static void main(String[] args) throws Exception {
Class cat = Cat.class;
//獲取構造器
Constructor<?> cons = cat.getConstructor(String.class,String.class,int.class);
//實例化對象
Cat cat1 = (Cat)cons.newInstance("tom","denny",5);
System.out.println(cat1);
System.out.println("****************");
Field fields = cat.getDeclaredField("name");
//打開訪問權限限制
fields.setAccessible(true);
fields.set(cat1, "jack");
System.out.println(cat1);
}
//輸出結果
Cat [name=tom, master=denny, age=5]
****************
Cat [name=jack, master=denny, age=5]
獲取成員方法
public static void main(String[] args) throws Exception {
Class cat = Cat.class;
//獲取構造器
Constructor<?> cons = cat.getConstructor(String.class, String.class, int.class);
//實例化對象
Cat cat1 = (Cat) cons.newInstance("tom", "denny", 5);
System.out.println(cat1);
System.out.println("****************");
//獲取私有和公共成員方法
Method method1 = cat.getDeclaredMethod("setName", String.class);
Method method2 = cat.getDeclaredMethod("eat");
method1.setAccessible(true);
method1.invoke(cat1, "petter");
System.out.println(cat1);
method2.invoke(cat1);
}
//輸出結果
Cat [name=tom, master=denny, age=5]
****************
Cat [name=petter, master=denny, age=5]
小魚干真好吃~
2.2.4 反射越過泛型檢查
public static void main(String[] args) throws Exception {
//泛型只在編譯期進行檢查,在運行期會被擦除
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
//拿到字節碼文件,字節碼文件屬於運行期
Class cla = Class.forName("java.util.ArrayList");
Method meth = cla.getMethod("add", Object.class);
meth.invoke(list, "abc");
System.out.println(list);
}
//輸出結果
[111, 222, abc]