反射庫提供了一個非常豐富且精心設(shè)計的工具集,以便能夠動態(tài)編寫能夠操縱Java代碼的程序。這項功能被大量應(yīng)用于JavaBeans中,它是Java組件的體系結(jié)構(gòu)。
1.什么是反射?
反射是可以讓我們在運行時獲取類的方法、屬性、父類、接口等類的內(nèi)部信息的機制,在編寫一些通用性較高的代碼或者框架的時候使用。也就是說,反射本質(zhì)上是一個“反著來”的過程。我們通過new創(chuàng)建一個類的實例時,實際上是由Java虛擬機根據(jù)這個類的Class對象在運行時構(gòu)建出來的,而反射是通過一個類的Class對象來獲取它的定義信息,從而我們可以訪問到它的屬性、方法,知道這個類的父類、實現(xiàn)了哪些接口等信息。
要想理解反射的原理,首先要了解什么是類型信息。Java讓我們識別對象和類的信息,主要有2種方式:一種是傳統(tǒng)的RTTI,它假定我們在編譯時已經(jīng)知道了所有的類型信息;另一種是反射機制,它允許我們在運行時發(fā)現(xiàn)和使用類的信息。
程序中一般的對象的類型都是在編譯期就確定下來的,而Java反射機制可以動態(tài)地創(chuàng)建對象并調(diào)用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創(chuàng)建對象,即使這個對象的類型在編譯期是未知的。
反射的核心是JVM在運行時才動態(tài)加載類或調(diào)用方法/訪問屬性,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰。
2.反射的作用
- 在運行時獲取任意一個類的父類、接口、構(gòu)造器、屬性、方法等;
- 在運行時構(gòu)造任意一個類的對象;
- 在運行時調(diào)用任意一個對象的方法(通過反射甚至可以調(diào)用private方法)
3.Class類
我們知道使用javac能夠?qū)?java文件編譯為.class文件,這個.class文件包含了我們對類的原始定義信息(父類、接口、構(gòu)造器、屬性、方法等)。Class 類的實例表示正在運行的 Java 應(yīng)用程序中的類或接口。在 Java 中,每個 Class 都有一個相應(yīng)的 Class 對象,即對于每一個類,.class文件在運行時會被ClassLoader加載到JVM中,當(dāng)一個.class文件被加載后,JVM會為之生成一個Class對象,用于表示這個類的類型信息,我們在程序中通過new實例化的對象實際上是在運行時根據(jù)相應(yīng)的Class對象構(gòu)造出來的。確切的說,這個Class對象實際上是java.lang.Class類的一個實例,從中我們可以得出結(jié)論:萬物皆對象,任何類型(包括基本類型,引用類型,void關(guān)鍵字等).class都是java.lang.Class的實例,簡言之,class對象是Class泛型類的實例,它代表了一個類型。由于java.lang.Class類不存在公有構(gòu)造器,它在每個類第一次被加載時由JVM自動調(diào)用,因此我們不能直接實例化這個類,我們可以通過以下方法獲取一個Class對象。
在下面的講解中,我們將以People類和Student類為例:
public class People {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void speak() {
System.out.println(getName() + " " + getAge());
}
}
public class Student extends People {
private int grade;
public Student(String name, int age) {
super(name, age);
}
public Student(String name, int age, int grade) {
super(name, age);
this.grade = grade;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
private void learn(String course) {
System.out.println(name + " learn " + course);
}
}
獲取class對象有以下三種:
- 可以通過
類名.class得到相應(yīng)類的Class對象,如:
Class peopleClass = People.class;
- 如果已知類的全限定名稱(包含包名),可以通過Class的forName靜態(tài)方法得到類的Class對象,如:
Class peopleClass = Class.forName("cn.habitdiary.People");
//假設(shè)People類在cn.habitdiary包中
在使用forName時必須要保證傳入的字符串是一個類名或接口名,否則會拋出一個ClassNotFoundException,這是一個必檢異常,所以我們在使用該方法時必須提供一個異常處理器,例如:
try{
String name = "xxx";
Class c1 = Class.forName(name);
}
catch(Exception e){
e.printStackTrace();
}
在JDBC開發(fā)中常用此方法加載數(shù)據(jù)庫驅(qū)動
- 可以通過
類的實例對象.getClass()得到相應(yīng)類的Class對象,如:
People people = new People("Steven", 20);
Class peopleClass = people.getClass();
實例對象.getClass().getName() 可以獲取當(dāng)前對象的類的全限定名稱(包含包名)
實例對象.getClass().getCanonicalName()大部分情況和getName()相同,但在表示數(shù)組或內(nèi)部類時有所區(qū)別,比如對于String數(shù)組,getName返回的是[Ljava.lang.String之類的表現(xiàn)形式,而getCanonicalName返回的就是跟我們聲明類似的形式。
實例對象.getClass().getSimpleName()只是去掉getCanonicalName()返回結(jié)果前面的包部分
但在類加載的時候需要的是getName得到的那樣的名字,而在根據(jù)類名字創(chuàng)建文件的時候最好使用getCanonicalName()
具體分析見:源碼解析getCanonicalName(), getName(), getSimpleName()的不同
對于基本數(shù)據(jù)類型的封裝類,還可以采用.TYPE來獲取相對應(yīng)的基本數(shù)據(jù)類型的 Class 實例
三種方式的比較:
1.調(diào)用Class.forName()方法,如果類沒有加載就加載,加載時執(zhí)行static語句,找不到就拋出異常,也可以理解為手動加載類的一種方法,它會自動初始化Class對象。
2.getClass()方法,在已經(jīng)持有該類的對象時來獲取Class引用。其Class對象已經(jīng)被初始化。
3..class方式創(chuàng)建Class對象引用時,不會自動初始化Class對象。主要進行下面的步驟:
1)加載,類加載器查找字節(jié)碼(classpath)創(chuàng)建Class對象;
2)鏈接,為靜態(tài)域分配存儲空間;
3)初始化,其被延遲到靜態(tài)方法或非常數(shù)靜態(tài)域首次引用時。
總結(jié):Java獲得Class對象的引用的方法中,Class.forName()方法會自動初始化Class對象,而.class方法不會,.class的初始化被延遲到靜態(tài)方法或非常數(shù)靜態(tài)域的首次引用。
注意:
1.一個Class對象實際上表現(xiàn)的是一個類型,而這個類型未必一定是一種類。例如,int不是類,但int.class是一個Class對象。
2.Class類實際上是一個泛型類。 Class c = T.class實際上是Class<T> c = T.class。Class c = x.getClass()實際上是Class<? extends T> c = x.getClass()(T的x的聲明類型,x.getClass()獲得的是x的實際類型的Class對象)。但有時候我們不能提前確定class對象的類型,如Class c = Class.forName("T")實際上是Class<?> c = Class.forName("T")。
3.虛擬機為每個類型管理一個Class對象,可以用 == 運算符實現(xiàn)兩個類對象比較的操作,這可以用來判斷兩個對象屬不屬于同一個類。
4.getClass()方法返回的是對象實際類型的class對象,而不是聲明類型的class對象。
5.newInstance()方法可以返回一個Class對象對應(yīng)類的新實例(返回值類型是Object),前提要有無參的構(gòu)造方法,newInstance()方法是通過調(diào)用無參構(gòu)造方法來創(chuàng)建對象的。比如:
String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
如果希望給構(gòu)造器提供參數(shù),就不能使用這種寫法,必須先獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建實例。這種方法可以用指定的構(gòu)造器構(gòu)造類的實例:
//獲取String所對應(yīng)的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數(shù)的構(gòu)造器
Constructor constructor = c.getConstructor(String.class);
//根據(jù)構(gòu)造器創(chuàng)建實例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
類的靜態(tài)加載和動態(tài)加載
靜態(tài)加載:在編譯時就需要加載所有可能用到的類,比如new關(guān)鍵字就是靜態(tài)加載類。
動態(tài)加載:在運行時加載類。
靜態(tài)加載類的缺點是:比如用new創(chuàng)建了多個類的對象,其中某一個類不存在,則整個程序無法通過編譯。而如果動態(tài)加載類,只要不使用不存在的類,其他類還可以正常使用。
比如
class Office{
public static void main(String[] args){
//靜態(tài)加載類
if("Word".equals(args[0])){
Word w = new Word();
w.start();
}
if("Excel".equals(args[0]){
Excel e = new Excel();
e.start();
}
}
上面的程序通過new關(guān)鍵字創(chuàng)建對象,是靜態(tài)加載類,所以如果Word類和Excel類中缺少一個,另一個類即使存在也無法通過編譯。
如果通過反射動態(tài)加載類可以解決這個問題,如下:
class OfficeBetter{
public static void main(String[] args){
try{
//動態(tài)加載類,在運行時刻加載
Class c = Class.forName(args[0]);
/*通過類類型,創(chuàng)建該類的對象,此時需要強制轉(zhuǎn)換為Excel和Word的公有類型,
所以可以定義OfficeAble接口,讓Excel和Word實現(xiàn)這個接口*/
OfficeAble oa = (OfficeAble)c.newInstance();
oa.start();
}
catch(Exception e){
e.printStackTrace();
}
}
由于是動態(tài)加載類,新增其他實現(xiàn)OfficeAble接口的類不必重新編譯OfficeBetter類。
4.在運行時分析類的能力
下面簡要介紹一下反射機制最重要的內(nèi)容 —— 檢查類的結(jié)構(gòu)。
Java中為了支持反射機制主要提供了以下的類:
java.lang.Class
java.lang.reflect.Field
java.lang.reflect.Constructor
java.lang.reflect.Method
java.lang.reflect.Modifier

java.lang.Class類的常用API如下:
- Field[] getFields()
- Field[] getDeclaredFields()
- Method[] getMethods()
- Method[] getDeclaredMethods()
- Constructor< ? >[] getConstructors()
- Constructor< ? >[] getDeclaredConstructors()
- Class< ? > getSupperClass()
- Class< ? >[] getInterfaces()
提示:getFields、getMethods和getConstructors方法將分別返回類提供的public域、方法和構(gòu)造器數(shù)組,其中包括超類的公有成員;而getDeclaredFields、getDeclaredMethods和getDeclaredConstructors方法將分別返回類中聲明的全部域、方法和構(gòu)造器,不論訪問權(quán)限,但不包括超類的成員。getSupperClass()返回class對象對應(yīng)類的超類的class對象,沒有顯式繼承的類的超類是Object。getInterfaces返回class對象對應(yīng)類的所有接口的class對象
其中java.lang.reflect包中的三個類Field、Constructor和Method分別用于描述類的域、構(gòu)造器、方法。
這三個類的常用API如下:
- String getName() 返回一個用于描述域名、構(gòu)造器或方法的字符串
- Class< ? > getDeclaringClass() 返回一個用于描述類中定義的域、構(gòu)造器或方法的Class對象
- Class< ? >[] getExceptionTypes() (在Constuctor和Method類中)
返回一個用于描述方法拋出異常類型的Class對象數(shù)組 - int getModifiers() 返回一個描述域、構(gòu)造器或方法的修飾符的整型數(shù)值。使用Modifier的toString靜態(tài)方法可以分析這個返回值
- Class< ? >[] getParameterTypes() (在Constructor和Method類中) 返回一個用于描述參數(shù)類型的Class對象數(shù)組
- Class< ? > getReturnType() (在Method類中) 返回一個用于描述返回類型的Class對象
java.lang.reflect.Modifier類的常用API如下
- static String toString(int modifiers)
返回修飾符對應(yīng)的字符串描述 - static boolean isAbstract(int modifiers)
- static boolean isFinal(int modifiers)
- static boolean isInterface(int modifiers)
- static boolean isNative(int modifiers)
- static boolean isPrivate(int modifiers)
- static boolean isProtected(int modifiers)
- static boolean isPublic(int modifiers)
- static boolean isStatic(int modifiers)
- static boolean isStrict(int modifiers)
- static boolean isSynchronized(int modifiers)
- static boolean isVolatile(int modifiers)
上述方法檢測修飾符是否是某一特定修飾符
java.lang.reflect.Modifier類一般和Field、Constructor、Method類的getModifiers()方法配套使用,用于解析該方法返回的整型數(shù)值的含義
下面是一個檢測類內(nèi)部結(jié)構(gòu)的例子
import java.lang.reflect.*;
import java.lang.Class;
import java.util.Scanner;
public class ReflectionTest {
public static void main(String[] args) {
String name;
if(args.length > 0)
name = args[0];
else {
Scanner in = new Scanner(System.in);
System.out.println("Enter class name (e.g. java.util.Date)");
name = in.next();
}
try {
Class<?> c1 = Class.forName(name);
Class<?> superc1 = c1.getSuperclass();
String modifiers = Modifier.toString(c1.getModifiers());
if (modifiers.length() > 0)
System.out.print(modifiers + " " );
System.out.print("class " + name);
if(superc1 != null && superc1 != Object.class)
System.out.print(" extends " + superc1.getSimpleName());
System.out.print("\n{\n");
printFields(c1);
System.out.println();
printConstructors(c1);
System.out.println();
printMethods(c1);
System.out.println("}");
}
catch(ClassNotFoundException e)
{
e.printStackTrace();
}
System.exit(0);
}
public static void printConstructors(Class<?> c1) {
Constructor<?>[] constructors = c1.getDeclaredConstructors();
for(Constructor<?> c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length() > 0)
System.out.print(modifiers + " ");
System.out.print(name + "(");
Class<?>[] paramTypes = c.getParameterTypes();
for(int j = 0;j < paramTypes.length;j++) {
if(j > 0)
System.out.print(", ");
System.out.print(paramTypes[j].getSimpleName());
}
System.out.println(");");
}
}
public static void printMethods(Class<?> c1)
{
Method[] methods = c1.getDeclaredMethods();
for(Method m :methods) {
Class<?> retType = m.getReturnType();
String name = m.getName();
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if(modifiers.length() > 0)
System.out.print(modifiers + " ");
System.out.print(retType.getSimpleName() + " " + name + "(");
Class<?>[] paramTypes = m.getParameterTypes();
for(int j = 0;j < paramTypes.length;j++) {
if(j > 0)
System.out.print(", ");
System.out.print(paramTypes[j].getSimpleName());
}
System.out.println(");");
}
}
public static void printFields(Class<?> c1)
{
Field[] Fields = c1.getDeclaredFields();
for(Field f : Fields) {
Class<?> type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if(modifiers.length() > 0)
System.out.print(modifiers + " ");
System.out.println(type.getSimpleName() + " " + name + ";");
}
}
}
補充:instanceof運算符、Class的isInstance( )與isAssignableFrom()的區(qū)別
5.在運行時使用反射分析對象
反射不僅可以查看類的域、構(gòu)造器、方法等,還可以進一步查看某個對象指定數(shù)據(jù)域的值。
查看對象域的關(guān)鍵方法是Field類中的get方法。如果f是一個Field類型的對象(例如,通過getDeclaredFields得到的對象),obj是某個包含f域的類的對象,f.get(obj)將返回一個Object對象,其值為obj域的當(dāng)前值。比如:
Employee harry = new Empolyee("Harry Hacker",35000,10,1,1989);
Class<Employee> c1 = harry.getClass();
Field f = c1.getDeclaredField("name"); //返回某一個特定域
f.setAccessible(true); //由于name是私有域,必須先設(shè)置為可訪問
Object v = f.get(harry);
上述的String可以作為Object返回,但如果某個域是基本數(shù)據(jù)類型,比如double,可以使用Field類的getDouble方法返回double類型數(shù)值,也可以使用get方法,反射機制會將其自動裝箱成Double類型對象。f.set(obj,value) 可以把obj對象的f域設(shè)置為value
下面是一些相關(guān)API
在java.lang.reflect.Field中:
- Object get(Object obj)
返回obj對象中用Field對象表示的域值 - xxx getXxx(Object obj)
返回obj對象的基本類型的域的值 - void set(Object obj,Object newValue)
用一個新值設(shè)置obj對象中Field對象表示的域
在java.lang.Class中:
- Field getField(String name)
返回指定名稱的公有域 - Field getDeclaredField(String name)
- 返回指定名稱的聲明的域
在java.lang.reflect.AccessibleObject中:
- void setAccessible(boolean flag)
為反射對象設(shè)置可訪問標志,flag為true表明屏蔽Java語言的訪問檢查,使得對象的私有屬性也可以被查詢和設(shè)置 - boolean isAccessible()
返回反射對象的可訪問標志的值 - static void setAccessible(AccessibleObject[] array,boolean flag)
批量設(shè)置AccessibleObject(是Field、Constructor、Method的公共超類)數(shù)組的所有元素的可訪問標志
6.使用反射編寫泛型數(shù)組代碼
java.lang.reflect包中的Array類允許動態(tài)地創(chuàng)建數(shù)組。例如,在Arrays類中有copyOf方法,可以擴展已經(jīng)填滿的數(shù)組。
Employee[] a = new Employee[100];
a = Arrays.copyOf(a,2 * a.length);
我們想要編寫一個適用于所有數(shù)組類型的copyOf方法,下面是第一次嘗試:
public static Object[] badCopyOf(Object[] a,int newLength){
Object[] newArray = new Object[newLength];
System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength);
return newArray;
}
上述代碼存在一個錯誤,即使用了new Object[newLength]創(chuàng)建數(shù)組,這樣會在運行時拋出ClassCastException,將一個Employee[]臨時轉(zhuǎn)換成Object[],再把它轉(zhuǎn)回來是可以的,但從一開始就是Object[]的數(shù)組永遠不能轉(zhuǎn)換成Employee[]數(shù)組。
為了解決這個問題,下面提供java.lang.reflect.Array中的API
- static Object get(Object array,int index)
返回對象數(shù)組某個位置上的元素 - static xxx getXxx(Object array,int index)
(xxx是基本數(shù)據(jù)類型)返回基本類型數(shù)組某個位置上的值 - static void set(Object array,int index,Object newValue)
設(shè)置對象數(shù)組某個位置上的元素 - static void setXxx(Object array,int index,xxx newValue)
(xxx是基本數(shù)據(jù)類型)設(shè)置基本類型數(shù)組某個位置上的值 - static int getLength(Object array)
返回數(shù)組的長度 - static Object newInstance(Class componentType,int length)
- static Object newInstance(Class componentType,int[] length)
返回一個具有給定類型、給定維數(shù)的新數(shù)組
下面給出正確的代碼實現(xiàn):
public static Object goodCopyOf(Object a,int newLength){
{
Class c1 = a.getClass();//獲取a數(shù)組的類對象
if(!c1.isArray()) return null;//確認是一個數(shù)組
Class componentType = c1.getComponentType();
//獲取數(shù)組類型
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType,newLength);
System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;
}
這個CopyOf方法可以擴展任意類型的數(shù)組,不僅僅是對象數(shù)組使用示例:
int[] a = {1,2,3,4,5};
a = (int[]) goodCopyOf(a,10);
為了實現(xiàn)上述操作,應(yīng)該將goodCopyOf的參數(shù)聲明為Object類型,而不要聲明為Object[],因為整數(shù)數(shù)組類型可以被轉(zhuǎn)換為Object,但不能轉(zhuǎn)換成Object[]類型
7.調(diào)用任意方法
通過反射還可以調(diào)用任意方法,這是通過Method類的invoke方法實現(xiàn)的,方法簽名是:Object invoke(Object obj,Object... args),Object obj表示調(diào)用方法的對象,Object...args表示方法的參數(shù)列表。
如果方法是靜態(tài)方法,將第一個參數(shù)設(shè)置為null;如果方法是非靜態(tài)無參方法,第二個參數(shù)列表可以忽略。
例如:String n = (String)m1.invoke(harry);(m1表示Employee類的getName方法)。如果方法m1的返回值是void,則invoke方法返回null,否則返回具體類型。如果返回值是基本類型,invoke方法會返回其包裝器類型,可以利用自動開箱將其還原為基本數(shù)據(jù)類型。例如:double s = (Double)m2.invoke(harry);(m2表示Employee類的getSalary方法)
getMethods方法和getDeclaredMethods會返回一個Method對象列表,如果要得到特定的Method對象,可以調(diào)用Class類的getMethod方法,其簽名是Method getMethod(String Methodname,Class...parameterTypes)。
例如:
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary",double.class);
下面給出一個調(diào)用任意方法打印函數(shù)表的程序(以自定義的square和Math.sqrt方法為例):
import java.lang.reflect.*;
public class MethodTableTest
{
public static void main(String[] args)
{
Method square = MethodTableTest.class.getMethod("square",double.class);
Method sqrt = Math.class.getMethod("sqrt",double.class);
printTable(1,10,10,square);
printTable(1,10,10,sqrt);
}
public static double square(double x)
{
return x * x;
}
public static void printTable(double from,double to,int n,Method f)
{
System.out.println(f);
double dx = (to - from) / (n - 1);
for(double x = from;x <= to;x += dx)
{
try
{
double y = (Double)f.invoke(null,x);
System.out.printf("%10.4f | %10/4f\n",x,y);
}
catch(Exception e)
{
e.printStackTrace();
}
}
invoke方法如果提供了錯誤的參數(shù),會拋出一個異常,所以要提供一個異常處理器
建議在有必要的時候才使用invoke方法,有如下原因:
1.invoke方法的參數(shù)和返回值必須是Object類型,意味著必須進行多次類型轉(zhuǎn)換,這樣會使編譯器錯過檢查代碼的機會,等到測試階段才發(fā)現(xiàn)錯誤,找到并改正會更加困難
2.通過反射調(diào)用方法比直接調(diào)用方法要明顯慢一些
特別重申:建議Java開發(fā)者不要使用Method對象的回調(diào)功能,使用接口進行回調(diào)會使代碼的執(zhí)行速度更快,更易于維護。
8.通過反射了解泛型本質(zhì)
來看下面一段代碼:
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2); //true
/*反射的操作都是編譯之后的操作,編譯之后會發(fā)生類型擦除,即ArrayList<String>被擦除為ArrayList,所以c1 == c2結(jié)果為true*/
Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,編譯之后就會發(fā)生類型擦除,所以繞過編譯泛型就無效了
驗證:我們可以通過方法的反射來操作,繞過編譯
try {
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20);//繞過編譯操作就繞過了泛型
System.out.println(list1.size());
System.out.println(list1);
} catch (Exception e) {
e.printStackTrace();
}
推薦博客: