除了
int
等基本類型外,Java的其他類型全部都是
class
(包括
interface
)。例如:
-
String
-
Object
-
Runnable
-
Exception
- ...
仔細思考,我們可以得出結論:
class
(包括
interface
)的本質是資料類型(
Type
)。無繼承關系的資料類型無法指派:
Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!
而
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
):
┌───────────────────────────┐
│ 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
建立了對應的
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();
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
用
instanceof
不但比對指定類型,還比對指定類型的子類。而用
==
判斷
class
執行個體可以精确地判斷資料類型,但不能作子類型比較。
通常情況下,我們應該用
instanceof
判斷資料類型,因為面向抽象程式設計的時候,我們不關心具體的子類型。隻有在需要精确判斷一個類型是不是某個
class
的時候,我們才使用
==
判斷
class
執行個體。
因為反射的目的是為了獲得某個執行個體的資訊。是以,當我們拿到某個
Object
執行個體時,我們可以通過反射擷取該
Object
的
class
資訊:
void printObjectInfo(Object obj) {
Class cls = obj.getClass();
}
要從
Class
執行個體擷取擷取的基本資訊,參考下面的代碼:
// reflection
Run
注意到數組(例如
String[]
)也是一種類,而且不同于
String.class
,它的類名是
[Ljava.lang.String;
。此外,JVM為每一種基本類型如
int
也建立了
Class
執行個體,通過
int.class
通路。
如果擷取到了一個
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時才加載。例如:
// 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);
}
}
// Commons Logging優先使用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;
}
}