對于Java反射,平常工作中雖然經(jīng)常用到,但一直以來都沒有系統(tǒng)總結(jié)過,所以趁著目前有空總結(jié)一下,加深一下理解。
如果發(fā)現(xiàn)謬誤,歡迎各位批評指正。
本文相關(guān)知識點大部分總結(jié)自Oracle官方文檔,對于英文比較好的朋友,建議直接閱讀原文檔。
按例,首先描述定義一下。
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
通過反射,Java代碼可以發(fā)現(xiàn)有關(guān)已加載類的字段,方法和構(gòu)造函數(shù)的信息,并可以在安全限制內(nèi)對這些字段,方法和構(gòu)造函數(shù)進行操作。
簡而言之,你可以在運行狀態(tài)中通過反射機制做到:
- 對于任意一個類,都能夠知道這個類的所有屬性和方法;
- 對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;
這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。
在我看來我們平時使用Java反射主要涉及兩個類(接口)Class
, Member
,如果把這兩個類搞清楚了,反射基本就ok了。
Class
提到反射就不得不提到Class,Class可以說是反射能夠?qū)崿F(xiàn)的基礎(chǔ);注意這里說的Class與class關(guān)鍵字不是同一種東西。class關(guān)鍵字是在聲明java類時使用的;而Class 是java JDK提供的一個類,完整路徑為 java.lang.Class
,本質(zhì)上與Math, String 或者你自己定義各種類沒什么區(qū)別。
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
...
}
那Class到底在反射中起到什么作用呢?
For every type of object, the Java virtual machine instantiates an immutable instance of
java.lang.Class
which provides methods to examine the runtime properties of the object including its members and type information.Class
also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.
對于每一種類,Java虛擬機都會初始化出一個Class類型的實例,每當(dāng)我們編寫并且編譯一個新創(chuàng)建的類就會產(chǎn)生一個對應(yīng)Class對象,并且這個Class對象會被保存在同名.class文件里。當(dāng)我們new一個新對象或者引用靜態(tài)成員變量時,Java虛擬機(JVM)中的類加載器系統(tǒng)會將對應(yīng)Class對象加載到JVM中,然后JVM再根據(jù)這個類型信息相關(guān)的Class對象創(chuàng)建我們需要實例對象或者提供靜態(tài)變量的引用值。
比如創(chuàng)建編譯一個Shapes類,那么,JVM就會創(chuàng)建一個Shapes對應(yīng)Class類的Class實例,該Class實例保存了Shapes類相關(guān)的類型信息,包括屬性,方法,構(gòu)造方法等等,通過這個Class實例可以在運行時訪問Shapes對象的屬性和方法等。另外通過Class類還可以創(chuàng)建出一個新的Shapes對象。這就是反射能夠?qū)崿F(xiàn)的原因,可以說Class是反射操作的基礎(chǔ)。
需要特別注意的是,每個class(注意class是小寫,代表普通類)類,無論創(chuàng)建多少個實例對象,在JVM中都對應(yīng)同一個Class對象。
下面就通過一個簡單的例子來說明如何通過反射實例化一個對象。
public class Animal {
private String name;
private int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Animal : name = " + name + " age = " + age;
}
}
public class TestReflection{
private static final String TAG = "Reflection";
public void testReflection(){
//獲取Animal類的Class對象
Class c = Animal.class;
try {
//通過Class對象反射獲取Animal類的構(gòu)造方法
Constructor constructor = c.getConstructor(String.class, int.class);
//調(diào)用構(gòu)造方法獲取Animal實例
Animal animal = (Animal) constructor.newInstance( "Jack", 3);
//將構(gòu)造出來的Animal對象打印出來
Log.d(TAG, animal.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
下面我們來看下打印值
03-28 20:12:00.958 2835-2835/? D/Reflection: Animal : name = Jack age = 3
可以看出我們確實成功構(gòu)造出了Animal對象,而且在這過程中Class功不可沒。
有人說你這也太費事了,都知道Animal對象了,我分分鐘就能給你new出來了。
Animal animal = new Animal("Jack", 3);
沒錯!
但是如果并不能直接導(dǎo)入Animal類呢,如果構(gòu)造方法都是private的呢?這個時候反射就能大展身手了。
如何獲取Class
說Class是反射能夠?qū)崿F(xiàn)的基礎(chǔ)的另一個原因是:Java反射包java.lang.reflect
中的所有類都沒有public構(gòu)造方法,要想獲得這些類實例,只能通過Class類獲取。所以說如果想使用反射,必須得獲得Class對象。
下面列舉了幾種能夠獲取Class對象的方法。
-
Object.getClass()
通過對象實例獲取對應(yīng)Class對象,如
//Returns the Class for String
Class c = "foo".getClass();
enum E { A, B }
//Returns the Class corresponding to the enumeration type E.
Class c = A.getClass();
byte[] bytes = new byte[1024];
//Returns the Class corresponding to an array with component type byte.
Class c = bytes.getClass();
Set<String> s = new HashSet<String>();
//Returns the Class corresponding to java.util.HashSet.
Class c = s.getClass();
然而對于基本類型無法使用這種方法
boolean b;
Class c = b.getClass(); // compile-time error
-
The .class Syntax
通過類的類型獲取Class對象,基本類型同樣可以使用這種方法,如
//The `.class` syntax returns the Class corresponding to the type `boolean`.
Class c = boolean.class;
//Returns the Class for String
Class c = String.class;
-
Class.forName()
通過類的全限定名獲取Class對象, 基本類型無法使用此方法
Class c = Class.forName("java.lang.String");
對于數(shù)組比較特殊
Class cDoubleArray = Class.forName("[D"); //相當(dāng)于double[].class
Class cStringArray = Class.forName("[[Ljava.lang.String;"); //相當(dāng)于String[][].class
-
TYPE Field for Primitive Type Wrappers
基本類型和void 類型的包裝類可以使用TYPE字段獲取
Class c = Double.TYPE; //等價于 double.class.
Class c = Void.TYPE;
-
Methods that Return Classes
另外還有一些反射方法可以獲取Class對象,但前提是你已經(jīng)獲取了一個Class對象。
有點拗口,比如說你已經(jīng)獲取了一個類的Class對象,就可以通過反射方法獲取這個類的父類的Class對象。
Class.getSuperclass()
獲得給定類的父類Class
// javax.swing.JButton的父類是javax.swing.AbstractButton
Class c = javax.swing.JButton.class.getSuperclass();
類似方法還有:
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
通過Class獲取類修飾符和類型
我們知道類的聲明一般如下表示
[圖片上傳失敗...(image-7d6d12-1528676523570)]
下面我們就以HashMap為例,通過一個Demo來說明如何獲取這些信息
public class TestReflection {
private static final String TAG = "Reflection";
public void testReflection() {
Class<?> c = HashMap.class;
//獲取類名
Log.d(TAG, "Class : " + c.getCanonicalName());
//獲取類限定符
Log.d(TAG, "Modifiers : " + Modifier.toString(c.getModifiers()));
//獲取類泛型信息
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
StringBuilder parameter = new StringBuilder("Parameters : ");
for (TypeVariable t : tv) {
parameter.append(t.getName());
parameter.append(" ");
}
Log.d(TAG, parameter.toString());
} else {
Log.d(TAG, " -- No Type Parameters --");
}
//獲取類實現(xiàn)的所有接口
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
StringBuilder interfaces = new StringBuilder("Implemented Interfaces : ");
for (Type intf : intfs){
interfaces.append(intf.toString());
interfaces.append(" ");
}
Log.d(TAG, interfaces.toString());
} else {
Log.d(TAG, " -- No Implemented Interfaces --");
}
//獲取類繼承數(shù)上的所有父類
List<Class> l = new ArrayList<>();
printAncestor(c, l);
if (l.size() != 0) {
StringBuilder inheritance = new StringBuilder("Inheritance Path : ");
for (Class<?> cl : l){
inheritance.append(cl.getCanonicalName());
inheritance.append(" ");
}
Log.d(TAG, inheritance.toString());
} else {
Log.d(TAG, " -- No Super Classes --%n%n");
}
//獲取類的注解(只能獲取到 RUNTIME 類型的注解)
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
StringBuilder annotation = new StringBuilder("Annotations : ");
for (Annotation a : ann){
annotation.append(a.toString());
annotation.append(" ");
}
Log.d(TAG, annotation.toString());
} else {
Log.d(TAG, " -- No Annotations --%n%n");
}
}
private static void printAncestor(Class<?> c, List<Class> l) {
Class<?> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
}
打印結(jié)果如下
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Class : java.util.HashMap
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Modifiers : public
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Parameters : K V
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Implemented Interfaces : java.util.Map<K, V> interface java.lang.Cloneable interface java.io.Serializable
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Inheritance Path : java.util.AbstractMap java.lang.Object
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: -- No Annotations --
Member
Reflection defines an interface
java.lang.reflect.Member
which is implemented byjava.lang.reflect.Field
,java.lang.reflect.Method
, andjava.lang.reflect.Constructor
.
對于Member接口可能會有人不清楚是干什么的,但如果提到實現(xiàn)它的三個實現(xiàn)類,估計用過反射的人都能知道。我們知道類成員主要包括構(gòu)造函數(shù)
,變量
和方法
,Java中的操作基本都和這三者相關(guān),而Member的這三個實現(xiàn)類就分別對應(yīng)他們。
java.lang.reflect.Field
:對應(yīng)類變量
java.lang.reflect.Method
:對應(yīng)類方法
java.lang.reflect.Constructor
:對應(yīng)類構(gòu)造函數(shù)
反射就是通過這三個類才能在運行時改變對象狀態(tài)。下面就讓我們通過一些例子來說明如何通過反射操作它們。
首先建一個測試類
public class Cat {
public static final String TAG = Cat.class.getSimpleName();
private String name;
@Deprecated
public int age;
public Cat(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void eat(String food){
Log.d(TAG, "eat food " + food);
}
public void eat(String... foods){
StringBuilder s = new StringBuilder();
for(String food : foods){
s.append(food);
s.append(" ");
}
Log.d(TAG, "eat food " + s.toString());
}
public void sleep(){
Log.d(TAG, "sleep");
}
@Override
public String toString() {
return "name = " + name + " age = " + age;
}
}
Field
通過Field你可以訪問給定對象的類變量,包括獲取變量的類型、修飾符、注解、變量名、變量的值或者重新設(shè)置變量值,即使變量是private的。
獲取Field
Class提供了4種方法獲得給定類的Field。
getDeclaredField(String name)
獲取指定的變量(只要是聲明的變量都能獲得,包括private)
getField(String name)
獲取指定的變量(只能獲得public的)
getDeclaredFields()
獲取所有聲明的變量(包括private)
getFields()
獲取所有的public變量獲取變量類型、修飾符、注解
一個例子說明問題
public void testField(){
Class c = Cat.class;
Field[] fields = c.getDeclaredFields();
for(Field f : fields){
StringBuilder builder = new StringBuilder();
//獲取名稱
builder.append("filed name = ");
builder.append(f.getName());
//獲取類型
builder.append(" type = ");
builder.append(f.getType());
//獲取修飾符
builder.append(" modifiers = ");
builder.append(Modifier.toString(f.getModifiers()));
//獲取注解
Annotation[] ann = f.getAnnotations();
if (ann.length != 0) {
builder.append(" annotations = ");
for (Annotation a : ann){
builder.append(a.toString());
builder.append(" ");
}
} else {
builder.append(" -- No Annotations --");
}
Log.d(TAG, builder.toString());
}
}
打印結(jié)果:
filed name = age type = int modifiers = public annotations = @java.lang.Deprecated()
filed name = name type = class java.lang.String modifiers = private -- No Annotations --
filed name = TAG type = class java.lang.String modifiers = public static final -- No Annotations --
-
獲取、設(shè)置變量值
給定一個對象和它的成員變量名稱,就能通過反射獲取和改變該變量的值。
什么都不說了,沒有什么是不能通過一個例子解決的, Easy~
仍然是上面的測試類,通過反射獲取并改變Cat的name和age.
public void testField(){
Cat cat = new Cat("Tom", 2);
Class c = cat.getClass();
try {
//注意獲取private變量時,需要用getDeclaredField
Field fieldName = c.getDeclaredField("name");
Field fieldAge = c.getField("age");
//反射獲取名字, 年齡
String name = (String) fieldName.get(cat);
int age = fieldAge.getInt(cat);
Log.d(TAG, "before set, Cat name = " + name + " age = " + age);
//反射重新set名字和年齡
fieldName.set(cat, "Timmy");
fieldAge.setInt(cat, 3);
Log.d(TAG, "after set, Cat " + cat.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
嗯?竟然報錯?
System.err: java.lang.IllegalAccessException: Class java.lang.Class<com.example.ming.testnestscrollview.TestReflection> cannot access private field java.lang.String com.example.ming.testnestscrollview.Cat.name of class java.lang.Class<com.example.ming.testnestscrollview.Cat>
System.err: at java.lang.reflect.Field.get(Native Method)
System.err: at com.example.ming.testnestscrollview.TestReflection.testField(TestReflection.java:22)
System.err: at com.example.ming.testnestscrollview.MainActivity.onCreate(MainActivity.java:17)
觀察一下異常信息java.lang.IllegalAccessException
,說我們沒有權(quán)限操作變量name;回到Cat類中查看一下name變量。
private String name;
原來name變量是private,Java運行時會進行訪問權(quán)限檢查,private類型的變量無法進行直接訪問,剛剛進行的反射操作并沒有打破這種封裝,所以我們依然沒有權(quán)限對private屬性進行直接訪問。
難道就沒有辦法打破這種限制嗎?必須有!強大的反射早已暗中為我們準(zhǔn)備好了一切。
反射包里為我們提供了一個強大的類
-
java.lang.reflect.AccessibleObject
AccessibleObject
為我們提供了一個方法 setAccessible(boolean flag),該方法的作用就是可以取消 Java 語言訪問權(quán)限檢查。所以任何繼承AccessibleObject
的類的對象都可以使用該方法取消 Java 語言訪問權(quán)限檢查。(final類型變量也可以通過這種辦法訪問)
public final class Field extends AccessibleObject implements Member
Field
正是AccessibleObject
的子類,那么簡單了,只要在訪問私有變量前調(diào)用filed.setAccessible(true)
就可以了
...
fieldName.setAccessible(true);
//反射獲取名字, 年齡
String name = (String) fieldName.get(cat);
...
打印結(jié)果
TestReflection: before set, Cat name = Tom age = 2
TestReflection: after set, Cat name = Timmy age = 3
Bingo!
注意Method
和Constructor
也都是繼承AccessibleObject
,所以如果遇到私有方法和私有構(gòu)造函數(shù)無法訪問,記得處理方法一樣。
Method
The
java.lang.reflect.Method
class provides APIs to access information about a method's modifiers, return type, parameters, annotations, and thrown exceptions. It also be used to invoke methods.
這節(jié)主要介紹如何通過反射訪問對象的方法。
-
獲取Method
Class依然提供了4種方法獲取Method:
getDeclaredMethod(String name, Class<?>... parameterTypes)
根據(jù)方法名獲得指定的方法, 參數(shù)name為方法名,參數(shù)parameterTypes為方法的參數(shù)類型,如 getDeclaredMethod(“eat”, String.class)
getMethod(String name, Class<?>... parameterTypes)
根據(jù)方法名獲取指定的public方法,其它同上
getDeclaredMethods()
獲取所有聲明的方法
getMethods()
獲取所有的public方法
注意:獲取帶參數(shù)方法時,如果參數(shù)類型錯誤會報
NoSuchMethodException
,對于參數(shù)是泛型的情況,泛型須當(dāng)成Object處理(Object.class)
-
獲取方法返回類型
getReturnType()
獲取目標(biāo)方法返回類型對應(yīng)的Class對象
getGenericReturnType()
獲取目標(biāo)方法返回類型對應(yīng)的Type對象
這兩個方法有啥區(qū)別呢?- getReturnType()返回類型為Class,getGenericReturnType()返回類型為Type; Class實現(xiàn)Type。
- 返回值為普通簡單類型如Object, int, String等,getGenericReturnType()返回值和getReturnType()一樣
例如public String function1()
那么各自返回值為:
getReturnType() : class java.lang.String
getGenericReturnType() : class java.lang.String
- 返回值為泛型
例如public T function2()
那么各自返回值為:
getReturnType() : class java.lang.Object
getGenericReturnType() : T
- 返回值為參數(shù)化類型
例如public Class<String> function3()
那么各自返回值為:
getReturnType() : class java.lang.Class
getGenericReturnType() : java.lang.Class<java.lang.String>
其實反射中所有形如getGenericXXX()
的方法規(guī)則都與上面所述類似。
獲取方法參數(shù)類型
getParameterTypes()
獲取目標(biāo)方法各參數(shù)類型對應(yīng)的Class對象
getGenericParameterTypes()
獲取目標(biāo)方法各參數(shù)類型對應(yīng)的Type對象
返回值為數(shù)組,它倆區(qū)別同上 “方法返回類型的區(qū)別” 。獲取方法聲明拋出的異常的類型
getExceptionTypes()
獲取目標(biāo)方法拋出的異常類型對應(yīng)的Class對象
getGenericExceptionTypes()
獲取目標(biāo)方法拋出的異常類型對應(yīng)的Type對象
返回值為數(shù)組,區(qū)別同上獲取方法參數(shù)名稱
.class文件中默認(rèn)不存儲方法參數(shù)名稱,如果想要獲取方法參數(shù)名稱,需要在編譯的時候加上-parameters
參數(shù)。(構(gòu)造方法的參數(shù)獲取方法同樣)
//這里的m可以是普通方法Method,也可以是構(gòu)造方法Constructor
//獲取方法所有參數(shù)
Parameter[] params = m.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
p.getType(); //獲取參數(shù)類型
p.getName(); //獲取參數(shù)名稱,如果編譯時未加上`-parameters`,返回的名稱形如`argX`, X為參數(shù)在方法聲明中的位置,從0開始
p.getModifiers(); //獲取參數(shù)修飾符
p.isNamePresent(); //.class文件中是否保存參數(shù)名稱, 編譯時加上`-parameters`返回true,反之flase
}
獲取方法參數(shù)名稱的詳細(xì)信息請參考o(jì)racle的官方例子MethodParameterSpy
-
獲取方法修飾符
方法與Filed等類似
method.getModifiers();
Ps:順便多介紹幾個Method方法
-
method.isVarArgs()
//判斷方法參數(shù)是否是可變參數(shù)
public Constructor<T> getConstructor(Class<?>... parameterTypes) //返回true
public Constructor<T> getConstructor(Class<?> [] parameterTypes) //返回flase
-
method.isSynthetic()
//判斷是否是復(fù)合方法,個人理解復(fù)合方法是編譯期間編譯器生成的方法,并不是源代碼中有的方法 -
method.isBridge()
//判斷是否是橋接方法,橋接方法是 JDK 1.5 引入泛型后,為了使Java的泛型方法生成的字節(jié)碼和 1.5 版本前的字節(jié)碼相兼容,由編譯器自動生成的方法。可以參考https://www.cnblogs.com/zsg88/p/7588929.html
-
通過反射調(diào)用方法
反射通過Method的invoke()
方法來調(diào)用目標(biāo)方法。第一個參數(shù)為需要調(diào)用的目標(biāo)類對象,如果方法為static的,則該參數(shù)為null。后面的參數(shù)都為目標(biāo)方法的參數(shù)值,順序與目標(biāo)方法聲明中的參數(shù)順序一致。
public native Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
還是以上面測試類Cat為例
注意:如果方法是private的,可以使用
method.setAccessible(true)
方法繞過權(quán)限檢查
Class<?> c = Cat.class;
try {
//構(gòu)造Cat實例
Constructor constructor = c.getConstructor(String.class, int.class);
Object cat = constructor.newInstance( "Jack", 3);
//調(diào)用無參方法
Method sleep = c.getDeclaredMethod("sleep");
sleep.invoke(cat);
//調(diào)用定項參數(shù)方法
Method eat = c.getDeclaredMethod("eat", String.class);
eat.invoke(cat, "grass");
//調(diào)用不定項參數(shù)方法
//不定項參數(shù)可以當(dāng)成數(shù)組來處理
Class[] argTypes = new Class[] { String[].class };
Method varargsEat = c.getDeclaredMethod("eat", argTypes);
String[] foods = new String[]{
"grass", "meat"
};
varargsEat.invoke(cat, (Object)foods);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
被調(diào)用的方法本身所拋出的異常在反射中都會以
InvocationTargetException
拋出。換句話說,反射調(diào)用過程中如果異常InvocationTargetException
拋出,說明反射調(diào)用本身是成功的,因為這個異常是目標(biāo)方法本身所拋出的異常。
Constructor
這節(jié)主要介紹如何通過反射訪問構(gòu)造方法并通過構(gòu)造方法構(gòu)建新的對象。
-
獲取構(gòu)造方法
和Method一樣,Class也為Constructor提供了4種方法獲取
getDeclaredConstructor(Class<?>... parameterTypes)
獲取指定構(gòu)造函數(shù),參數(shù)parameterTypes為構(gòu)造方法的參數(shù)類型
getConstructor(Class<?>... parameterTypes)
獲取指定public構(gòu)造函數(shù),參數(shù)parameterTypes為構(gòu)造方法的參數(shù)類型
getDeclaredConstructors()
獲取所有聲明的構(gòu)造方法
getConstructors()
獲取所有的public構(gòu)造方法
構(gòu)造方法的名稱、限定符、參數(shù)、聲明的異常等獲取方法都與Method類似,請參照Method
-
創(chuàng)建對象
通過反射有兩種方法可以創(chuàng)建對象:
java.lang.reflect.Constructor.newInstance()
Class.newInstance()
一般來講,我們優(yōu)先使用第一種方法;那么這兩種方法有何異同呢?
-
Class.newInstance()
僅可用來調(diào)用無參的構(gòu)造方法;Constructor.newInstance()
可以調(diào)用任意參數(shù)的構(gòu)造方法。 -
Class.newInstance()
會將構(gòu)造方法中拋出的異常不作處理原樣拋出;Constructor.newInstance()
會將構(gòu)造方法中拋出的異常都包裝成InvocationTargetException
拋出。 -
Class.newInstance()
需要擁有構(gòu)造方法的訪問權(quán)限;Constructor.newInstance()
可以通過setAccessible(true)
方法繞過訪問權(quán)限訪問private構(gòu)造方法。
例子在Method一節(jié)已經(jīng)寫過,這里直接截取過來
Class<?> c = Cat.class;
try {
Constructor constructor = c.getConstructor(String.class, int.class);
Cat cat = (Cat) constructor.newInstance( "Jack", 3);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
注意:反射不支持自動封箱,傳入?yún)?shù)時要小心(自動封箱是在編譯期間的,而反射在運行期間)
數(shù)組和枚舉
數(shù)組和枚舉也是對象,但是在反射中,對數(shù)組和枚舉的創(chuàng)建、訪問和普通對象有那么一丟丟的不同,所以Java反射為數(shù)組和枚舉提供了一些特定的API接口。
數(shù)組
-
數(shù)組類型
數(shù)組類型:數(shù)組本質(zhì)是一個對象,所以它也有自己的類型。
例如對于int[] intArray
,數(shù)組類型為class [I
。數(shù)組類型中的[
個數(shù)代表數(shù)組的維度,例如[
代表一維數(shù)組,[[
代表二維數(shù)組;[
后面的字母代表數(shù)組元素類型,I
代表int
,一般為類型的首字母大寫(long類型例外,為J)。
class [B //byte類型一維數(shù)組
class [S //short類型一維數(shù)組
class [I //int類型一維數(shù)組
class [C //char類型一維數(shù)組
class [J //long類型一維數(shù)組,J代表long類型,因為L被引用對象類型占用了
class [F //float類型一維數(shù)組
class [D //double類型一維數(shù)組
class [Lcom.dada.Season //引用類型一維數(shù)組
class [[Ljava.lang.String //引用類型二維數(shù)組
//獲取一個變量的類型
Class<?> c = field.getType();
//判斷該變量是否為數(shù)組
if (c.isArray()) {
//獲取數(shù)組的元素類型
c.getComponentType()
}
-
創(chuàng)建和初始化數(shù)組
Java反射為我們提供了java.lang.reflect.Array
類用來創(chuàng)建和初始化數(shù)組。
//創(chuàng)建數(shù)組, 參數(shù)componentType為數(shù)組元素的類型,后面不定項參數(shù)的個數(shù)代表數(shù)組的維度,參數(shù)值為數(shù)組長度
Array.newInstance(Class<?> componentType, int... dimensions)
//設(shè)置數(shù)組值,array為數(shù)組對象,index為數(shù)組的下標(biāo),value為需要設(shè)置的值
Array.set(Object array, int index, int value)
//獲取數(shù)組的值,array為數(shù)組對象,index為數(shù)組的下標(biāo)
Array.get(Object array, int index)
例子,用反射創(chuàng)建 int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2);
Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);
注意:反射支持對數(shù)據(jù)自動加寬,但不允許數(shù)據(jù)narrowing(變窄?真難翻譯)。意思是對于上述set方法,你可以在int類型數(shù)組中 set short類型數(shù)據(jù),但不可以set long類型數(shù)據(jù),否則會報IllegalArgumentException。
-
多維數(shù)組
Java反射沒有提供能夠直接訪問多維數(shù)組元素的API,但你可以把多維數(shù)組當(dāng)成數(shù)組的數(shù)組處理。
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
或者
Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
枚舉
枚舉隱式繼承自java.lang.Enum
,Enum繼承自O(shè)bject,所以枚舉本質(zhì)也是一個類,也可以有成員變量,構(gòu)造方法,方法等;對于普通類所能使用的反射方法,枚舉都能使用;另外java反射額外提供了幾個方法為枚舉服務(wù)。
Class.isEnum()
Indicates whether this class represents an enum type
Class.getEnumConstants()
Retrieves the list of enum constants defined by the enum in the order they're declared
java.lang.reflect.Field.isEnumConstant()
Indicates whether this field represents an element of an enumerated type
反射的缺點
沒有任何一項技術(shù)是十全十美的,Java反射擁有強大功能的同時也帶來了一些副作用。
-
性能開銷
反射涉及類型動態(tài)解析,所以JVM無法對這些代碼進行優(yōu)化。因此,反射操作的效率要比那些非反射操作低得多。我們應(yīng)該避免在經(jīng)常被執(zhí)行的代碼或?qū)π阅芤蠛芨叩某绦蛑惺褂梅瓷洹?/li> -
安全限制
使用反射技術(shù)要求程序必須在一個沒有安全限制的環(huán)境中運行。如果一個程序必須在有安全限制的環(huán)境中運行,如Applet,那么這就是個問題了。 -
內(nèi)部曝光
由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導(dǎo)致意料之外的副作用--代碼有功能上的錯誤,降低可移植性。反射代碼破壞了抽象性,因此當(dāng)平臺發(fā)生改變的時候,代碼的行為就有可能也隨著變化。
使用反射的一個原則:如果使用常規(guī)方法能夠?qū)崿F(xiàn),那么就不要用反射。
作者:ming152
鏈接:http://www.lxweimin.com/p/607ff4e79a13
來源:簡書