天天看點

java的class類

除了​

​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;
    }
}