文章目錄
- Java反射超詳解?
- 1.反射基礎
- 1.1Class類
- 1.2類加載
- 2.反射的使用
- 2.1Class物件的獲取
- 2.2Constructor類及其用法
- 2.3Field類及其用法
- 2.4Method類及其用法
Java反射超詳解?
1.反射基礎
Java反射機制是在程式的運行程序中,對于任何一個類,都能夠知道它的所有屬性和方法;對于任意一個物件,都能夠知道它的任意屬性和方法,這種動態獲取資訊以及動態呼叫物件方法的功能稱為Java語言的反射機制,
Java反射機制主要提供以下這幾個功能:
- 在運行時判斷任意一個物件所屬的類
- 在運行時構造任意一個類的物件
- 在運行時判斷任意一個類所有的成員變數和方法
- 在運行時呼叫任意一個物件的方法
1.1Class類
Class類,Class類也是一個實實在在的類,存在于JDK的java.lang包中,Class類的實體表示java應用運行時的類(class ans enum)或介面(interface and annotation)(每個java類運行時都在JVM里表現為一個class物件,可通過類名.class、型別.getClass()、Class.forName("類名")等方法獲取class物件),陣列同樣也被映射為為class 物件的一個類,所有具有相同元素型別和維數的陣列都共享該 Class 物件,基本型別boolean,byte,char,short,int,long,float,double和關鍵字void同樣表現為 class 物件,
到這我們也就可以得出以下幾點資訊:
- Class類也是類的一種,與class關鍵字是不一樣的,
- 手動撰寫的類被編譯后會產生一個Class物件,其表示的是創建的類的型別資訊,而且這個Class物件保存在同名.class的檔案中(位元組碼檔案),
- 每個通過關鍵字class標識的類,在記憶體中有且只有一個與之對應的Class物件來描述其型別資訊,無論創建多少個實體物件,其依據的都是用一個Class物件,
- Class類只存私有建構式,因此對應Class物件只能有JVM創建和加載,
- Class類的物件作用是運行時提供或獲得某個物件的型別資訊,這點對于反射技術很重要(關于反射稍后分析),
1.2類加載
-
類加載機制流程

-
類的加載

注:詳細的類加載內容,看JVM板塊,
2.反射的使用
2.1Class物件的獲取
在類加載的時候,jvm會創建一個class物件,class物件可以說是反射中最常見的,
獲取class物件的方式的主要三種:
- 根據類名:類名.class
- 根據物件:物件.getClass()
- 根據全限定類名:Class.forName(全限定類名)
public class demo1Main1 {
public static void main(String[] args) throws Exception {
//獲取Class物件的三種物件
System.out.println("根據類名:\t" + User.class);
System.out.println("根據物件:\t" + new User().getClass());
System.out.println("根據全限定類名:\t" + Class.forName("demo1.User"));
//常用的方法
Class<User> userClass = User.class;
System.out.println("獲取全限定類名:\t" + userClass.getName());
System.out.println("獲取類名:\t" + userClass.getSimpleName());
System.out.println("實體化:\t" + userClass.newInstance());
}
}
輸出結果:
根據類名: class demo1.User
根據物件: class demo1.User
根據全限定類名: class demo1.User
獲取全限定類名: demo1.User
獲取類名: User
實體化: User{name='init', age=0}
再來看看Class類的方法:
-
toString()public String toString() { return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + getName(); }toString()方法能夠將物件轉換為字串,toString()首先判斷Class型別是否是介面型別,也就是說普通類和介面都能用Class物件表示,然后在判斷是否是基本資料型別,這里判斷的都是基本資料型別和包裝類,還有void型別,
-
getName()獲取類的全限定名稱,(包括包名)即類的完整名稱,
- 如果是參考型別,比如 String.class.getName()→
java.lang.String - 如果是基本資料型別,比如 byte.class.getName()→
byte - 如果是陣列型別,比如 new Object[3].getClass().getName()→
[Ljava.lang.Object;
- 如果是參考型別,比如 String.class.getName()→
-
getSimpleName()獲取類名(不包括包名),
-
getCanonicalName()獲取全限定的類名(包括包名),
-
toGenericString()回傳類的全限定名稱,而且包括類的修飾符和型別引數資訊,
-
forName()根據類名獲得一個Class物件的參考,這個方法會使類物件進行初始化,
例如:
Class t = Class.forName("java.lang.Thread")就能夠初始化一個Thread執行緒物件,在Java中,一共有三種獲取類實體的方式:
Class.forName(java.lang.Thread)Thread.classthread.getClass()
-
newInstance()
創建一個類的實體,代表著這個類的物件,上面forName()方法對類進行初始化,newInstance方法對類進行實體化,使用該方法創建的類,必須帶有無參的構造器, -
getClassLoader()獲取類加載器物件,
-
getInterfaces()獲取當前類實作的類或是介面,可能是多個,所以回傳的是Class陣列,
-
isInterface()判斷Class物件是否是表示一個介面,
-
getFields()獲得某個類的所有的公共(public)的欄位,包括繼承自父類的所有公共欄位, 類似的還有
getMethods和getConstructors, -
getDeclaredFields獲得某個類的自己宣告的欄位,即包括public、private和proteced,默認但是不包括父類宣告的任何欄位,類似的還有
getDeclaredMethods和getDeclaredConstructors,
getName、getCanonicalName與getSimpleName的區別:
getSimpleName:只獲取類名.getName:類的全限定名,jvm中Class的表示,可以用于動態加載Class物件,例如Class.forName,getCanonicalName:回傳更容易理解的表示,主要用于輸出(toString)或log列印,大多數情況下和getName一樣,但是在內部類、陣列等型別的表示形式就不同了,
栗子:
package com.cry;
public class Test {
private class inner{
}
public static void main(String[] args) throws ClassNotFoundException {
//普通類
System.out.println(Test.class.getSimpleName()); //Test
System.out.println(Test.class.getName()); //com.cry.Test
System.out.println(Test.class.getCanonicalName()); //com.cry.Test
//內部類
System.out.println(inner.class.getSimpleName()); //inner
System.out.println(inner.class.getName()); //com.cry.Test$inner
System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner
//陣列
System.out.println(args.getClass().getSimpleName()); //String[]
System.out.println(args.getClass().getName()); //[Ljava.lang.String;
System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
//我們不能用getCanonicalName去加載類物件,必須用getName
//Class.forName(inner.class.getCanonicalName()); 報錯
Class.forName(inner.class.getName());
}
}
2.2Constructor類及其用法
Constructor類存在于反射包(java.lang.reflect)中,反映的是Class 物件所表示的類的構造方法,
獲取Constructor物件是通過Class類中的方法獲取的,Class類與Constructor相關的主要方法如下:
| 方法回傳值 | 方法名稱 | 方法說明 |
|---|---|---|
| Constructor | getConstructor(Class<?>… parameterTypes) | 回傳指定引數型別、具有public訪問權限的建構式物件 |
| Constructor<?>[] | getConstructors() | 回傳所有具有public訪問權限的建構式的Constructor物件陣列 |
| Constructor | getDeclaredConstructor(Class<?>… parameterTypes) | 回傳指定引數型別、所有宣告的(包括private)建構式物件 |
| Constructor<?>[] | getDeclaredConstructors() | 回傳所有宣告的(包括private)建構式物件 |
| T | newInstance() | 呼叫無參構造器創建此 Class 物件所表示的類的一個新實體, |
栗子:
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必須無參建構式,否則將拋例外
User user = (User) clazz.newInstance();
user.setAge(20);
user.setName("Jack");
System.out.println(user);
System.out.println("--------------------------------------------");
//獲取帶String引數的public建構式
Constructor cs1 =clazz.getConstructor(String.class);
//創建User
User user1= (User) cs1.newInstance("hiway");
user1.setAge(22);
System.out.println("user1:"+user1.toString());
System.out.println("--------------------------------------------");
//取得指定帶int和String引數建構式,該方法是私有構造private
Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必須設定可訪問
cs2.setAccessible(true);
//創建user物件
User user2= (User) cs2.newInstance(25,"hiway2");
System.out.println("user2:"+user2.toString());
System.out.println("--------------------------------------------");
//獲取所有構造包含private
Constructor<?> cons[] = clazz.getDeclaredConstructors();
// 查看每個構造方法需要的引數
for (int i = 0; i < cons.length; i++) {
//獲取建構式引數型別
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.println("建構式["+i+"]:"+cons[i].toString() );
System.out.print("引數型別["+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 + '\'' +
'}';
}
}
輸出結果:
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
建構式[0]:private com.example.javabase.User(int,java.lang.String)
引數型別[0]:(int,java.lang.String)
建構式[1]:public com.example.javabase.User(java.lang.String)
引數型別[1]:(java.lang.String)
建構式[2]:public com.example.javabase.User()
引數型別[2]:()
關于Constructor類本身一些常用方法如下(區域分,其他可查API):
| 方法回傳值 | 方法名稱 | 方法說明 |
|---|---|---|
| Class | getDeclaringClass() | 回傳 Class 物件,該物件表示宣告由此 Constructor 物件表示的構造方法的類,其實就是回傳真實型別(不包含引數) |
| Type[] | getGenericParameterTypes() | 按照宣告順序回傳一組 Type 物件,回傳的就是 Constructor物件建構式的形參型別, |
| String | getName() | 以字串形式回傳此構造方法的名稱, |
| Class<?>[] | getParameterTypes() | 按照宣告順序回傳一組 Class 物件,即回傳Constructor 物件所表示構造方法的形參型別 |
| T | newInstance(Object… initargs) | 使用此 Constructor物件表示的建構式來創建新實體 |
| String | toGenericString() | 回傳描述此 Constructor 的字串,其中包括型別引數, |
栗子:
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("引數名稱tp:"+tp);
}
System.out.println("-----getParameterTypes-----");
//獲取建構式引數型別
Class<?> clazzs[] = cs3.getParameterTypes();
for (Class claz:clazzs) {
System.out.println("引數名稱:"+claz.getName());
}
System.out.println("-----getName-----");
//以字串形式回傳此構造方法的名稱
System.out.println("getName:"+cs3.getName());
System.out.println("-----getoGenericString-----");
//回傳描述此 Constructor 的字串,其中包括型別引數,
System.out.println("getoGenericString():"+cs3.toGenericString());
輸出結果:
-----getDeclaringClass-----
構造方法的類:com.example.javabase.User
-----getGenericParameterTypes-----
引數名稱tp:int
引數名稱tp:class java.lang.String
-----getParameterTypes-----
引數名稱:int
引數名稱:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)
2.3Field類及其用法
Field 提供有關類或介面的單個欄位的資訊,以及對它的動態訪問權限,反射的欄位可能是一個類(靜態)欄位或實體欄位,
同樣的道理,我們可以通過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);
}
}
class Person{
public int age;
public String name;
//省略set和get方法
}
class Student extends Person{
public String desc;
private int score;
//省略set和get方法
}
輸出結果:
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類的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 標志設定為指示的布林值,即設定其可訪問性 |
上述方法可能是較為常用的,事實上在設定值的方法上,Field類還提供了專門針對基本資料型別的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,這里就不全部列出了,需要時查API檔案即可,需要特別注意的是被final關鍵字修飾的Field欄位是安全的,在運行時可以接收任何修改,但最終其實際值是不會發生改變的,
2.4Method類及其用法
Method 提供關于類或介面上單獨某個方法(以及如何訪問該方法)的資訊,所反映的方法可能是類方法或實體方法(包括抽象方法),
下面是Class類獲取Method物件相關的方法:
| 方法回傳值 | 方法名稱 | 方法說明 |
|---|---|---|
| Method | getDeclaredMethod(String name, Class<?>… parameterTypes) | 回傳一個指定引數的Method物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告方法, |
| Method[] | getDeclaredMethod | 回傳 Method 物件的一個陣列,這些物件反映此 Class 物件表示的類或介面宣告的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法, |
| Method | getMethod(String name, Class<?>… parameterTypes) | 回傳一個 Method 物件,它反映此 Class 物件所表示的類或介面的指定公共成員方法, |
| Method[] | getMethods() | 回傳一個包含某些 Method 物件的陣列,這些物件反映此 Class 物件所表示的類或介面(包括那些由該類或介面宣告的以及從超類和超介面繼承的那些的類或介面)的公共成員方法, |
在通過getMethods方法獲取Method物件時,會把父類的方法也獲取到,如上的輸出結果,把Object類的方法都列印出來了,而getDeclaredMethod/getDeclaredMethods方法都只能獲取當前類的方法,我們在使用時根據情況選擇即可,下面將演示通過Method物件呼叫指定類的方法:
Class clazz = Class.forName("reflect.Circle");
//創建物件
Circle circle = (Circle) clazz.newInstance();
//獲取指定引數的方法物件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) 第一個引數代表呼叫的物件,第二個引數傳遞的呼叫方法的引數,這樣就完成了類方法的動態呼叫,
| 方法回傳值 | 方法名稱 | 方法說明 |
|---|---|---|
| Object | invoke(Object obj, Object… args) | 對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法, |
| Class<?> | getReturnType() | 回傳一個 Class 物件,該物件描述了此 Method 物件所表示的方法的正式回傳型別,即方法的回傳型別 |
| Type | getGenericReturnType() | 回傳表示由此 Method 物件所表示方法的正式回傳型別的 Type 物件,也是方法的回傳型別, |
| Class<?>[] | getParameterTypes() | 按照宣告順序回傳 Class 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別,即回傳方法的引數型別組成的陣列 |
| Type[] | getGenericParameterTypes() | 按照宣告順序回傳 Type 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別的,也是回傳方法的引數型別 |
| String | getName() | 以 String 形式回傳此 Method 物件表示的方法名稱,即回傳方法的名稱 |
| boolean | isVarArgs() | 判斷方法是否帶可變引數,如果將此方法宣告為帶有可變數量的引數,則回傳 true;否則,回傳 false, |
| String | toGenericString() | 回傳描述此 Method 的字串,包括型別引數, |
getReturnType方法/getGenericReturnType方法都是獲取Method物件表示的方法的回傳型別,只不過前者回傳的Class型別后者回傳的Type(前面已分析過),Type就是一個介面而已,在Java8中新增一個默認的方法實作,回傳的就引數型別資訊
public interface Type {
//1.8新增
default String getTypeName() {
return toString();
}
}
而getParameterTypes/getGenericParameterTypes也是同樣的道理,都是獲取Method物件所表示的方法的引數型別,其他方法與前面的Field和Constructor是類似的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/294929.html
標籤:java
上一篇:黃金礦工小游戲制作步驟
