什么是反射谴古?
反射就是Reflection
,Java反射是指程序運行期間可以拿到一個對象的一切信息。
正常情況下,如果我們要調(diào)用一個對象的方法经磅,或者訪問一個對象的字段,通常會傳入對象實例:
// Main.java
import com.itranswarp.learnjava.Person;
public class Main {
String getFullName(Person p) {
return p.getFirstName() + " " + p.getLastName();
}
}
jdk編譯后生成 String.class文件钮追,
編譯Java源碼生成“.class”文件里面實際是字節(jié)碼预厌,而不是可以直接執(zhí)行的機器碼。在運行時元媚,JVM會通過類加載器加載字節(jié)碼轧叽,解釋或者編譯執(zhí)行.
而class
是由JVM在執(zhí)行過程中動態(tài)加載的。JVM在第一次讀取到一種class類型時刊棕,將其加載進(jìn)內(nèi)存炭晒。
每加載一種class
,JVM就為其創(chuàng)建一個Class
類型的實例甥角,并關(guān)聯(lián)起來网严。注意:這里的Class
類型是一個名叫Class
的class。它長這樣:
public final class Class {
private Class() {}
}
// java.lang包下:
package java.lang;
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement {
private Class(ClassLoader var1) {
this.classLoader = var1;
}
以String
類為例嗤无,當(dāng)JVM加載String類時屿笼,它
首先讀取String.class
文件到內(nèi)存,
然后翁巍,為String類創(chuàng)建一個Class
實例并關(guān)聯(lián)起來:
Class cls = new Class(String);
這個Class實例
是JVM內(nèi)部創(chuàng)建的驴一,如果我們查看JDK源碼,可以發(fā)現(xiàn)Class類的構(gòu)造方法是private灶壶,只有JVM能創(chuàng)建Class實例肝断,我們自己的Java程序是無法創(chuàng)建Class實例的。
所以驰凛,JVM持有的每個Class實例
都指向一個數(shù)據(jù)類型(class或interface):
┌────────────────┐
│ Class Instance │──────> String
├───────────── ┤
│name = "java.lang.String" │
└──────────────── ┘
┌─────────────────┐
│ Class Instance │──────> Random
├─────────────────┤
│name = "java.util.Random" │
└─────────────────┘
┌──────────────────┐
│ Class Instance │──────> Runnable
├──────────────────┤
│name = "java.lang.Runnable"│
└──────────────────┘
一個Class實例
包含了該class的所有完整信息:
┌──────────────────┐
│ Class Instance │──────> String
├─────────────────┤
│name = "java.lang.String" │
├──────────────────┤
│package = "java.lang" │
├──────────────────┤
│super = "java.lang.Object" │
├──────────────────┤
│interface = CharSequence... │
├──────────────────┤
│field = value[],hash,... │
├─────────────────┤
│method = indexOf()... │
└──────────────────┘
由于JVM為每個加載的class創(chuàng)建了對應(yīng)的Class實例
胸懈,并在實例中保存了該class的所有信息,包括類名恰响、包名趣钱、父類、實現(xiàn)的接口胚宦、所有方法首有、字段等燕垃,因此,如果獲取了某個Class實例
井联,我們就可以通過這個Class實例
獲取到該實例對應(yīng)的class
的所有信息卜壕。
這種通過Class實例
獲取class
信息的方法稱為反射(Reflection)。
如何獲取一個class的Class實例烙常?有三個方法:
1轴捎、靜態(tài)方法 XXX.class
直接通過一個class的靜態(tài)變量class獲取,這個方法有點像對象訪問屬性的方法,可以直接.
獲取Class實例蚕脏。
Class class1 = String.class;
2侦副、getClass()
方法
如果我們有一個實例變量,可以通過該實例變量提供的getClass()
方法獲韧毡蕖:
String s = "Hello";
Class cls = s.getClass();
3跃洛、Class.forName("XXX")
獲取
方法三:如果知道一個class
的完整類名,可以通過靜態(tài)方法Class.forName()
獲戎找椤:
Class cls = Class.forName("java.lang.String");
因為Class實例在JVM中是唯一的,所以葱蝗,上述方法獲取的Class實例是同一個實例穴张。可以用==比較兩個Class實例:
以上三種方法指向的都是同一個class两曼,所以可以用==來比較皂甘,只是可以精確地判斷數(shù)據(jù)類型,但不能作子類型比較悼凑。JVM在執(zhí)行Java程序的時候偿枕,并不是一次性把所有用到的class全部加載到內(nèi)存户辫,而是動態(tài)加載,在運行期根據(jù)條件加載不同的實現(xiàn)類墓塌。
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
boolean sameClass = cls1 == cls2; // true
注意一下Class實例比較和instanceof的差別:
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true奥额,因為n是Integer類型
boolean b2 = n instanceof Number; // true,因為n是Number類型的子類
boolean b3 = n.getClass() == Integer.class; // true垫挨,因為n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因為Integer.class!=Number.class
public final class Integer extends Number implements Comparable<Integer> {
用instanceof
不但匹配指定類型九榔,還匹配指定類型的子類涡相。而用==
判斷class實例
可以精確地判斷數(shù)據(jù)類型谜诫,但不能作子類型比較喻旷。
通常情況下,我們應(yīng)該用instanceof
判斷數(shù)據(jù)類型且预,因為面向抽象編程的時候,我們不關(guān)心具體的子類型遍尺。只有在需要精確判斷一個類型是不是某個class
的時候涮拗,我們才使用==
判斷class實例三热。
因為反射的目的是為了獲得某個實例的信息。因此呐能,當(dāng)我們拿到某個Object實例時抑堡,我們可以通過反射獲取該Object的class信息:
void printObjectInfo(Object obj) {
Class cls = obj.getClass();
}
要從Class實例
獲取獲取的基本信息,參考下面的代碼:
public class Main {
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
}
注意到數(shù)組(例如String[]
)也是一種Class
偎漫,而且不同于String.class有缆,它的類名是[Ljava.lang.String。此外通危,JVM為每一種基本類型如int也創(chuàng)建了Class灌曙,通過int.class訪問。
如果獲取到了一個Class實例在刺,我們就可以通過該Class實例來創(chuàng)建對應(yīng)類型的實例:
// 獲取String的Class實例:
Class cls = String.class;
// 創(chuàng)建一個String實例:
String s = (String) cls.newInstance();
上述代碼相當(dāng)于new String()
。通過Class.newInstance()可以創(chuàng)建類實例魄幕,它的局限是:只能調(diào)用public的無參數(shù)構(gòu)造方法。帶參數(shù)的構(gòu)造方法坛芽,或者非public的構(gòu)造方法都無法通過Class.newInstance()
被調(diào)用翼抠。
動態(tài)加載
JVM在執(zhí)行Java程序的時候,并不是一次性把所有用到的class全部加載到內(nèi)存活喊,而是第一次需要用到class時才加載量愧。例如:
// Main.java
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}
static void create(String name) {
Person p = new Person(name);
}
}
當(dāng)執(zhí)行Main.java
時,由于用到了Main
煞烫,因此软棺,JVM首先會把Main.class
加載到內(nèi)存尤勋。然而,并不會加載Person.class
瘦棋,除非程序執(zhí)行到create()
方法,JVM發(fā)現(xiàn)需要加載Person
類時赌朋,才會首次加載Person.class
篇裁。如果沒有執(zhí)行create()方法,那么Person.class根本就不會被加載达布。
這就是JVM動態(tài)加載class的特性黍聂。
動態(tài)加載class的特性對于Java程序非常重要身腻。利用JVM動態(tài)加載class的特性匹厘,我們才能在運行期根據(jù)條件加載不同的實現(xiàn)類。例如愈诚,Commons Logging總是優(yōu)先使用Log4j,只有當(dāng)Log4j不存在時尤溜,才使用JDK的logging汗唱。利用JVM動態(tài)加載特性,大致的實現(xiàn)代碼如下:
// Commons Logging優(yōu)先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
factory = createLog4j();
} else {
factory = createJdkLog();
}
boolean isClassPresent(String name) {
try {
Class.forName(name);
return true;
} catch (Exception e) {
return false;
}
}
這就是為什么我們只需要把Log4j的jar包放到classpath中授霸,Commons Logging
就會自動使用Log4j的原因际插。
小結(jié)
JVM為每個加載的class
及interface
創(chuàng)建了對應(yīng)的Class實例
來保存class及interface的所有信息;
獲取一個class對應(yīng)的Class實例后辛辨,就可以獲取該class的所有信息瑟枫;
通過Class實例獲取class信息的方法稱為反射(Reflection);
JVM總是動態(tài)加載class僻焚,可以在運行期根據(jù)條件來控制加載class膝擂。
訪問字段
對任意的一個Object
實例,只要我們獲取了它的Class狞山,就可以獲取它的一切信息叉寂。
我們先看看如何通過Class實例獲取字段信息。Class類提供了以下幾個方法來獲取字段:
-
Field getField(name)
:根據(jù)字段名獲取某個public的field(包括父類) -
Field getDeclaredField(name)
:根據(jù)字段名獲取當(dāng)前類的某個field(不包括父類) -
Field[] getFields()
:獲取所有public的field(包括父類) -
Field[] getDeclaredFields()
:獲取當(dāng)前類的所有field(不包括父類)
我們來看一下示例代碼:
XXX.class
類加載器將.class文件加載到內(nèi)存伊约,代碼區(qū)
Code Segment
public <A extends Annotation> A[] getAnnotationsByType(Class<A> var1) {
java中有如下幾種類加載器:
BootstrapClassLoader 最核心的屡律,load其他的classLoader, 其他的classloader再load其他的Class
- implemented by native language (C/C++/匯編。區(qū)別于JRE版本搏讶。其他loader都是java寫的)(rt.java,沒有名字)
- load the core public <A extends Annotation> A[] getAnnotationsByType(Class<A> var1) {
classes of JDK
ExtesionClassLoader 負(fù)責(zé)JDK的擴展類
loader the class from jre/lib/ext
ApplicationClassLoaderload user-define classes
ClassLoader.getSystemClassLoader() == application class loader
other class loadersSecureClassLoader
URLClassLoader (2/3的父類)
user-define ClassLoader
當(dāng)前裝在這個類的classLoader
parent不是指繼承霍殴,是指那個classLoader引用
jdk classLoader被appClassLoader,找上一層的妒蔚,如果上一層加載了肴盏,就不會再加載帽衙,
安全性好!
如果自己寫java.lang.String, 破壞性強厉萝,但因為上一層加載谴垫,就不會加載手寫的這個String.