java基礎(chǔ):深入理解Class對(duì)象與反射機(jī)制

其他更多java基礎(chǔ)文章:
java基礎(chǔ)學(xué)習(xí)(目錄)


深入理解Class對(duì)象

RRIT及Class對(duì)象的概念

RRIT(Run-Time Type Identification)運(yùn)行時(shí)類(lèi)型識(shí)別。在《Thinking in Java》一書(shū)第十四章中有提到,其作用是在運(yùn)行時(shí)識(shí)別一個(gè)對(duì)象的類(lèi)型和類(lèi)的信息。主要有兩種方式:一種是“傳統(tǒng)的”RTTI,它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類(lèi)型;另一種是“反射”機(jī)制,它允許我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)和使用類(lèi)的信息。

類(lèi)是程序的一部分,每個(gè)類(lèi)都有一個(gè)class對(duì)象。換言之,每當(dāng)編寫(xiě)并且編譯了一個(gè)新類(lèi),就會(huì)產(chǎn)生一個(gè)Class對(duì)象(更恰當(dāng)?shù)卣f(shuō),是被保存在一個(gè)同名的.class文件中)。所有的類(lèi)都是在對(duì)其第一次使用時(shí),動(dòng)態(tài)加載到JVM中的。例如我們寫(xiě)了一個(gè)Test類(lèi),編譯后生成了Test.class,此時(shí)我們的Test類(lèi)的Class對(duì)象就保存在class文件中。當(dāng)我們new一個(gè)新對(duì)象或者引用靜態(tài)成員變量時(shí),Java虛擬機(jī)(JVM)中的類(lèi)加載器子系統(tǒng)會(huì)將對(duì)應(yīng)Class對(duì)象加載到JVM中,然后JVM再根據(jù)這個(gè)類(lèi)型信息相關(guān)的Class對(duì)象創(chuàng)建我們需要實(shí)例對(duì)象或者提供靜態(tài)變量的引用值。需要特別注意的是,手動(dòng)編寫(xiě)的每個(gè)class類(lèi),無(wú)論創(chuàng)建多少個(gè)實(shí)例對(duì)象,在JVM中都只有一個(gè)Class對(duì)象,即在內(nèi)存中每個(gè)類(lèi)有且只有一個(gè)相對(duì)應(yīng)的Class對(duì)象。

Test t1 = new Test();
Test t2 = new Test();
Test t3 = new Test();

如上所示,實(shí)際上JVM內(nèi)存中只存有一個(gè)Test的Class對(duì)象。

Class類(lèi),Class類(lèi)也是一個(gè)實(shí)實(shí)在在的類(lèi),存在于JDK的java.lang包中。Class類(lèi)的實(shí)例表示java應(yīng)用運(yùn)行時(shí)的類(lèi)(class ans enum)或接口(interface and annotation)(每個(gè)java類(lèi)運(yùn)行時(shí)都在JVM里表現(xiàn)為一個(gè)class對(duì)象,可通過(guò)類(lèi)名.class、類(lèi)型.getClass()、Class.forName("類(lèi)名")等方法獲取class對(duì)象)。數(shù)組同樣也被映射為為class 對(duì)象的一個(gè)類(lèi),所有具有相同元素類(lèi)型和維數(shù)的數(shù)組都共享該 Class 對(duì)象。基本類(lèi)型boolean,byte,char,short,int,long,float,double和關(guān)鍵字void同樣表現(xiàn)為 class 對(duì)象。

ublic final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.   //私有構(gòu)造器,只有JVM才能調(diào)用創(chuàng)建Class對(duì)象
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

到這我們也就可以得出以下幾點(diǎn)信息:

  • Class類(lèi)也是類(lèi)的一種,與class關(guān)鍵字是不一樣的。

  • 手動(dòng)編寫(xiě)的類(lèi)被編譯后會(huì)產(chǎn)生一個(gè)Class對(duì)象,其表示的是創(chuàng)建的類(lèi)的類(lèi)型信息,而且這個(gè)Class對(duì)象保存在同名.class的文件中(字節(jié)碼文件)

  • 每個(gè)通過(guò)關(guān)鍵字class標(biāo)識(shí)的類(lèi),在內(nèi)存中有且只有一個(gè)與之對(duì)應(yīng)的Class對(duì)象來(lái)描述其類(lèi)型信息,無(wú)論創(chuàng)建多少個(gè)實(shí)例對(duì)象,其依據(jù)的都是用一個(gè)Class對(duì)象。

  • Class類(lèi)只存私有構(gòu)造函數(shù),因此對(duì)應(yīng)Class對(duì)象只能有JVM創(chuàng)建和加載

  • Class類(lèi)的對(duì)象作用是運(yùn)行時(shí)提供或獲得某個(gè)對(duì)象的類(lèi)型信息,這點(diǎn)對(duì)于反射技術(shù)很重要(關(guān)于反射稍后分析)。

Class對(duì)象的加載及獲取

Class對(duì)象的加載

前面我們已提到過(guò),Class對(duì)象是由JVM加載的,那么其加載時(shí)機(jī)是?實(shí)際上所有的類(lèi)都是在對(duì)其第一次使用時(shí)動(dòng)態(tài)加載到JVM中的,當(dāng)程序創(chuàng)建第一個(gè)對(duì)類(lèi)的靜態(tài)成員引用時(shí),就會(huì)加載這個(gè)被使用的類(lèi)(實(shí)際上加載的就是這個(gè)類(lèi)的字節(jié)碼文件),注意,使用new操作符創(chuàng)建類(lèi)的新實(shí)例對(duì)象也會(huì)被當(dāng)作對(duì)類(lèi)的靜態(tài)成員的引用(構(gòu)造函數(shù)也是類(lèi)的靜態(tài)方法),由此看來(lái)Java程序在它們開(kāi)始運(yùn)行之前并非被完全加載到內(nèi)存的,其各個(gè)部分是按需加載,所以在使用該類(lèi)時(shí),類(lèi)加載器首先會(huì)檢查這個(gè)類(lèi)的Class對(duì)象是否已被加載(類(lèi)的實(shí)例對(duì)象創(chuàng)建時(shí)依據(jù)Class對(duì)象中類(lèi)型信息完成的),如果還沒(méi)有加載,默認(rèn)的類(lèi)加載器就會(huì)先根據(jù)類(lèi)名查找.class文件(編譯后Class對(duì)象被保存在同名的.class文件中),在這個(gè)類(lèi)的字節(jié)碼文件被加載時(shí),它們必須接受相關(guān)驗(yàn)證,以確保其沒(méi)有被破壞并且不包含不良Java代碼(這是java的安全機(jī)制檢測(cè)),完全沒(méi)有問(wèn)題后就會(huì)被動(dòng)態(tài)加載到內(nèi)存中,此時(shí)相當(dāng)于Class對(duì)象也就被載入內(nèi)存了(畢竟.class字節(jié)碼文件保存的就是Class對(duì)象),同時(shí)也就可以被用來(lái)創(chuàng)建這個(gè)類(lèi)的所有實(shí)例對(duì)象。


類(lèi)加載的過(guò)程 :
1. 加載
在加載階段,虛擬機(jī)需要完成3件事:
(1)通過(guò)一個(gè)類(lèi)的全限定名(org/fenixsoft/clazz/TestClass)獲取定義此類(lèi)的二進(jìn)制字節(jié)流(.class文件);
(2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
(3)在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類(lèi)的各種數(shù)據(jù)的訪問(wèn)入口;
2. 驗(yàn)證
驗(yàn)證階段是非常重要的,這個(gè)階段是否嚴(yán)謹(jǐn),直接決定了Java虛擬機(jī)是否能承受惡意代碼的攻擊,從執(zhí)行性能的角度上講,驗(yàn)證階段的工作量在虛擬機(jī)的類(lèi)加載子系統(tǒng)中又占了相當(dāng)大的一部分。驗(yàn)證階段大致上完成下面4個(gè)階段的驗(yàn)證動(dòng)作:
(1)文件格式驗(yàn)證
驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理;
這階段的驗(yàn)證是基于二進(jìn)制字節(jié)流進(jìn)行的,只有通過(guò)了這個(gè)階段的驗(yàn)證,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)進(jìn)行儲(chǔ)存,所以后面的3個(gè)驗(yàn)證階段全部是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的,不會(huì)再直接操作字節(jié)流。
(2)元數(shù)據(jù)驗(yàn)證
對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求,保證不存在不符合Java語(yǔ)言規(guī)范的元數(shù)據(jù)信息;
(3)字節(jié)碼驗(yàn)證
通過(guò)數(shù)據(jù)流和控制流分析,確定程序是語(yǔ)義是合法的、符合邏輯的,保證被校驗(yàn)的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的事件;
(4)符號(hào)引用驗(yàn)證
可以看作是對(duì)類(lèi)自身以外(常量池中各種符號(hào)引用)的信息進(jìn)行匹配性校驗(yàn),確保解析動(dòng)作能正常執(zhí)行;
3. 準(zhǔn)備
準(zhǔn)備階段是正式為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始值階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。這里進(jìn)行內(nèi)存分配僅僅是類(lèi)變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中;
4. 解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。解析動(dòng)作主要針對(duì)類(lèi)或接口、字段、類(lèi)方法、方法類(lèi)型、方法句柄和調(diào)用點(diǎn)限定符7類(lèi)符號(hào)引用進(jìn)行;
5. 初始化
初始化階段才真正開(kāi)始執(zhí)行類(lèi)中定義的Java程序代碼(或者說(shuō)是字節(jié)碼)。初始化是如何被觸發(fā)的:
(1)遇到new、getstatic、putstatic或involestatic這4條指令時(shí);
(2)使用 java.lang.reflect 包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候;
(3)初始化一個(gè)類(lèi)時(shí),如果父類(lèi)還沒(méi)被初始化,則先觸發(fā)父類(lèi)的初始化;
(4)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(lèi) (包含main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi);
(5)如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后解析的結(jié)果是 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,若句柄所對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則將它初始化;

上文源自《深入理解java虛擬機(jī)》一書(shū),大家可以去讀一下,這本書(shū)基本上是java程序猿學(xué)習(xí)必讀之一了。在此就不深入展開(kāi)了,因?yàn)檫@又是另一個(gè)JVM領(lǐng)域了。 以后如果寫(xiě)了該方面的文章,會(huì)貼到這里。

Class對(duì)象的獲取

Class對(duì)象的獲取主要有3種:

  • 通過(guò)實(shí)例getClass()方法獲取
  • Class.forName方法獲取
  • 類(lèi)字面常量獲取

通過(guò)實(shí)例getClass()方法獲取

    Test t1 = new Test();
    Class clazz=test.getClass();
    System.out.println("clazz:"+clazz.getName());

getClass()是從頂級(jí)類(lèi)Object繼承而來(lái)的,它將返回表示該對(duì)象的實(shí)際類(lèi)型的Class對(duì)象引用。

Class.forName方法獲取

    try{
      //通過(guò)Class.forName獲取Test類(lèi)的Class對(duì)象
      Class clazz=Class.forName("com.hiway.Test");
      System.out.println("clazz:"+clazz.getName());
    }catch (ClassNotFoundException e){
      e.printStackTrace();
    }

forName方法是Class類(lèi)的一個(gè)static成員方法,記住所有的Class對(duì)象都源于這個(gè)Class類(lèi),因此Class類(lèi)中定義的方法將適應(yīng)所有Class對(duì)象。這里通過(guò)forName方法,我們可以獲取到Test類(lèi)對(duì)應(yīng)的Class對(duì)象引用。
注意調(diào)用forName方法時(shí)需要捕獲一個(gè)名稱為ClassNotFoundException的異常,因?yàn)閒orName方法在編譯器是無(wú)法檢測(cè)到其傳遞的字符串對(duì)應(yīng)的類(lèi)是否存在的(是否有.class文件),只能在程序運(yùn)行時(shí)進(jìn)行檢查,如果不存在就會(huì)拋出ClassNotFoundException異常。

使用forName方式會(huì)觸發(fā)類(lèi)的初始化,與之相比的是使用類(lèi)字面常量獲取

類(lèi)字面常量獲取

//字面常量的方式獲取Class對(duì)象
Class clazz = Test.class;

這樣做不僅更簡(jiǎn)單,而且更安全,因?yàn)樗诰幾g時(shí)就會(huì)受到檢查(因此不需要置于try語(yǔ)句塊中)。并且它根除了對(duì)forName()方法的調(diào)用,所以也更高效。
注意,有一點(diǎn)很有趣,當(dāng)使用“.class”來(lái)創(chuàng)建對(duì)Class對(duì)象的引用時(shí),不會(huì)自動(dòng)地初始化該Class對(duì)象。注意,有一點(diǎn)很有趣,當(dāng)使用“.class”來(lái)創(chuàng)建對(duì)Class對(duì)象的引用時(shí),不會(huì)自動(dòng)地初始化該Class對(duì)象,為了使用類(lèi)而做的準(zhǔn)備工作實(shí)際包含三個(gè)步驟:

  1. 加載,這是由類(lèi)加載器執(zhí)行的,該步驟將查找字節(jié)碼(通常在classpath所指定的路徑中查找,但這并非是必需的),并從這些字節(jié)碼中創(chuàng)建一個(gè)Class對(duì)象。
  2. 鏈接。在鏈接階段將驗(yàn)證類(lèi)中的字節(jié)碼,為靜態(tài)域分布存儲(chǔ)空間,并且如果必需的話,將解析這個(gè)類(lèi)創(chuàng)建的對(duì)其他類(lèi)的所有引用。
  3. 初始化。如果該類(lèi)具有超類(lèi),則對(duì)其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。
class Initable{
     static final int staticFinal = 47;
     static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
     static {
          System.out.ptintln("Initializing Initable");
     }
}

class Initable2 {
     static int staticNonFinal = 147;
     static {
          System.out.println("Initializing Initable2");
     }
}

class Initable3 {
     static int staticNonFinal = 74;
     static {
          System.out.println("Initializing Initable3");
     }
}

public class ClassInitialization {
     public static Random rand = new Random(47);
     public static void main(String[] args) throws Exception {
          Class initable = Initable.class;
          System.out.println("After creating Initable ref");
          System.out.println(Initable.staticFinal);
          System.out.println(Initable.staticFinal2);
          System.out.println(Initable2.staticNonFinal);
          Clas initable3 = Class.forName("Initable3");
          System.out.println("After creating Initable3 ref");
          System.out.println(Initable3.staticNonFinal);
     }
}

/* output
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable ref
74

如果一個(gè)static final值是編譯器常量,就像Initable.staticFinal那樣,那么這個(gè)值不需要對(duì)Initable類(lèi)進(jìn)行初始化就可以被讀取。但是,如果只是將一個(gè)域設(shè)置為static和final的,還不足以確保這種行為,例如,對(duì)Initable.staticFinal2的訪問(wèn)將強(qiáng)制進(jìn)行類(lèi)的初始化,因?yàn)樗皇且粋€(gè)編譯期常量。

如果一個(gè)static域不是final的,那么在對(duì)它訪問(wèn)時(shí),總是要求在它被讀取之前,要先進(jìn)行鏈接(為這個(gè)域分配存儲(chǔ)空間)和初始化(初始化該存儲(chǔ)空間),就像在對(duì)Initable2.staticNonFinal的訪問(wèn)中所看到的那樣。從輸出結(jié)果來(lái)看,可以發(fā)現(xiàn),通過(guò)字面常量獲取方式獲取Initable類(lèi)的Class對(duì)象并沒(méi)有觸發(fā)Initable類(lèi)的初始化,這點(diǎn)也驗(yàn)證了前面的分析,同時(shí)發(fā)現(xiàn)調(diào)用Initable.staticFinal變量時(shí)也沒(méi)有觸發(fā)初始化,這是因?yàn)閟taticFinal屬于編譯期靜態(tài)常量,在編譯階段通過(guò)常量傳播優(yōu)化的方式將Initable類(lèi)的常量staticFinal存儲(chǔ)到了一個(gè)稱為NotInitialization類(lèi)的常量池中,在以后對(duì)Initable類(lèi)常量staticFinal的引用實(shí)際都轉(zhuǎn)化為對(duì)NotInitialization類(lèi)對(duì)自身常量池的引用,所以在編譯期后,對(duì)編譯期常量的引用都將在NotInitialization類(lèi)的常量池獲取,這也就是引用編譯期靜態(tài)常量不會(huì)觸發(fā)Initable類(lèi)初始化的重要原因。但在之后調(diào)用了Initable.staticFinal2變量后就觸發(fā)了Initable類(lèi)的初始化,注意staticFinal2雖然被static和final修飾,但其值在編譯期并不能確定,因此staticFinal2并不是編譯期常量,使用該變量必須先初始化Initable類(lèi)。Initable2和Initable3類(lèi)中都是靜態(tài)成員變量并非編譯期常量,引用都會(huì)觸發(fā)初始化。至于forName方法獲取Class對(duì)象,肯定會(huì)觸發(fā)初始化,這點(diǎn)在前面已分析過(guò)。

instanceof與Class的等價(jià)性

關(guān)于instanceof 關(guān)鍵字,它返回一個(gè)boolean類(lèi)型的值,意在告訴我們對(duì)象是不是某個(gè)特定的類(lèi)型實(shí)例。如下,在強(qiáng)制轉(zhuǎn)換前利用instanceof檢測(cè)obj是不是Animal類(lèi)型的實(shí)例對(duì)象,如果返回true再進(jìn)行類(lèi)型轉(zhuǎn)換,這樣可以避免拋出類(lèi)型轉(zhuǎn)換的異常(ClassCastException)

public void cast2(Object obj){
    if(obj instanceof Animal){
          Animal animal= (Animal) obj;
      }
}

而isInstance方法則是Class類(lèi)中的一個(gè)Native方法,也是用于判斷對(duì)象類(lèi)型的,看個(gè)簡(jiǎn)單例子:

public void cast2(Object obj){
        //instanceof關(guān)鍵字
        if(obj instanceof Animal){
            Animal animal= (Animal) obj;
        }

        //isInstance方法
        if(Animal.class.isInstance(obj)){
            Animal animal= (Animal) obj;
        }
  }

事實(shí)上instanceOf 與isInstance方法產(chǎn)生的結(jié)果是相同的。

class A {}

class B extends A {}

public class C {
  static void test(Object x) {
    print("Testing x of type " + x.getClass());
    print("x instanceof A " + (x instanceof A));
    print("x instanceof B "+ (x instanceof B));
    print("A.isInstance(x) "+ A.class.isInstance(x));
    print("B.isInstance(x) " +
      B.class.isInstance(x));
    print("x.getClass() == A.class " +
      (x.getClass() == A.class));
    print("x.getClass() == B.class " +
      (x.getClass() == B.class));
    print("x.getClass().equals(A.class)) "+
      (x.getClass().equals(A.class)));
    print("x.getClass().equals(B.class)) " +
      (x.getClass().equals(B.class)));
  }
  public static void main(String[] args) {
    test(new A());
    test(new B());
  } 
}

/* output
Testing x of type class com.zejian.A
x instanceof A true
x instanceof B false //父類(lèi)不一定是子類(lèi)的某個(gè)類(lèi)型
A.isInstance(x) true
B.isInstance(x) false
x.getClass() == A.class true
x.getClass() == B.class false
x.getClass().equals(A.class)) true
x.getClass().equals(B.class)) false
---------------------------------------------
Testing x of type class com.zejian.B
x instanceof A true
x instanceof B true
A.isInstance(x) true
B.isInstance(x) true
x.getClass() == A.class false
x.getClass() == B.class true
x.getClass().equals(A.class)) false
x.getClass().equals(B.class)) true

反射

反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類(lèi),都能夠知道這個(gè)類(lèi)的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性,這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制。一直以來(lái)反射技術(shù)都是Java中的閃亮點(diǎn),這也是目前大部分框架(如Spring/Mybatis等)得以實(shí)現(xiàn)的支柱。在Java中,Class類(lèi)與java.lang.reflect類(lèi)庫(kù)一起對(duì)反射技術(shù)進(jìn)行了全力的支持。在反射包中,我們常用的類(lèi)主要有Constructor類(lèi)表示的是Class 對(duì)象所表示的類(lèi)的構(gòu)造方法,利用它可以在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建對(duì)象、Field表示Class對(duì)象所表示的類(lèi)的成員變量,通過(guò)它可以在運(yùn)行時(shí)動(dòng)態(tài)修改成員變量的屬性值(包含private)、Method表示Class對(duì)象所表示的類(lèi)的成員方法,通過(guò)它可以動(dòng)態(tài)調(diào)用對(duì)象的方法(包含private),下面將對(duì)這幾個(gè)重要類(lèi)進(jìn)行分別說(shuō)明。

Constructor類(lèi)及其用法

Constructor類(lèi)存在于反射包(java.lang.reflect)中,反映的是Class 對(duì)象所表示的類(lèi)的構(gòu)造方法。獲取Constructor對(duì)象是通過(guò)Class類(lèi)中的方法獲取的,Class類(lèi)與Constructor相關(guān)的主要方法如下:

方法返回值 方法名稱 方法說(shuō)明
static Class<?> forName(String className) 返回與帶有給定字符串名的類(lèi)或接口相關(guān)聯(lián)的 Class 對(duì)象。
Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定參數(shù)類(lèi)型、具有public訪問(wèn)權(quán)限的構(gòu)造函數(shù)對(duì)象
Constructor<?>[] getConstructors() 返回所有具有public訪問(wèn)權(quán)限的構(gòu)造函數(shù)的Constructor對(duì)象數(shù)組
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回指定參數(shù)類(lèi)型、所有聲明的(包括private)構(gòu)造函數(shù)對(duì)象
Constructor<?>[] getDeclaredConstructor() 返回所有聲明的(包括private)構(gòu)造函數(shù)對(duì)象
T newInstance() 調(diào)用無(wú)參構(gòu)造器創(chuàng)建此 Class 對(duì)象所表示的類(lèi)的一個(gè)新實(shí)例。

下面看一個(gè)簡(jiǎn)單例子來(lái)了解Constructor對(duì)象的使用:

public class ConstructionTest implements Serializable {
    public static void main(String[] args) throws Exception {

        Class<?> clazz = null;

        //獲取Class對(duì)象的引用
        clazz = Class.forName("com.example.javabase.User");

        //第一種方法,實(shí)例化默認(rèn)構(gòu)造方法,User必須無(wú)參構(gòu)造函數(shù),否則將拋異常
        User user = (User) clazz.newInstance();
        user.setAge(20);
        user.setName("Jack");
        System.out.println(user);

        System.out.println("--------------------------------------------");

        //獲取帶String參數(shù)的public構(gòu)造函數(shù)
        Constructor cs1 =clazz.getConstructor(String.class);
        //創(chuàng)建User
        User user1= (User) cs1.newInstance("hiway");
        user1.setAge(22);
        System.out.println("user1:"+user1.toString());

        System.out.println("--------------------------------------------");

        //取得指定帶int和String參數(shù)構(gòu)造函數(shù),該方法是私有構(gòu)造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必須設(shè)置可訪問(wèn)
        cs2.setAccessible(true);
        //創(chuàng)建user對(duì)象
        User user2= (User) cs2.newInstance(25,"hiway2");
        System.out.println("user2:"+user2.toString());

        System.out.println("--------------------------------------------");

        //獲取所有構(gòu)造包含private
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        // 查看每個(gè)構(gòu)造方法需要的參數(shù)
        for (int i = 0; i < cons.length; i++) {
            //獲取構(gòu)造函數(shù)參數(shù)類(lèi)型
            Class<?> clazzs[] = cons[i].getParameterTypes();
            System.out.println("構(gòu)造函數(shù)["+i+"]:"+cons[i].toString() );
            System.out.print("參數(shù)類(lèi)型["+i+"]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    System.out.print(clazzs[j].getName());
                else
                    System.out.print(clazzs[j].getName() + ",");
            }
            System.out.println(")");
        }
    }
}


class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }

    /**
     * 私有構(gòu)造
     * @param age
     * @param name
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

/* output 
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
構(gòu)造函數(shù)[0]:private com.example.javabase.User(int,java.lang.String)
參數(shù)類(lèi)型[0]:(int,java.lang.String)
構(gòu)造函數(shù)[1]:public com.example.javabase.User(java.lang.String)
參數(shù)類(lèi)型[1]:(java.lang.String)
構(gòu)造函數(shù)[2]:public com.example.javabase.User()
參數(shù)類(lèi)型[2]:()

關(guān)于Constructor類(lèi)本身一些常用方法如下(僅部分,其他可查API)

方法返回值 方法名稱 方法說(shuō)明
Class<T> getDeclaringClass() 返回 Class 對(duì)象,該對(duì)象表示聲明由此 Constructor 對(duì)象表示的構(gòu)造方法的類(lèi),其實(shí)就是返回真實(shí)類(lèi)型(不包含參數(shù))
Type[] getGenericParameterTypes() 按照聲明順序返回一組 Type 對(duì)象,返回的就是 Constructor對(duì)象構(gòu)造函數(shù)的形參類(lèi)型。
String getName() 以字符串形式返回此構(gòu)造方法的名稱。
Class<?>[] getParameterTypes() 按照聲明順序返回一組 Class 對(duì)象,即返回Constructor 對(duì)象所表示構(gòu)造方法的形參類(lèi)型
T newInstance(Object... initargs) 使用此 Constructor對(duì)象表示的構(gòu)造函數(shù)來(lái)創(chuàng)建新實(shí)例
String toGenericString() 返回描述此 Constructor 的字符串,其中包括類(lèi)型參數(shù)。

代碼演示如下:

        Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class);
        System.out.println("-----getDeclaringClass-----");
        Class uclazz=cs3.getDeclaringClass();
//Constructor對(duì)象表示的構(gòu)造方法的類(lèi)
        System.out.println("構(gòu)造方法的類(lèi):"+uclazz.getName());

        System.out.println("-----getGenericParameterTypes-----");
//對(duì)象表示此 Constructor 對(duì)象所表示的方法的形參類(lèi)型
        Type[] tps=cs3.getGenericParameterTypes();
        for (Type tp:tps) {
            System.out.println("參數(shù)名稱tp:"+tp);
        }
        System.out.println("-----getParameterTypes-----");
//獲取構(gòu)造函數(shù)參數(shù)類(lèi)型
        Class<?> clazzs[] = cs3.getParameterTypes();
        for (Class claz:clazzs) {
            System.out.println("參數(shù)名稱:"+claz.getName());
        }
        System.out.println("-----getName-----");
//以字符串形式返回此構(gòu)造方法的名稱
        System.out.println("getName:"+cs3.getName());

        System.out.println("-----getoGenericString-----");
//返回描述此 Constructor 的字符串,其中包括類(lèi)型參數(shù)。
        System.out.println("getoGenericString():"+cs3.toGenericString());

/* output 
-----getDeclaringClass-----
構(gòu)造方法的類(lèi):com.example.javabase.User
-----getGenericParameterTypes-----
參數(shù)名稱tp:int
參數(shù)名稱tp:class java.lang.String
-----getParameterTypes-----
參數(shù)名稱:int
參數(shù)名稱:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)

Field類(lèi)及其用法

Field 提供有關(guān)類(lèi)或接口的單個(gè)字段的信息,以及對(duì)它的動(dòng)態(tài)訪問(wèn)權(quán)限。反射的字段可能是一個(gè)類(lèi)(靜態(tài))字段或?qū)嵗侄巍M瑯拥牡览恚覀兛梢酝ㄟ^(guò)Class類(lèi)的提供的方法來(lái)獲取代表字段信息的Field對(duì)象,Class類(lèi)與Field對(duì)象相關(guān)方法如下:

方法返回值 方法名稱 方法說(shuō)明
Field getDeclaredField(String name) 獲取指定name名稱的(包含private修飾的)字段,不包括繼承的字段
Field[] getDeclaredField() 獲取Class對(duì)象所表示的類(lèi)或接口的所有(包含private修飾的)字段,不包括繼承的字段
Field getField(String name) 獲取指定name名稱、具有public修飾的字段,包含繼承字段
Field[] getField() 獲取修飾符為public的字段,包含繼承字段

下面的代碼演示了上述方法的使用過(guò)程

public class ReflectField {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> clazz = Class.forName("reflect.Student");
        //獲取指定字段名稱的Field類(lèi),注意字段修飾符必須為public而且存在該字段,
        // 否則拋NoSuchFieldException
        Field field = clazz.getField("age");
        System.out.println("field:"+field);

        //獲取所有修飾符為public的字段,包含父類(lèi)字段,注意修飾符為public才會(huì)獲取
        Field fields[] = clazz.getFields();
        for (Field f:fields) {
            System.out.println("f:"+f.getDeclaringClass());
        }

        System.out.println("================getDeclaredFields====================");
        //獲取當(dāng)前類(lèi)所字段(包含private字段),注意不包含父類(lèi)的字段
        Field fields2[] = clazz.getDeclaredFields();
        for (Field f:fields2) {
            System.out.println("f2:"+f.getDeclaringClass());
        }
        //獲取指定字段名稱的Field類(lèi),可以是任意修飾符的自動(dòng),注意不包含父類(lèi)的字段
        Field field2 = clazz.getDeclaredField("desc");
        System.out.println("field2:"+field2);
    }
    /**
      輸出結(jié)果: 
     field:public int reflect.Person.age
     f:public java.lang.String reflect.Student.desc
     f:public int reflect.Person.age
     f:public java.lang.String reflect.Person.name

     ================getDeclaredFields====================
     f2:public java.lang.String reflect.Student.desc
     f2:private int reflect.Student.score
     field2:public java.lang.String reflect.Student.desc
     */
}

class Person{
    public int age;
    public String name;
    //省略set和get方法
}

class Student extends Person{
    public String desc;
    private int score;
    //省略set和get方法
}

上述方法需要注意的是,如果我們不期望獲取其父類(lèi)的字段,則需使用Class類(lèi)的getDeclaredField/getDeclaredFields方法來(lái)獲取字段即可,倘若需要連帶獲取到父類(lèi)的字段,那么請(qǐng)使用Class類(lèi)的getField/getFields,但是也只能獲取到public修飾的的字段,無(wú)法獲取父類(lèi)的私有字段。下面將通過(guò)Field類(lèi)本身的方法對(duì)指定類(lèi)屬性賦值,代碼演示如下:

//獲取Class對(duì)象引用
Class<?> clazz = Class.forName("reflect.Student");

Student st= (Student) clazz.newInstance();
//獲取父類(lèi)public字段并賦值
Field ageField = clazz.getField("age");
ageField.set(st,18);
Field nameField = clazz.getField("name");
nameField.set(st,"Lily");

//只獲取當(dāng)前類(lèi)的字段,不獲取父類(lèi)的字段
Field descField = clazz.getDeclaredField("desc");
descField.set(st,"I am student");
Field scoreField = clazz.getDeclaredField("score");
//設(shè)置可訪問(wèn),score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());

//輸出結(jié)果:Student{age=18, name='Lily ,desc='I am student', score=88} 

//獲取字段值
System.out.println(scoreField.get(st));
// 88

其中的set(Object obj, Object value)方法是Field類(lèi)本身的方法,用于設(shè)置字段的值,而get(Object obj)則是獲取字段的值,當(dāng)然關(guān)于Field類(lèi)還有其他常用的方法如下:

方法返回值 方法名稱 方法說(shuō)明
void set(Object obj, Object value) 將指定對(duì)象變量上此 Field 對(duì)象表示的字段設(shè)置為指定的新值。
Object get(Object obj) 返回指定對(duì)象上此 Field 表示的字段的值
Class<?> getType() 返回一個(gè) Class 對(duì)象,它標(biāo)識(shí)了此Field 對(duì)象所表示字段的聲明類(lèi)型。
boolean isEnumConstant() 如果此字段表示枚舉類(lèi)型的元素則返回 true;否則返回 false
String toGenericString() 返回一個(gè)描述此 Field(包括其一般類(lèi)型)的字符串
String getName() 返回此 Field 對(duì)象表示的字段的名稱
Class<?> getDeclaringClass() 返回表示類(lèi)或接口的 Class 對(duì)象,該類(lèi)或接口聲明由此 Field 對(duì)象表示的字段
void setAccessible(boolean flag) 將此對(duì)象的 accessible 標(biāo)志設(shè)置為指示的布爾值,即設(shè)置其可訪問(wèn)性

上述方法可能是較為常用的,事實(shí)上在設(shè)置值的方法上,F(xiàn)ield類(lèi)還提供了專門(mén)針對(duì)基本數(shù)據(jù)類(lèi)型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,這里就不全部列出了,需要時(shí)查API文檔即可。需要特別注意的是被final關(guān)鍵字修飾的Field字段是安全的,在運(yùn)行時(shí)可以接收任何修改,但最終其實(shí)際值是不會(huì)發(fā)生改變的。

Method類(lèi)及其用法

Method 提供關(guān)于類(lèi)或接口上單獨(dú)某個(gè)方法(以及如何訪問(wèn)該方法)的信息,所反映的方法可能是類(lèi)方法或?qū)嵗椒ǎòǔ橄蠓椒ǎO旅媸荂lass類(lèi)獲取Method對(duì)象相關(guān)的方法:

方法返回值 方法名稱 方法說(shuō)明
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個(gè)指定參數(shù)的Method對(duì)象,該對(duì)象反映此 Class 對(duì)象所表示的類(lèi)或接口的指定已聲明方法。
Method[] getDeclaredMethod() 返回 Method 對(duì)象的一個(gè)數(shù)組,這些對(duì)象反映此 Class 對(duì)象表示的類(lèi)或接口聲明的所有方法,包括公共、保護(hù)、默認(rèn)(包)訪問(wèn)和私有方法,但不包括繼承的方法。
Method getMethod(String name, Class<?>... parameterTypes) 返回一個(gè) Method 對(duì)象,它反映此 Class 對(duì)象所表示的類(lèi)或接口的指定公共成員方法。
Method[] getMethods() 返回一個(gè)包含某些 Method 對(duì)象的數(shù)組,這些對(duì)象反映此 Class 對(duì)象所表示的類(lèi)或接口(包括那些由該類(lèi)或接口聲明的以及從超類(lèi)和超接口繼承的那些的類(lèi)或接口)的公共 member 方法。

同樣通過(guò)案例演示上述方法:

import java.lang.reflect.Method;

public class ReflectMethod  {


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

        Class clazz = Class.forName("reflect.Circle");

        //根據(jù)參數(shù)獲取public的Method,包含繼承自父類(lèi)的方法
        Method method = clazz.getMethod("draw",int.class,String.class);

        System.out.println("method:"+method);

        //獲取所有public的方法:
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }

        System.out.println("=========================================");

        //獲取當(dāng)前類(lèi)的方法包含private,該方法無(wú)法獲取繼承自父類(lèi)的method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::"+method1);
        //獲取當(dāng)前類(lèi)的所有方法包含private,該方法無(wú)法獲取繼承自父類(lèi)的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }
    }

/**
     輸出結(jié)果:
     method:public void reflect.Shape.draw(int,java.lang.String)

     m::public int reflect.Circle.getAllCount()
     m::public void reflect.Shape.draw()
     m::public void reflect.Shape.draw(int,java.lang.String)
     m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
     m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
     m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
     m::public boolean java.lang.Object.equals(java.lang.Object)
     m::public java.lang.String java.lang.Object.toString()
     m::public native int java.lang.Object.hashCode()
     m::public final native java.lang.Class java.lang.Object.getClass()
     m::public final native void java.lang.Object.notify()
     m::public final native void java.lang.Object.notifyAll()

     =========================================
     method1::private void reflect.Circle.drawCircle()

     m1::public int reflect.Circle.getAllCount()
     m1::private void reflect.Circle.drawCircle()
     */
}

class Shape {
    public void draw(){
        System.out.println("draw");
    }

    public void draw(int count , String name){
        System.out.println("draw "+ name +",count="+count);
    }

}
class Circle extends Shape{

    private void drawCircle(){
        System.out.println("drawCircle");
    }
    public int getAllCount(){
        return 100;
    }
}

在通過(guò)getMethods方法獲取Method對(duì)象時(shí),會(huì)把父類(lèi)的方法也獲取到,如上的輸出結(jié)果,把Object類(lèi)的方法都打印出來(lái)了。而getDeclaredMethod/getDeclaredMethods方法都只能獲取當(dāng)前類(lèi)的方法。我們?cè)谑褂脮r(shí)根據(jù)情況選擇即可。下面將演示通過(guò)Method對(duì)象調(diào)用指定類(lèi)的方法:

Class clazz = Class.forName("reflect.Circle");
//創(chuàng)建對(duì)象
Circle circle = (Circle) clazz.newInstance();

//獲取指定參數(shù)的方法對(duì)象Method
Method method = clazz.getMethod("draw",int.class,String.class);

//通過(guò)Method對(duì)象的invoke(Object obj,Object... args)方法調(diào)用
method.invoke(circle,15,"圈圈");

//對(duì)私有無(wú)參方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的訪問(wèn)標(biāo)識(shí)
method1.setAccessible(true);
method1.invoke(circle);

//對(duì)有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);

/**
    輸出結(jié)果:
    draw 圈圈,count=15
    drawCircle
    count:100
*/

在上述代碼中調(diào)用方法,使用了Method類(lèi)的invoke(Object obj,Object... args)第一個(gè)參數(shù)代表調(diào)用的對(duì)象,第二個(gè)參數(shù)傳遞的調(diào)用方法的參數(shù)。這樣就完成了類(lèi)方法的動(dòng)態(tài)調(diào)用。

方法返回值 方法名稱 方法說(shuō)明
Object invoke(Object obj, Object... args) 對(duì)帶有指定參數(shù)的指定對(duì)象調(diào)用由此 Method 對(duì)象表示的底層方法。
Class<?> getReturnType() 返回一個(gè) Class 對(duì)象,該對(duì)象描述了此 Method 對(duì)象所表示的方法的正式返回類(lèi)型,即方法的返回類(lèi)型
Type getGenericReturnType() 返回表示由此 Method 對(duì)象所表示方法的正式返回類(lèi)型的 Type 對(duì)象,也是方法的返回類(lèi)型。
Class<?>[] getParameterTypes() 按照聲明順序返回 Class 對(duì)象的數(shù)組,這些對(duì)象描述了此 Method 對(duì)象所表示的方法的形參類(lèi)型。即返回方法的參數(shù)類(lèi)型組成的數(shù)組
Type[] getGenericParameterTypes() 按照聲明順序返回 Type 對(duì)象的數(shù)組,這些對(duì)象描述了此 Method 對(duì)象所表示的方法的形參類(lèi)型的,也是返回方法的參數(shù)類(lèi)型
String getName() 以 String 形式返回此 Method 對(duì)象表示的方法名稱,即返回方法的名稱
boolean isVarArgs() 判斷方法是否帶可變參數(shù),如果將此方法聲明為帶有可變數(shù)量的參數(shù),則返回 true;否則,返回 false。
String toGenericString() 返回描述此 Method 的字符串,包括類(lèi)型參數(shù)。

getReturnType方法/getGenericReturnType方法都是獲取Method對(duì)象表示的方法的返回類(lèi)型,只不過(guò)前者返回的Class類(lèi)型后者返回的Type(前面已分析過(guò)),Type就是一個(gè)接口而已,在Java8中新增一個(gè)默認(rèn)的方法實(shí)現(xiàn),返回的就參數(shù)類(lèi)型信息

public interface Type {
    //1.8新增
    default String getTypeName() {
        return toString();
    }
}

而getParameterTypes/getGenericParameterTypes也是同樣的道理,都是獲取Method對(duì)象所表示的方法的參數(shù)類(lèi)型,其他方法與前面的Field和Constructor是類(lèi)似的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,540評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,028評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,223評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,442評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,926評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容