1. 程式人生 > 實用技巧 >反射之初認識

反射之初認識

除了int等基本型別外,Java的其他型別全部都是class(包括interface)。
仔細思考,我們可以得出結論:class(包括interface)的本質是資料型別(Type)。
而class是由JVM在執行過程中動態載入的。JVM在第一次讀取到一種class型別時,將其載入進記憶體。
每載入一種class,JVM就為其建立一個Class型別的例項,並關聯起來。注意:這裡的Class型別是一個名叫Class的class

public final class Class {
    private Class() {}
}

以String類為例,當JVM載入String類時,它首先讀取String.class檔案到記憶體,然後,為String類建立一個Class例項並關聯起來:
Class cls = new Class(String);

這個Class例項是JVM內部建立的,如果我們檢視JDK原始碼,可以發現Class類的構造方法是private,只有JVM能建立Class例項,我們自己的Java程式是無法建立Class例項的
所以,JVM持有的每個Class例項都指向一個數據型別(class或interface)

由於JVM為每個載入的class建立了對應的Class例項,並在例項中儲存了該class的所有資訊,包括類名、包名、父類、實現的介面、所有方法、欄位等,因此,如果獲取了某個Class例項,我們就可以通過這個Class例項獲取到該例項對應的class的所有資訊。

這種通過Class例項獲取class資訊的方法稱為反射(Reflection)。

如何獲取一個classClass例項?有三個方法:

方法一:直接通過一個class的靜態變數class獲取:

Class cls = String.class

方法二:如果我們有一個例項變數,可以通過該例項變數提供的getClass()方法獲取:

String s = "Hello";
Class cls = s.getClass();

方法三:如果知道一個class的完整類名,可以通過靜態方法Class.forName()獲取:

Class cls = Class.forName(java.lang.String);

因為Class例項在JVM中是唯一的,所以,上述方法獲取的Class

例項是同一個例項。可以用==比較兩個Class例項:

Class cls1 = String.class;
String s = "hello";
Class cls2 = s.getClass();
System.out.println(cls1 == cls2); // true

因為反射的目的是為了獲得某個例項的資訊。因此,當我們拿到某個Object例項時,我們可以通過反射獲取該Objectclass資訊:

 1 public class Demo{
 2     public static void main(String[] args) {
 3         printObjectInfo("wang");
 4     }
 5     
 6     public static void printObjectInfo(Object object){
 7         Class cls = object.getClass();
 8         System.out.println(cls);  //class java.lang.String
 9     }
10 }

要從Class例項獲取某個class的基本資訊

 1 public class Demo12{
 2     public static void main(String[] args) {
 3         //字串例項
 4         printClassInfo("".getClass());
 5         System.out.println("****************");
 6         //Runnable介面
 7         printClassInfo(Runnable.class);
 8         System.out.println("****************");
 9         printClassInfo(java.time.Month.class);
10         System.out.println("****************");
11         printClassInfo(int[].class);
12         System.out.println("****************");
13         printClassInfo(long.class);
14     }
15     
16     static void printClassInfo(Class cls){
17         //class全限定類名
18         System.out.println("Class name:" + cls.getName());
19         //class類名
20         System.out.println("Simple name:" + cls.getSimpleName());
21         //包名
22         if(cls.getPackage() != null){
23             System.out.println("Package name:" + cls.getPackage().getName());
24         }
25         //判斷該類是否是介面
26         System.out.println("is interface:" + cls.isInterface());
27         //判斷該類是否是列舉
28         System.out.println("is enum:" + cls.isEnum());
29         //判斷該類是否是陣列
30         System.out.println("is array:" + cls.isArray());
31         //判斷該類是否是基本型別
32         System.out.println("is primitive:" + cls.isPrimitive());
33     }
34 }

如果獲取到了一個Class例項,我們就可以通過該Class例項來建立對應型別的例項

//獲取String的Class物件
Class cls = String.class;
//建立一個String物件
String s = (String)cls.newInstance();

上述程式碼相當於new String()。通過Class.newInstance()可以建立類例項,

它的侷限是:只能呼叫public的無引數構造方法。帶引數的構造方法,或者非public的構造方法都無法通過Class.newInstance()被呼叫

動態載入

JVM在執行Java程式的時候,並不是一次性把所有用到的class全部載入到記憶體,而是第一次需要用到class時才載入。

 1 public class Demo{
 3     public static void main(String[] args){
 4         if(args.length > 0){
 5             create(args[0]);
 6         }
 7     }
 8     
 9     static void create(String name){
10         Person p = new Person(name);
11     }
12     
13 }

當執行Demo.java時,由於用到了Demo,因此,JVM首先會把Demo.class載入到記憶體。然而,並不會載入Person.class,除非程式執行到create()方法,JVM發現需要載入Person類時,才會首次載入Person.class。如果沒有執行create()方法,那麼Person.class根本就不會被載入

這就是JVM動態載入class的特性

動態載入class的特性對於Java程式非常重要。利用JVM動態載入class的特性,我們才能在執行期根據條件載入不同的實現類。例如,Commons Logging總是優先使用Log4j,只有當Log4j不存在時,才使用JDK的logging.利用JVM動態載入特性,大致的實現程式碼如下:

 1 //Commons logging優先使用Log4j
 2     LogFactory factory =  null;
 3     if(isClassPresent("org.apache.logging.log4j.Logger")){
 4         factory = createLog4j();
 5     }else{
 6         factory = createJdkLog();
 7     }
 8     
 9     boolean isClassPresent(String name){
10         try{
11             Class.forName(name);
12             return true;
13         }catch(Exception e){
14             return false;
15         }
16         
17     }

這就是為什麼我們只需要吧Log4j的jar包放到classpath中,Commons Logging就會自動使用Log4j的原因

小結

1.JVM為每個載入的classinterface建立了對應的Class例項來儲存classinterface的所有資訊;

2.獲取一個class對應的Class例項後,就可以獲取該class的所有資訊;

3.通過Class例項獲取class資訊的方法稱為反射(Reflection);

4.JVM總是動態載入class,可以在執行期根據條件來控制載入class。