死磕Java內(nèi)部類(一篇就夠)

Java內(nèi)部類蓬戚,相信大家都用過,但是多數(shù)同學(xué)可能對它了解的并不深入宾抓,只是靠記憶來完成日常工作子漩,卻不能融會貫通,遇到奇葩問題更是難以有思路去解決石洗。這篇文章帶大家一起死磕Java內(nèi)部類的方方面面幢泼。
友情提示:這篇文章的討論基于JDK版本 1.8.0_191

開篇問題

我一直覺得技術(shù)是工具,是一定要落地的讲衫,要切實解決某些問題的缕棵,所以我們通過先拋出問題,然后解決這些問題,在這個過程中來加深理解挥吵,最容易有收獲重父。
so,先拋出幾個問題忽匈。(如果這些問題你早已思考過房午,答案也了然于胸,那恭喜你丹允,這篇文章可以關(guān)掉了)郭厌。

  • 為什么需要內(nèi)部類?
  • 為什么內(nèi)部類(包括匿名內(nèi)部類雕蔽、局部內(nèi)部類)折柠,會持有外部類的引用飒货?
  • 為什么匿名內(nèi)部類使用到外部類方法中的局部變量時需要是final類型的玖雁?
  • 如何創(chuàng)建內(nèi)部類實例,如何繼承內(nèi)部類膝藕?
  • Lambda表達(dá)式是如何實現(xiàn)的嚣艇?

為什么需要內(nèi)部類?

要回答這個問題承冰,先要弄明白什么是內(nèi)部類?我們知道Java有三種類型的內(nèi)部類

普通的內(nèi)部類

public class Demo {

    // 普通內(nèi)部類
    public class DemoRunnable implements Runnable {
        @Override
        public void run() {
        }
    }
}

匿名內(nèi)部類

public class Demo {

    // 匿名內(nèi)部類
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {

        }
    };
}

方法內(nèi)局部內(nèi)部類

public class Demo {

    // 局部內(nèi)部類
    public void work() {
        class InnerRunnable implements Runnable {
            @Override
            public void run() {

            }
        }
        InnerRunnable runnable = new InnerRunnable();
    }

}

這三種形式的內(nèi)部類食零,大家肯定都用過困乒,但是技術(shù)在設(shè)計之初肯定也是要用來解決某個問題或者某個痛點(diǎn),那可以想想內(nèi)部類相對比外部定義類有什么優(yōu)勢呢贰谣?
我們通過一個小例子來做說明

public class Worker {
    private List<Job> mJobList = new ArrayList<>();

    public void addJob(Runnable task) {
        mJobList.add(new Job(task));
    }

    private class Job implements Runnable {
        Runnable task;
        public  Job(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            runnable.run();
            System.out.println("left job size : " + mJobList.size());
        }
    }
}

定義了一個Worker類娜搂,暴露了一個addJob方法,一個參數(shù)task吱抚,類型是Runnable百宇,然后定義 了一個內(nèi)部類Job類對task進(jìn)行了一層封裝,這里Job是私有的秘豹,所以外界是感知不到Job的存在的恳谎,所以有了內(nèi)部類第一個優(yōu)勢。

  • 內(nèi)部類能夠更好的封裝憋肖,內(nèi)聚因痛,屏蔽細(xì)節(jié)

我們在Job的run方法中,打印了外部Worker的mJobList列表中剩余Job數(shù)量岸更,代碼這樣寫沒問題鸵膏,但是細(xì)想,內(nèi)部類是如何拿到外部類的成員變量的呢怎炊?這里先賣個關(guān)子谭企,但是已經(jīng)可以先得出內(nèi)部類的第二個優(yōu)勢了廓译。

  • 內(nèi)部類天然有訪問外部類成員變量的能力

內(nèi)部類主要就是上面的二個優(yōu)勢。當(dāng)然還有一些其他的小優(yōu)點(diǎn)债查,比如可以用來實現(xiàn)多重繼承非区,可以將邏輯內(nèi)聚在一個類方便維護(hù)等,這些見仁見智盹廷,先不去說它們征绸。

我們接著看第二個問題!6碚肌管怠!

為什么內(nèi)部類(包括匿名內(nèi)部類、局部內(nèi)部類)缸榄,會持有外部類的引用渤弛?

問這個問題,顯得我是個杠精甚带,您先別著急她肯,其實我想問的是,內(nèi)部類Java是怎么實現(xiàn)的鹰贵。
我們還是舉例說明晴氨,先以普通的內(nèi)部類為例

普通內(nèi)部類的實現(xiàn)

public class Demo {
    // 普通內(nèi)部類
    public class DemoRunnable implements Runnable {
        @Override
        public void run() {
        }
    }
}

切到Demo.java所在文件夾,命令行執(zhí)行 javac Demo.java砾莱,在Demo類同目錄下可以看到生成了二個class文件


普通內(nèi)部類生成class.png

Demo.class很好理解瑞筐,另一個 類

Demo$DemoRunnable.class

就是我們的內(nèi)部類編譯出來的凄鼻,它的命名也是有規(guī)律的腊瑟,外部類名Demo+$+內(nèi)部類名DemoRunnable。
查看反編譯后的代碼(IntelliJ IDEA本身就支持块蚌,直接查看class文件即可)

package inner;

public class Demo$DemoRunnable implements Runnable {
    public Demo$DemoRunnable(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
    }
}

生成的類只有一個構(gòu)造器闰非,參數(shù)就是Demo類型,而且保存到內(nèi)部類本身的this$0字段中峭范。到這里我們其實已經(jīng)可以想到财松,內(nèi)部類持有的外部類引用就是通過這個構(gòu)造器傳遞進(jìn)來的,它是一個強(qiáng)引用纱控。

驗證我們的想法

怎么驗證呢辆毡?我們需要在Demo.class類中加一個方法,來實例化這個DemoRunnable內(nèi)部類對象

   // Demo.java
    public void run() {
        DemoRunnable demoRunnable = new DemoRunnable();
        demoRunnable.run();
    }

再次執(zhí)行 javac Demo.java甜害,再執(zhí)行javap -verbose Demo.class舶掖,查看Demo類的字節(jié)碼,前方高能尔店,需要一些字節(jié)碼知識眨攘,這里我們重點(diǎn)關(guān)注run方法(插一句題外話主慰,字節(jié)碼簡單的要能看懂,-鲫售。-)

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class inner/Demo$DemoRunnable
         3: dup
         4: aload_0
         5: invokespecial #3                  // Method inner/Demo$DemoRunnable."<init>":(Linner/Demo;)V
         8: astore_1
         9: aload_1
        10: invokevirtual #4                  // Method inner/Demo$DemoRunnable.run:()V
        13: return

  • 先通過new指令共螺,新建了一個Demo$DemoRunnable對象
  • aload_0指令將外部類Demo對象自身加載到棧幀中
  • 調(diào)用Demo$DemoRunnable類的init方法,注意這里將Demo對象作為了參數(shù)傳遞進(jìn)來了

到這一步其實已經(jīng)很清楚了情竹,就是將外部類對象自身作為參數(shù)傳遞給了內(nèi)部類構(gòu)造器藐不,與我們上面的猜想一致。

匿名內(nèi)部類的實現(xiàn)

public class Demo {
    // 匿名內(nèi)部類
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {

        }
    };
}

同樣執(zhí)行javac Demo.java鲤妥,這次多生成了一個Demo$1.class佳吞,反編譯查看代碼

package inner;

class Demo$1 implements Runnable {
    Demo$1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
    }
}

可以看到匿名內(nèi)部類和普通內(nèi)部類實現(xiàn)基本一致,只是編譯器自動給它拼了個名字棉安,所以匿名內(nèi)部類不能自定義構(gòu)造器底扳,因為名字編譯完成后才能確定。
方法局部內(nèi)部類贡耽,我這里就不贅述了衷模,原理都是一樣的,大家可以自行試驗蒲赂。
這樣我們算是解答了第二個問題阱冶,來看第三個問題。

為什么匿名內(nèi)部類使用到外部類方法中的局部變量時需要是final類型的滥嘴?

這里先申明一下木蹬,這個問題本身是有問題的,問題在哪呢若皱?因為java8中并不一定需要聲明為final镊叁。我們來看個例子

   // Demo.java
    public void run() {
        int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
            }
        };
    }

匿名內(nèi)部類對象runnable,使用了外部類方法中的age局部變量走触。編譯運(yùn)行完全沒問題晦譬,而age并沒有final修飾啊互广!
那我們再在run方法中敛腌,嘗試修改age試試

    public void run() {
        int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
                age = 20;   // error
            }
        };
    }

編譯器報錯了,提示信息是”age is access from inner class, need to be final or effectively final“惫皱。很顯然編譯器很智能像樊,由于我們第一個例子并沒有修改age的值,所以編譯器認(rèn)為這是effectively final旅敷,是安全的生棍,可以編譯通過,而第二個例子嘗試修改age的值扫皱,編譯器立馬就報錯了足绅。

外部類變量是怎么傳遞給內(nèi)部類的捷绑?

這里對于變量的類型分三種情況分別來說明

非final局部變量

我們?nèi)サ魢L試修改age的代碼,然后執(zhí)行javac Demo.java氢妈,查看Demo$1.class的實現(xiàn)代碼

package inner;

class Demo$1 implements Runnable {
    Demo$1(Demo var1, int var2) {
        this.this$0 = var1;
        this.val$age = var2;
    }

    public void run() {
        int var1 = this.val$age + 1;
        System.out.println(var1);
    }
}

可以看到對于非final局部變量粹污,是通過構(gòu)造器的方式傳遞進(jìn)來的。

final局部變量

age修改為final

    public void run() {
        final int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
            }
        };
    }

同樣執(zhí)行javac Demo.java首量,查看Demo$1.class的實現(xiàn)代碼

class Demo$1 implements Runnable {
    Demo$1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
        byte var1 = 11;
        System.out.println(var1);
    }
}

可以看到編譯器很聰明的做了優(yōu)化壮吩,age是final的,所以在編譯期間是確定的加缘,直接將+1優(yōu)化為11鸭叙。
為了測試編譯器的智商,我們把a(bǔ)ge的賦值修改一下拣宏,改為運(yùn)行時才能確定的沈贝,看編譯器如何應(yīng)對

    public void run() {
        final int age = (int) System.currentTimeMillis();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
            }
        };
    }

再看Demo$1 字節(jié)碼實現(xiàn)

class Demo$1 implements Runnable {
    Demo$1(Demo var1, int var2) {
        this.this$0 = var1;
        this.val$age = var2;
    }

    public void run() {
        int var1 = this.val$age + 1;
        System.out.println(var1);
    }
}

編譯器意識到編譯期age的值不能確定,所以還是采用構(gòu)造器傳參的形式實現(xiàn)⊙現(xiàn)代編譯器還是很機(jī)智的宋下。

外部類成員變量

將age改為Demo的成員變量,注意沒有加任何修飾符辑莫,是包級訪問級別学歧。

public class Demo {
    int age = 10;
    public void run() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
                age = 20;
            }
        };
    }
}

javac Demo.java,查看匿名內(nèi)部內(nèi)的實現(xiàn)

class Demo$1 implements Runnable {
    Demo$1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
        int var1 = this.this$0.age + 1;
        System.out.println(var1);
        this.this$0.age = 20;
    }
}

這一次編譯器直接通過外部類的引用操作age各吨,沒毛病枝笨,由于age是包訪問級別,所以這樣是最高效的揭蜒。
如果將age改為private横浑,編譯器會在Demo類中生成二個方法,分別用于讀取age和設(shè)置age忌锯,篇幅關(guān)系伪嫁,這種情況留給大家自行測試领炫。

解答為何局部變量傳遞給匿名內(nèi)部類需要是final?

通過上面的例子可以看到偶垮,不是一定需要局部變量是final的,但是你不能在匿名內(nèi)部類中修改外部局部變量帝洪,因為Java對于匿名內(nèi)部類傳遞變量的實現(xiàn)是基于構(gòu)造器傳參的似舵,也就是說如果允許你在匿名內(nèi)部類中修改值,你修改的是匿名內(nèi)部類中的外部局部變量副本葱峡,最終并不會對外部類產(chǎn)生效果砚哗,因為已經(jīng)是二個變量了。
這樣就會讓程序員產(chǎn)生困擾砰奕,原以為修改會生效蛛芥,事實上卻并不會提鸟,所以Java就禁止在匿名內(nèi)部類中修改外部局部變量。

如何創(chuàng)建內(nèi)部類實例仅淑,如何繼承內(nèi)部類称勋?

由于內(nèi)部類對象需要持有外部類對象的引用,所以必須得先有外部類對象

Demo.DemoRunnable demoRunnable = new Demo().new DemoRunnable();

那如何繼承一個內(nèi)部類呢涯竟,先給出示例

    public class Demo2 extends Demo.DemoRunnable {
        public Demo2(Demo demo) {
            demo.super();
        }

        @Override
        public void run() {
            super.run();
        }
    }

必須在構(gòu)造器中傳入一個Demo對象赡鲜,并且還需要調(diào)用demo.super();
看個例子

public class DemoKata {
    public static void main(String[] args) {
        Demo2 demo2 = new DemoKata().new Demo2(new Demo());
    }

    public class Demo2 extends Demo.DemoRunnable {
        public Demo2(Demo demo) {
            demo.super();
        }

        @Override
        public void run() {
            super.run();
        }
    }
}

由于Demo2也是一個內(nèi)部類,所以需要先new一個DemoKata對象庐船。
這一個問題描述的場景可能用的并不多银酬,一般也不這么去用,這里提一下筐钟,大家知道有這么回事就行揩瞪。

Lambda表達(dá)式是如何實現(xiàn)的?

Java8引入了Lambda表達(dá)式篓冲,一定程度上可以簡化我們的代碼壮韭,使代碼結(jié)構(gòu)看起來更優(yōu)雅。做技術(shù)的還是要有刨根問底的那股勁纹因,問問自己有沒有想過Java中Lambda到底是如何實現(xiàn)的呢喷屋?

來看一個最簡單的例子

public class Animal {
    public void run(Runnable runnable) {
    }
}

Animal類中定義了一個run方法,參數(shù)是一個Runnable對象瞭恰,Java8以前屯曹,我們可以傳入一個匿名內(nèi)部類對象

run(new Runnable() {
            @Override
            public void run() {
            }
});

Java 8 之后編譯器已經(jīng)很智能的提示我們可以用Lambda表達(dá)式來替換。既然可以替換惊畏,那匿名內(nèi)部類和Lambda表達(dá)式是不是底層實現(xiàn)是一樣的呢恶耽,或者說Lambda表達(dá)式只是匿名內(nèi)部類的語法糖呢?
要解答這個問題颜启,我們還是要去字節(jié)碼中找線索偷俭。通過前面的知識,我們知道javac Animal.java命令將類編譯成class缰盏,匿名內(nèi)部類的方式會產(chǎn)生一個額外的類涌萤。那用Lambda表達(dá)式會不會也會編譯新類呢?我們試一下便知口猜。

    public void run(Runnable runnable) {
    }

    public void test() {
        run(() -> {});
    }

javac Animal.java负溪,發(fā)現(xiàn)并沒有生成額外的類!<醚住川抡!
我們繼續(xù)使用javap -verbose Animal.class來查看Animal.class的字節(jié)碼實現(xiàn),重點(diǎn)關(guān)注test方法

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         6: invokevirtual #3                  // Method run:(Ljava/lang/Runnable;)V
         9: return

SourceFile: "Demo.java"
InnerClasses:
     public static final #34= #33 of #37; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #19 ()V
      #20 invokestatic com/company/inner/Demo.lambda$test$0:()V
      #19 ()V

發(fā)現(xiàn)test方法字節(jié)碼中多了一個invokedynamic #2 0指令须尚,這是java7引入的新指令崖堤,其中#2 指向

#2 = InvokeDynamic      #0:#21         // #0:run:()Ljava/lang/Runnable;

而0代表BootstrapMethods方法表中的第一個侍咱,java/lang/invoke/LambdaMetafactory.metafactory方法被調(diào)用。

BootstrapMethods:
  0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #19 ()V
      #20 invokestatic com/company/inner/Demo.lambda$test$0:()V
      #19 ()V

這里面我們看到了com/company/inner/Demo.lambdatest0這么個東西密幔,看起來跟我們的匿名內(nèi)部類的名稱有些類似放坏,而且中間還有l(wèi)ambda,有可能就是我們要找的生成的類老玛。
我們不妨驗證下我們的想法淤年,可以通過下面的代碼打印出Lambda對象的真實類名。

    public void run(Runnable runnable) {
        System.out.println(runnable.getClass().getCanonicalName());
    }

    public void test() {
        run(() -> {});
    }

打印出runnable的類名蜡豹,結(jié)果如下

com.company.inner.Demo$$Lambda$1/764977973

跟我們上面的猜測并不完全一致麸粮,我們繼續(xù)找別的線索,既然我們有看到LambdaMetafactory.metafactory這個類被調(diào)用镜廉,不妨繼續(xù)跟進(jìn)看下它的實現(xiàn)

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

內(nèi)部new了一個InnerClassLambdaMetafactory對象弄诲。看名字很可疑娇唯,繼續(xù)跟進(jìn)

public InnerClassLambdaMetafactory(...)
            throws LambdaConversionException {
        //....
        lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
       //....
    }

省略了很多代碼齐遵,我們重點(diǎn)看lambdaClassName這個字符串(通過名字就知道是干啥的),可以看到它的拼接結(jié)果跟我們上面打印的Lambda類名基本一致塔插。而下面的ClassWriter也暴露了梗摇,其實Lambda運(yùn)用的是Asm字節(jié)碼技術(shù),在運(yùn)行時生成類文件想许。我感覺到這里就差不多了伶授,再往下可能就有點(diǎn)太過細(xì)節(jié)了。-流纹。-

Lambda實現(xiàn)總結(jié)

所以Lambda表達(dá)式并不是匿名內(nèi)部類的語法糖糜烹,它是基于invokedynamic指令,在運(yùn)行時使用ASM生成類文件來實現(xiàn)的漱凝。

寫在最后

這可能是我迄今寫的最長的一篇技術(shù)文章了疮蹦,寫的過程中也在不斷的加深自己對知識點(diǎn)的理解,顛覆了很多以往的錯誤認(rèn)知茸炒。寫技術(shù)文章這條路我會一直堅持下去愕乎。
非常喜歡得到里面的一句slogan,胡適先生說的話扣典。
怕什么真理無窮妆毕,進(jìn)一寸有一寸的歡喜
共勉!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慎玖,一起剝皮案震驚了整個濱河市贮尖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趁怔,老刑警劉巖湿硝,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薪前,死亡現(xiàn)場離奇詭異,居然都是意外死亡关斜,警方通過查閱死者的電腦和手機(jī)示括,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痢畜,“玉大人垛膝,你說我怎么就攤上這事《∠。” “怎么了吼拥?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長线衫。 經(jīng)常有香客問我凿可,道長,這世上最難降的妖魔是什么授账? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任枯跑,我火速辦了婚禮,結(jié)果婚禮上白热,老公的妹妹穿的比我還像新娘敛助。我一直安慰自己,他們只是感情好屋确,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布辜腺。 她就那樣靜靜地躺著,像睡著了一般乍恐。 火紅的嫁衣襯著肌膚如雪评疗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天茵烈,我揣著相機(jī)與錄音百匆,去河邊找鬼。 笑死呜投,一個胖子當(dāng)著我的面吹牛加匈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仑荐,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雕拼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粘招?” 一聲冷哼從身側(cè)響起啥寇,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辑甜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衰絮,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年磷醋,在試婚紗的時候發(fā)現(xiàn)自己被綠了猫牡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡邓线,死狀恐怖淌友,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骇陈,我是刑警寧澤亩进,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站缩歪,受9級特大地震影響归薛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匪蝙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一主籍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逛球,春花似錦千元、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奥务,卻和暖如春物独,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氯葬。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工挡篓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帚称。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓官研,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闯睹。 傳聞我的和親對象是個殘疾皇子戏羽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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

  • 本文首發(fā)于我的個人博客 —— Bridge for You,轉(zhuǎn)載請標(biāo)明出處楼吃。 前言 我們先來看一道很簡單的小題: ...
    柳樹之閱讀 1,361評論 3 23
  • 轉(zhuǎn)載:https://juejin.im/post/5a903ef96fb9a063435ef0c8 本文將會從以...
    福later閱讀 402評論 0 3
  • 問:Java 常見的內(nèi)部類有哪幾種始花,簡單說說其特征妄讯? 答:靜態(tài)內(nèi)部類、成員內(nèi)部類衙荐、方法內(nèi)部類(局部內(nèi)部類)捞挥、匿名內(nèi)...
    Little丶Jerry閱讀 1,947評論 0 1
  • 整理來自互聯(lián)網(wǎng) 1浮创,JDK:Java Development Kit忧吟,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)工具...
    Ncompass閱讀 1,538評論 0 6
  • 一:java概述: 1斩披,JDK:Java Development Kit溜族,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)...
    慕容小偉閱讀 1,790評論 0 10