java 虛擬機器器有一個執行時資料區,這個資料區又被分為方法區,堆區和棧區,我們這裡需要
瞭解的主要是方法區。
方法區主要用來存放已經被虛擬機器器載入的類資訊、靜態變數、方法等資訊。
當虛擬機器器需要裝載某個類的時候,需要類裝載器定位相應的 class 檔案,然後將其讀入到
java 虛擬機器器中,緊接著虛擬機器器提取 class 中的型別資訊,將這些資訊儲存到方法區中。
在程式執行期間,java 執行時系統始終為所有物件維護一個被稱為執行時的型別標識。這個
資訊跟蹤著每個物件所屬的類。虛擬機器器利用執行時型別資訊選擇相應的方法執行。包含這些
資訊的類被稱為 Class。
Class 沒有公共構造方法。Class 物件是在載入類時由 Java 虛擬機器器以及通過呼叫類載入器中
的 defineClass 方法自動構造的。也就是這不需要我們自己去處理建立,JVM 已經幫我們創
建好了。
反射的本質就是得到 class 物件後反向獲取對應物件的各種資訊。
書上的的說法:JAVA 反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有
屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的信
息以及動態呼叫物件的方法的功能稱為 java 語言的反射機制。
自己的理解:反射就是利用 Class 物件把 java 類中的各種成分(成員變數、構造方法、方法
等資訊)對映成一個個的 java 物件。
直接建立物件不就行了,為什麼要用反射?這裡首先要明白動態和靜態的概念。
靜態編譯:在編譯時確定型別,繫結物件,即通過。
動態編譯:執行時確定型別,繫結物件。動態編譯最大限度發揮了 java 的靈活性,體現了多
態的應用,有以降低類之間的藕合性。
一句話,反射機制的優點就是可以實現動態建立物件和編譯,體現出很大的靈活性,特別是
在 J2EE 的開發中它的靈活性就表現的十分明顯。
比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,釋出了,
當發現需要更新某些功能時,我們不可能要使用者把以前的解除安裝,再重新安裝新的版本,假如
這樣的話,這個軟體肯定是沒有多少人用的。
採用靜態的話,需要把整個程式重新編譯一次才可以實現功能的更新,而採用反射機制的
話,它就可以不用解除安裝,只需要在執行時才動態的建立和編譯,就可以實現該功能。它的缺
點是對效能有影響。
反射機制可以幫我們做那些重複的有規律的事情,現在很多的自動生成程式碼的軟體就是運用
反射機制來完成的。
反射最重要的用途就是開發各種通用框架,比如在 spring 中,我們將所有的類 Bean 交給
spring 容器管理,無論是 XML 設定 Bean 還是註解設定,當我們從容器中獲取 Bean 來依賴
注入時,容器會讀取設定,而設定中給的就是類的資訊,spring 根據這些資訊,需要建立那
些 Bean,spring 就動態的建立這些類。
還有在我們建立資料庫連結時,這句程式碼 Class tc = Class.forName
("com.java.dbtest.TestConnection") 就是告訴 JVM 去載入這個類,而載入的過程是在程式
執行過程中動態載入的。通過類的全類名讓 jvm 在伺服器中找到並載入這個類,而如果是使
用別的資料庫,那就要換一個類了,如果是傳統寫死的方法建立,就要修改原來類的程式碼,
而對於反射,則只是傳入的引數就變成另一個了而已,可以通過修改組態檔,而不是直接
修改程式碼。
"""
package fanshe;
/**
* 獲取Class物件的三種方式
* 1 Object ——> getClass();
* 2 任何資料型別(包括基本資料型別)都有一個「靜態」的class屬性
* 3 通過Class類的靜態方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
//第一種方式獲取Class物件
Student stu1 = new Student();//這一new 產生一個Student物件,一個Class物件。
Class stuClass = stu1.getClass();//獲取Class物件
System.out.println(stuClass.getName());
//第二種方式獲取Class物件
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判斷第一種方式獲取的Class物件和第二種方式獲取的是否是同一個
//第三種方式獲取Class物件
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字串必須是真實路徑,就是帶包名的類路徑,包名.類名
System.out.println(stuClass3 == stuClass2);//判斷三種方式是否獲取的是同一個Class物件
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
"""
關於三種方式的選擇:
常用第三種,可以字串傳入也可以寫在組態檔中,可通過 getName()獲取物件的
全限定名,另外這個類必須在 classpath 路徑下,不然會丟擲 ClassNotFoundException 的
異常。
第一種物件都有了,不需要用反射。
第二種需要匯入類的包,依賴太強,不導包就拋編譯錯誤。一般用於匯入的包沒有源
碼,只有 class 檔案,需要修改方法或屬性的情況。
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)
姓名:男
"""
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=
"""
"""
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
"""
"""
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 方法執行了。。。