反射之初認識
除了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)。
如何獲取一個class
的Class
例項?有三個方法:
方法一:直接通過一個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
例項時,我們可以通過反射獲取該Object
的class
資訊:
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為每個載入的class
及interface
建立了對應的Class
例項來儲存class
及interface
的所有資訊;
2.獲取一個class
對應的Class
例項後,就可以獲取該class
的所有資訊;
3.通過Class例項獲取class
資訊的方法稱為反射(Reflection);
4.JVM總是動態載入class
,可以在執行期根據條件來控制載入class。