其他更多java基礎文章:
java基礎學習(目錄)
深入理解Class對象
RRIT及Class對象的概念
RRIT(Run-Time Type Identification)運行時類型識別纵揍。在《Thinking in Java》一書第十四章中有提到拘泞,其作用是在運行時識別一個對象的類型和類的信息。主要有兩種方式:一種是“傳統(tǒng)的”RTTI,它假定我們在編譯時已經知道了所有的類型淳梦;另一種是“反射”機制衰絮,它允許我們在運行時發(fā)現(xiàn)和使用類的信息。
類是程序的一部分谋减,每個類都有一個class對象灿意。換言之,每當編寫并且編譯了一個新類崇呵,就會產生一個Class對象(更恰當?shù)卣f缤剧,是被保存在一個同名的.class文件中)。所有的類都是在對其第一次使用時域慷,動態(tài)加載到JVM中的荒辕。例如我們寫了一個Test類汗销,編譯后生成了Test.class,此時我們的Test類的Class對象就保存在class文件中抵窒。當我們new一個新對象或者引用靜態(tài)成員變量時弛针,Java虛擬機(JVM)中的類加載器子系統(tǒng)會將對應Class對象加載到JVM中,然后JVM再根據(jù)這個類型信息相關的Class對象創(chuàng)建我們需要實例對象或者提供靜態(tài)變量的引用值李皇。需要特別注意的是削茁,手動編寫的每個class類,無論創(chuàng)建多少個實例對象掉房,在JVM中都只有一個Class對象茧跋,即在內存中每個類有且只有一個相對應的Class對象。
Test t1 = new Test();
Test t2 = new Test();
Test t3 = new Test();
如上所示卓囚,實際上JVM內存中只存有一個Test的Class對象瘾杭。
Class類,Class類也是一個實實在在的類哪亿,存在于JDK的java.lang包中粥烁。Class類的實例表示java應用運行時的類(class ans enum)或接口(interface and annotation)(每個java類運行時都在JVM里表現(xiàn)為一個class對象,可通過類名.class蝇棉、類型.getClass()讨阻、Class.forName("類名")等方法獲取class對象)
。數(shù)組同樣也被映射為為class 對象的一個類银萍,所有具有相同元素類型和維數(shù)的數(shù)組都共享該 Class 對象变勇。基本類型boolean贴唇,byte搀绣,char,short戳气,int链患,long,float瓶您,double和關鍵字void同樣表現(xiàn)為 class 對象麻捻。
ublic final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects. //私有構造器,只有JVM才能調用創(chuàng)建Class對象
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
到這我們也就可以得出以下幾點信息:
Class類也是類的一種呀袱,與class關鍵字是不一樣的贸毕。
手動編寫的類被編譯后會產生一個Class對象,其表示的是創(chuàng)建的類的類型信息夜赵,而且這個Class對象保存在同名.class的文件中(字節(jié)碼文件)
每個通過關鍵字class標識的類明棍,在內存中有且只有一個與之對應的Class對象來描述其類型信息,無論創(chuàng)建多少個實例對象寇僧,其依據(jù)的都是用一個Class對象摊腋。
Class類只存私有構造函數(shù)沸版,因此對應Class對象只能有JVM創(chuàng)建和加載
Class類的對象作用是運行時提供或獲得某個對象的類型信息,這點對于反射技術很重要(關于反射稍后分析)兴蒸。
Class對象的加載及獲取
Class對象的加載
前面我們已提到過视粮,Class對象是由JVM加載的,那么其加載時機是橙凳?實際上所有的類都是在對其第一次使用時動態(tài)加載到JVM中的蕾殴,當程序創(chuàng)建第一個對類的靜態(tài)成員引用時,就會加載這個被使用的類(實際上加載的就是這個類的字節(jié)碼文件)痕惋,注意区宇,使用new操作符創(chuàng)建類的新實例對象也會被當作對類的靜態(tài)成員的引用(構造函數(shù)也是類的靜態(tài)方法),由此看來Java程序在它們開始運行之前并非被完全加載到內存的值戳,其各個部分是按需加載议谷,所以在使用該類時,類加載器首先會檢查這個類的Class對象是否已被加載(類的實例對象創(chuàng)建時依據(jù)Class對象中類型信息完成的)堕虹,如果還沒有加載卧晓,默認的類加載器就會先根據(jù)類名查找.class文件(編譯后Class對象被保存在同名的.class文件中),在這個類的字節(jié)碼文件被加載時赴捞,它們必須接受相關驗證逼裆,以確保其沒有被破壞并且不包含不良Java代碼(這是java的安全機制檢測),完全沒有問題后就會被動態(tài)加載到內存中赦政,此時相當于Class對象也就被載入內存了(畢竟.class字節(jié)碼文件保存的就是Class對象)胜宇,同時也就可以被用來創(chuàng)建這個類的所有實例對象。
類加載的過程 :
1. 加載
在加載階段恢着,虛擬機需要完成3件事:
(1)通過一個類的全限定名(org/fenixsoft/clazz/TestClass)獲取定義此類的二進制字節(jié)流(.class文件);
(2)將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構;
(3)在內存中生成一個代表這個類的 java.lang.Class 對象桐愉,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口;
2. 驗證
驗證階段是非常重要的,這個階段是否嚴謹掰派,直接決定了Java虛擬機是否能承受惡意代碼的攻擊从诲,從執(zhí)行性能的角度上講,驗證階段的工作量在虛擬機的類加載子系統(tǒng)中又占了相當大的一部分靡羡。驗證階段大致上完成下面4個階段的驗證動作:
(1)文件格式驗證
驗證字節(jié)流是否符合Class文件格式的規(guī)范系洛,并且能被當前版本的虛擬機處理;
這階段的驗證是基于二進制字節(jié)流進行的,只有通過了這個階段的驗證略步,字節(jié)流才會進入內存的方法區(qū)進行儲存描扯,所以后面的3個驗證階段全部是基于方法區(qū)的存儲結構進行的,不會再直接操作字節(jié)流趟薄。
(2)元數(shù)據(jù)驗證
對字節(jié)碼描述的信息進行語義分析绽诚,以保證其描述的信息符合Java語言規(guī)范的要求,保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息;
(3)字節(jié)碼驗證
通過數(shù)據(jù)流和控制流分析,確定程序是語義是合法的憔购、符合邏輯的,保證被校驗的方法在運行時不會做出危害虛擬機安全的事件;
(4)符號引用驗證
可以看作是對類自身以外(常量池中各種符號引用)的信息進行匹配性校驗岔帽,確保解析動作能正常執(zhí)行;
3. 準備
準備階段是正式為類變量分配內存并設置類變量初始值階段玫鸟,這些變量所使用的內存都將在方法區(qū)中進行分配。這里進行內存分配僅僅是類變量(被static修飾的變量)犀勒,而不包括實例變量屎飘,實例變量將在對象實例化時隨著對象一起分配在Java堆中;
4. 解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或接口贾费、字段钦购、類方法、方法類型褂萧、方法句柄和調用點限定符7類符號引用進行;
5. 初始化
初始化階段才真正開始執(zhí)行類中定義的Java程序代碼(或者說是字節(jié)碼)押桃。初始化是如何被觸發(fā)的:
(1)遇到new、getstatic导犹、putstatic或involestatic這4條指令時;
(2)使用 java.lang.reflect 包的方法對類進行反射調用的時候;
(3)初始化一個類時唱凯,如果父類還沒被初始化,則先觸發(fā)父類的初始化;
(4)虛擬機啟動時谎痢,用戶需要指定一個要執(zhí)行的主類 (包含main()方法的那個類)磕昼,虛擬機會先初始化這個主類;
(5)如果一個 java.lang.invoke.MethodHandle 實例最后解析的結果是 REF_getStatic获黔、REF_putStatic纤房、REF_invokeStatic的方法句柄,若句柄所對應的類沒有進行過初始化罩抗,則將它初始化;
上文源自《深入理解java虛擬機》一書滨嘱,大家可以去讀一下峰鄙,這本書基本上是java程序猿學習必讀之一了。在此就不深入展開了九孩,因為這又是另一個JVM領域了先馆。 以后如果寫了該方面的文章,會貼到這里躺彬。
Class對象的獲取
Class對象的獲取主要有3種:
- 通過實例getClass()方法獲取
- Class.forName方法獲取
- 類字面常量獲取
通過實例getClass()方法獲取
Test t1 = new Test();
Class clazz=test.getClass();
System.out.println("clazz:"+clazz.getName());
getClass()是從頂級類Object繼承而來的煤墙,它將返回表示該對象的實際類型的Class對象引用。
Class.forName方法獲取
try{
//通過Class.forName獲取Test類的Class對象
Class clazz=Class.forName("com.hiway.Test");
System.out.println("clazz:"+clazz.getName());
}catch (ClassNotFoundException e){
e.printStackTrace();
}
forName方法是Class類的一個static成員方法宪拥,記住所有的Class對象都源于這個Class類仿野,因此Class類中定義的方法將適應所有Class對象。這里通過forName方法她君,我們可以獲取到Test類對應的Class對象引用脚作。
注意調用forName方法時需要捕獲一個名稱為ClassNotFoundException的異常,因為forName方法在編譯器是無法檢測到其傳遞的字符串對應的類是否存在的(是否有.class文件),只能在程序運行時進行檢查球涛,如果不存在就會拋出ClassNotFoundException異常劣针。
使用forName方式會觸發(fā)類的初始化,與之相比的是使用類字面常量獲取
類字面常量獲取
//字面常量的方式獲取Class對象
Class clazz = Test.class;
這樣做不僅更簡單亿扁,而且更安全捺典,因為它在編譯時就會受到檢查(因此不需要置于try語句塊中)。并且它根除了對forName()方法的調用从祝,所以也更高效襟己。
注意,有一點很有趣牍陌,當使用“.class”來創(chuàng)建對Class對象的引用時擎浴,不會自動地初始化該Class對象。注意毒涧,有一點很有趣贮预,當使用“.class”來創(chuàng)建對Class對象的引用時,不會自動地初始化該Class對象链嘀,為了使用類而做的準備工作實際包含三個步驟:
- 加載萌狂,這是由類加載器執(zhí)行的,該步驟將查找字節(jié)碼(通常在classpath所指定的路徑中查找怀泊,但這并非是必需的)茫藏,并從這些字節(jié)碼中創(chuàng)建一個Class對象。
- 鏈接霹琼。在鏈接階段將驗證類中的字節(jié)碼务傲,為靜態(tài)域分布存儲空間,并且如果必需的話枣申,將解析這個類創(chuàng)建的對其他類的所有引用售葡。
- 初始化。如果該類具有超類忠藤,則對其初始化挟伙,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。
class Initable{
static final int staticFinal = 47;
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.ptintln("Initializing Initable");
}
}
class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
Class initable = Initable.class;
System.out.println("After creating Initable ref");
System.out.println(Initable.staticFinal);
System.out.println(Initable.staticFinal2);
System.out.println(Initable2.staticNonFinal);
Clas initable3 = Class.forName("Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
/* output
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable ref
74
如果一個static final值是編譯器常量
模孩,就像Initable.staticFinal那樣尖阔,那么這個值不需要對Initable類進行初始化就可以被讀取。但是榨咐,如果只是將一個域設置為static和final的介却,還不足以確保這種行為,例如块茁,對Initable.staticFinal2的訪問將強制進行類的初始化齿坷,因為它不是一個編譯期常量桂肌。
如果一個static域不是final的,那么在對它訪問時永淌,總是要求在它被讀取之前崎场,要先進行鏈接(為這個域分配存儲空間)和初始化(初始化該存儲空間)
,就像在對Initable2.staticNonFinal的訪問中所看到的那樣遂蛀。從輸出結果來看照雁,可以發(fā)現(xiàn),通過字面常量獲取方式獲取Initable類的Class對象并沒有觸發(fā)Initable類的初始化答恶,這點也驗證了前面的分析,同時發(fā)現(xiàn)調用Initable.staticFinal變量時也沒有觸發(fā)初始化萍诱,這是因為staticFinal屬于編譯期靜態(tài)常量悬嗓,在編譯階段通過常量傳播優(yōu)化的方式將Initable類的常量staticFinal存儲到了一個稱為NotInitialization
類的常量池中,在以后對Initable類常量staticFinal的引用實際都轉化為對NotInitialization類對自身常量池的引用裕坊,所以在編譯期后包竹,對編譯期常量的引用都將在NotInitialization類的常量池獲取,這也就是引用編譯期靜態(tài)常量不會觸發(fā)Initable類初始化的重要原因籍凝。但在之后調用了Initable.staticFinal2變量后就觸發(fā)了Initable類的初始化周瞎,注意staticFinal2雖然被static和final修飾,但其值在編譯期并不能確定饵蒂,因此staticFinal2并不是編譯期常量声诸,使用該變量必須先初始化Initable類。Initable2和Initable3類中都是靜態(tài)成員變量并非編譯期常量退盯,引用都會觸發(fā)初始化彼乌。至于forName方法獲取Class對象,肯定會觸發(fā)初始化渊迁,這點在前面已分析過慰照。
instanceof與Class的等價性
關于instanceof 關鍵字,它返回一個boolean類型的值琉朽,意在告訴我們對象是不是某個特定的類型實例毒租。如下,在強制轉換前利用instanceof檢測obj是不是Animal類型的實例對象箱叁,如果返回true再進行類型轉換墅垮,這樣可以避免拋出類型轉換的異常(ClassCastException)
public void cast2(Object obj){
if(obj instanceof Animal){
Animal animal= (Animal) obj;
}
}
而isInstance方法則是Class類中的一個Native方法,也是用于判斷對象類型的蝌蹂,看個簡單例子:
public void cast2(Object obj){
//instanceof關鍵字
if(obj instanceof Animal){
Animal animal= (Animal) obj;
}
//isInstance方法
if(Animal.class.isInstance(obj)){
Animal animal= (Animal) obj;
}
}
事實上instanceOf 與isInstance方法產生的結果是相同的噩斟。
class A {}
class B extends A {}
public class C {
static void test(Object x) {
print("Testing x of type " + x.getClass());
print("x instanceof A " + (x instanceof A));
print("x instanceof B "+ (x instanceof B));
print("A.isInstance(x) "+ A.class.isInstance(x));
print("B.isInstance(x) " +
B.class.isInstance(x));
print("x.getClass() == A.class " +
(x.getClass() == A.class));
print("x.getClass() == B.class " +
(x.getClass() == B.class));
print("x.getClass().equals(A.class)) "+
(x.getClass().equals(A.class)));
print("x.getClass().equals(B.class)) " +
(x.getClass().equals(B.class)));
}
public static void main(String[] args) {
test(new A());
test(new B());
}
}
/* output
Testing x of type class com.zejian.A
x instanceof A true
x instanceof B false //父類不一定是子類的某個類型
A.isInstance(x) true
B.isInstance(x) false
x.getClass() == A.class true
x.getClass() == B.class false
x.getClass().equals(A.class)) true
x.getClass().equals(B.class)) false
---------------------------------------------
Testing x of type class com.zejian.B
x instanceof A true
x instanceof B true
A.isInstance(x) true
B.isInstance(x) true
x.getClass() == A.class false
x.getClass() == B.class true
x.getClass().equals(A.class)) false
x.getClass().equals(B.class)) true
反射
反射機制是在運行狀態(tài)中,對于任意一個類孤个,都能夠知道這個類的所有屬性和方法剃允;對于任意一個對象,都能夠調用它的任意一個方法和屬性,這種動態(tài)獲取的信息以及動態(tài)調用對象的方法的功能稱為java語言的反射機制斥废。一直以來反射技術都是Java中的閃亮點椒楣,這也是目前大部分框架(如Spring/Mybatis等)得以實現(xiàn)的支柱。在Java中牡肉,Class類與java.lang.reflect類庫一起對反射技術進行了全力的支持捧灰。在反射包中,我們常用的類主要有Constructor類表示的是Class 對象所表示的類的構造方法统锤,利用它可以在運行時動態(tài)創(chuàng)建對象毛俏、Field表示Class對象所表示的類的成員變量,通過它可以在運行時動態(tài)修改成員變量的屬性值(包含private)饲窿、Method表示Class對象所表示的類的成員方法煌寇,通過它可以動態(tài)調用對象的方法(包含private),下面將對這幾個重要類進行分別說明逾雄。
Constructor類及其用法
Constructor類存在于反射包(java.lang.reflect)中阀溶,反映的是Class 對象所表示的類的構造方法。獲取Constructor對象是通過Class類中的方法獲取的鸦泳,Class類與Constructor相關的主要方法如下:
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
static Class<?> | forName(String className) | 返回與帶有給定字符串名的類或接口相關聯(lián)的 Class 對象银锻。 |
Constructor<T> | getConstructor(Class<?>... parameterTypes) | 返回指定參數(shù)類型、具有public訪問權限的構造函數(shù)對象 |
Constructor<?>[] | getConstructors() | 返回所有具有public訪問權限的構造函數(shù)的Constructor對象數(shù)組 |
Constructor<T> | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定參數(shù)類型做鹰、所有聲明的(包括private)構造函數(shù)對象 |
Constructor<?>[] | getDeclaredConstructor() | 返回所有聲明的(包括private)構造函數(shù)對象 |
T | newInstance() | 調用無參構造器創(chuàng)建此 Class 對象所表示的類的一個新實例击纬。 |
下面看一個簡單例子來了解Constructor對象的使用:
public class ConstructionTest implements Serializable {
public static void main(String[] args) throws Exception {
Class<?> clazz = null;
//獲取Class對象的引用
clazz = Class.forName("com.example.javabase.User");
//第一種方法,實例化默認構造方法钾麸,User必須無參構造函數(shù),否則將拋異常
User user = (User) clazz.newInstance();
user.setAge(20);
user.setName("Jack");
System.out.println(user);
System.out.println("--------------------------------------------");
//獲取帶String參數(shù)的public構造函數(shù)
Constructor cs1 =clazz.getConstructor(String.class);
//創(chuàng)建User
User user1= (User) cs1.newInstance("hiway");
user1.setAge(22);
System.out.println("user1:"+user1.toString());
System.out.println("--------------------------------------------");
//取得指定帶int和String參數(shù)構造函數(shù),該方法是私有構造private
Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必須設置可訪問
cs2.setAccessible(true);
//創(chuàng)建user對象
User user2= (User) cs2.newInstance(25,"hiway2");
System.out.println("user2:"+user2.toString());
System.out.println("--------------------------------------------");
//獲取所有構造包含private
Constructor<?> cons[] = clazz.getDeclaredConstructors();
// 查看每個構造方法需要的參數(shù)
for (int i = 0; i < cons.length; i++) {
//獲取構造函數(shù)參數(shù)類型
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.println("構造函數(shù)["+i+"]:"+cons[i].toString() );
System.out.print("參數(shù)類型["+i+"]:(");
for (int j = 0; j < clazzs.length; j++) {
if (j == clazzs.length - 1)
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + ",");
}
System.out.println(")");
}
}
}
class User {
private int age;
private String name;
public User() {
super();
}
public User(String name) {
super();
this.name = name;
}
/**
* 私有構造
* @param age
* @param name
*/
private User(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
/* output
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
構造函數(shù)[0]:private com.example.javabase.User(int,java.lang.String)
參數(shù)類型[0]:(int,java.lang.String)
構造函數(shù)[1]:public com.example.javabase.User(java.lang.String)
參數(shù)類型[1]:(java.lang.String)
構造函數(shù)[2]:public com.example.javabase.User()
參數(shù)類型[2]:()
關于Constructor類本身一些常用方法如下(僅部分掉弛,其他可查API)
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
Class<T> | getDeclaringClass() | 返回 Class 對象,該對象表示聲明由此 Constructor 對象表示的構造方法的類,其實就是返回真實類型(不包含參數(shù)) |
Type[] | getGenericParameterTypes() | 按照聲明順序返回一組 Type 對象喂走,返回的就是 Constructor對象構造函數(shù)的形參類型殃饿。 |
String | getName() | 以字符串形式返回此構造方法的名稱。 |
Class<?>[] | getParameterTypes() | 按照聲明順序返回一組 Class 對象芋肠,即返回Constructor 對象所表示構造方法的形參類型 |
T | newInstance(Object... initargs) | 使用此 Constructor對象表示的構造函數(shù)來創(chuàng)建新實例 |
String | toGenericString() | 返回描述此 Constructor 的字符串乎芳,其中包括類型參數(shù)。 |
代碼演示如下:
Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class);
System.out.println("-----getDeclaringClass-----");
Class uclazz=cs3.getDeclaringClass();
//Constructor對象表示的構造方法的類
System.out.println("構造方法的類:"+uclazz.getName());
System.out.println("-----getGenericParameterTypes-----");
//對象表示此 Constructor 對象所表示的方法的形參類型
Type[] tps=cs3.getGenericParameterTypes();
for (Type tp:tps) {
System.out.println("參數(shù)名稱tp:"+tp);
}
System.out.println("-----getParameterTypes-----");
//獲取構造函數(shù)參數(shù)類型
Class<?> clazzs[] = cs3.getParameterTypes();
for (Class claz:clazzs) {
System.out.println("參數(shù)名稱:"+claz.getName());
}
System.out.println("-----getName-----");
//以字符串形式返回此構造方法的名稱
System.out.println("getName:"+cs3.getName());
System.out.println("-----getoGenericString-----");
//返回描述此 Constructor 的字符串帖池,其中包括類型參數(shù)奈惑。
System.out.println("getoGenericString():"+cs3.toGenericString());
/* output
-----getDeclaringClass-----
構造方法的類:com.example.javabase.User
-----getGenericParameterTypes-----
參數(shù)名稱tp:int
參數(shù)名稱tp:class java.lang.String
-----getParameterTypes-----
參數(shù)名稱:int
參數(shù)名稱:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)
Field類及其用法
Field 提供有關類或接口的單個字段的信息,以及對它的動態(tài)訪問權限睡汹。反射的字段可能是一個類(靜態(tài))字段或實例字段肴甸。同樣的道理,我們可以通過Class類的提供的方法來獲取代表字段信息的Field對象囚巴,Class類與Field對象相關方法如下:
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
Field | getDeclaredField(String name) | 獲取指定name名稱的(包含private修飾的)字段原在,不包括繼承的字段 |
Field[] | getDeclaredField() | 獲取Class對象所表示的類或接口的所有(包含private修飾的)字段,不包括繼承的字段 |
Field | getField(String name) | 獲取指定name名稱友扰、具有public修飾的字段,包含繼承字段 |
Field[] | getField() | 獲取修飾符為public的字段庶柿,包含繼承字段 |
下面的代碼演示了上述方法的使用過程
public class ReflectField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> clazz = Class.forName("reflect.Student");
//獲取指定字段名稱的Field類,注意字段修飾符必須為public而且存在該字段,
// 否則拋NoSuchFieldException
Field field = clazz.getField("age");
System.out.println("field:"+field);
//獲取所有修飾符為public的字段,包含父類字段,注意修飾符為public才會獲取
Field fields[] = clazz.getFields();
for (Field f:fields) {
System.out.println("f:"+f.getDeclaringClass());
}
System.out.println("================getDeclaredFields====================");
//獲取當前類所字段(包含private字段),注意不包含父類的字段
Field fields2[] = clazz.getDeclaredFields();
for (Field f:fields2) {
System.out.println("f2:"+f.getDeclaringClass());
}
//獲取指定字段名稱的Field類,可以是任意修飾符的自動,注意不包含父類的字段
Field field2 = clazz.getDeclaredField("desc");
System.out.println("field2:"+field2);
}
/**
輸出結果:
field:public int reflect.Person.age
f:public java.lang.String reflect.Student.desc
f:public int reflect.Person.age
f:public java.lang.String reflect.Person.name
================getDeclaredFields====================
f2:public java.lang.String reflect.Student.desc
f2:private int reflect.Student.score
field2:public java.lang.String reflect.Student.desc
*/
}
class Person{
public int age;
public String name;
//省略set和get方法
}
class Student extends Person{
public String desc;
private int score;
//省略set和get方法
}
上述方法需要注意的是村怪,如果我們不期望獲取其父類的字段,則需使用Class類的getDeclaredField/getDeclaredFields方法來獲取字段即可浮庐,倘若需要連帶獲取到父類的字段甚负,那么請使用Class類的getField/getFields,但是也只能獲取到public修飾的的字段审残,無法獲取父類的私有字段梭域。下面將通過Field類本身的方法對指定類屬性賦值,代碼演示如下:
//獲取Class對象引用
Class<?> clazz = Class.forName("reflect.Student");
Student st= (Student) clazz.newInstance();
//獲取父類public字段并賦值
Field ageField = clazz.getField("age");
ageField.set(st,18);
Field nameField = clazz.getField("name");
nameField.set(st,"Lily");
//只獲取當前類的字段,不獲取父類的字段
Field descField = clazz.getDeclaredField("desc");
descField.set(st,"I am student");
Field scoreField = clazz.getDeclaredField("score");
//設置可訪問搅轿,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());
//輸出結果:Student{age=18, name='Lily ,desc='I am student', score=88}
//獲取字段值
System.out.println(scoreField.get(st));
// 88
其中的set(Object obj, Object value)方法是Field類本身的方法碰辅,用于設置字段的值,而get(Object obj)則是獲取字段的值介时,當然關于Field類還有其他常用的方法如下:
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
void | set(Object obj, Object value) | 將指定對象變量上此 Field 對象表示的字段設置為指定的新值。 |
Object | get(Object obj) | 返回指定對象上此 Field 表示的字段的值 |
Class<?> | getType() | 返回一個 Class 對象凌彬,它標識了此Field 對象所表示字段的聲明類型沸柔。 |
boolean | isEnumConstant() | 如果此字段表示枚舉類型的元素則返回 true;否則返回 false |
String | toGenericString() | 返回一個描述此 Field(包括其一般類型)的字符串 |
String | getName() | 返回此 Field 對象表示的字段的名稱 |
Class<?> | getDeclaringClass() | 返回表示類或接口的 Class 對象铲敛,該類或接口聲明由此 Field 對象表示的字段 |
void | setAccessible(boolean flag) | 將此對象的 accessible 標志設置為指示的布爾值,即設置其可訪問性 |
上述方法可能是較為常用的褐澎,事實上在設置值的方法上,F(xiàn)ield類還提供了專門針對基本數(shù)據(jù)類型的方法伐蒋,如setInt()/getInt()工三、setBoolean()/getBoolean、setChar()/getChar()等等方法先鱼,這里就不全部列出了俭正,需要時查API文檔即可。需要特別注意的是被final關鍵字修飾的Field字段是安全的焙畔,在運行時可以接收任何修改掸读,但最終其實際值是不會發(fā)生改變的。
Method類及其用法
Method 提供關于類或接口上單獨某個方法(以及如何訪問該方法)的信息宏多,所反映的方法可能是類方法或實例方法(包括抽象方法)儿惫。下面是Class類獲取Method對象相關的方法:
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一個指定參數(shù)的Method對象,該對象反映此 Class 對象所表示的類或接口的指定已聲明方法伸但。 |
Method[] | getDeclaredMethod() | 返回 Method 對象的一個數(shù)組肾请,這些對象反映此 Class 對象表示的類或接口聲明的所有方法,包括公共更胖、保護铛铁、默認(包)訪問和私有方法隔显,但不包括繼承的方法。 |
Method | getMethod(String name, Class<?>... parameterTypes) | 返回一個 Method 對象避归,它反映此 Class 對象所表示的類或接口的指定公共成員方法荣月。 |
Method[] | getMethods() | 返回一個包含某些 Method 對象的數(shù)組,這些對象反映此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的公共 member 方法梳毙。 |
同樣通過案例演示上述方法:
import java.lang.reflect.Method;
public class ReflectMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("reflect.Circle");
//根據(jù)參數(shù)獲取public的Method,包含繼承自父類的方法
Method method = clazz.getMethod("draw",int.class,String.class);
System.out.println("method:"+method);
//獲取所有public的方法:
Method[] methods =clazz.getMethods();
for (Method m:methods){
System.out.println("m::"+m);
}
System.out.println("=========================================");
//獲取當前類的方法包含private,該方法無法獲取繼承自父類的method
Method method1 = clazz.getDeclaredMethod("drawCircle");
System.out.println("method1::"+method1);
//獲取當前類的所有方法包含private,該方法無法獲取繼承自父類的method
Method[] methods1=clazz.getDeclaredMethods();
for (Method m:methods1){
System.out.println("m1::"+m);
}
}
/**
輸出結果:
method:public void reflect.Shape.draw(int,java.lang.String)
m::public int reflect.Circle.getAllCount()
m::public void reflect.Shape.draw()
m::public void reflect.Shape.draw(int,java.lang.String)
m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
m::public boolean java.lang.Object.equals(java.lang.Object)
m::public java.lang.String java.lang.Object.toString()
m::public native int java.lang.Object.hashCode()
m::public final native java.lang.Class java.lang.Object.getClass()
m::public final native void java.lang.Object.notify()
m::public final native void java.lang.Object.notifyAll()
=========================================
method1::private void reflect.Circle.drawCircle()
m1::public int reflect.Circle.getAllCount()
m1::private void reflect.Circle.drawCircle()
*/
}
class Shape {
public void draw(){
System.out.println("draw");
}
public void draw(int count , String name){
System.out.println("draw "+ name +",count="+count);
}
}
class Circle extends Shape{
private void drawCircle(){
System.out.println("drawCircle");
}
public int getAllCount(){
return 100;
}
}
在通過getMethods方法獲取Method對象時哺窄,會把父類的方法也獲取到,如上的輸出結果账锹,把Object類的方法都打印出來了萌业。而getDeclaredMethod/getDeclaredMethods方法都只能獲取當前類的方法。我們在使用時根據(jù)情況選擇即可奸柬。下面將演示通過Method對象調用指定類的方法:
Class clazz = Class.forName("reflect.Circle");
//創(chuàng)建對象
Circle circle = (Circle) clazz.newInstance();
//獲取指定參數(shù)的方法對象Method
Method method = clazz.getMethod("draw",int.class,String.class);
//通過Method對象的invoke(Object obj,Object... args)方法調用
method.invoke(circle,15,"圈圈");
//對私有無參方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的訪問標識
method1.setAccessible(true);
method1.invoke(circle);
//對有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);
/**
輸出結果:
draw 圈圈,count=15
drawCircle
count:100
*/
在上述代碼中調用方法生年,使用了Method類的invoke(Object obj,Object... args)第一個參數(shù)代表調用的對象,第二個參數(shù)傳遞的調用方法的參數(shù)廓奕。這樣就完成了類方法的動態(tài)調用抱婉。
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
Object | invoke(Object obj, Object... args) | 對帶有指定參數(shù)的指定對象調用由此 Method 對象表示的底層方法。 |
Class<?> | getReturnType() | 返回一個 Class 對象桌粉,該對象描述了此 Method 對象所表示的方法的正式返回類型,即方法的返回類型 |
Type | getGenericReturnType() | 返回表示由此 Method 對象所表示方法的正式返回類型的 Type 對象蒸绩,也是方法的返回類型。 |
Class<?>[] | getParameterTypes() | 按照聲明順序返回 Class 對象的數(shù)組铃肯,這些對象描述了此 Method 對象所表示的方法的形參類型患亿。即返回方法的參數(shù)類型組成的數(shù)組 |
Type[] | getGenericParameterTypes() | 按照聲明順序返回 Type 對象的數(shù)組,這些對象描述了此 Method 對象所表示的方法的形參類型的押逼,也是返回方法的參數(shù)類型 |
String | getName() | 以 String 形式返回此 Method 對象表示的方法名稱步藕,即返回方法的名稱 |
boolean | isVarArgs() | 判斷方法是否帶可變參數(shù),如果將此方法聲明為帶有可變數(shù)量的參數(shù)挑格,則返回 true咙冗;否則,返回 false漂彤。 |
String | toGenericString() | 返回描述此 Method 的字符串乞娄,包括類型參數(shù)。 |
getReturnType方法/getGenericReturnType方法都是獲取Method對象表示的方法的返回類型显歧,只不過前者返回的Class類型后者返回的Type(前面已分析過)仪或,Type就是一個接口而已,在Java8中新增一個默認的方法實現(xiàn)士骤,返回的就參數(shù)類型信息
public interface Type {
//1.8新增
default String getTypeName() {
return toString();
}
}
而getParameterTypes/getGenericParameterTypes也是同樣的道理范删,都是獲取Method對象所表示的方法的參數(shù)類型,其他方法與前面的Field和Constructor是類似的拷肌。