什么?Java9這些史詩(shī)級(jí)更新你都不知道?Java9特性一文打盡!

image
  • 「MoreThanJava」 宣揚(yáng)的是 「學(xué)習(xí),不止 CODE」,本系列 Java 基礎(chǔ)教程是自己在結(jié)合各方面的知識(shí)之后,對(duì) Java 基礎(chǔ)的一個(gè)總回顧,旨在 「幫助新朋友快速高質(zhì)量的學(xué)習(xí)」
  • 當(dāng)然 不論新老朋友 我相信您都可以 從中獲益。如果覺(jué)得 「不錯(cuò)」 的朋友,歡迎 「關(guān)注 + 留言 + 分享」,文末有完整的獲取鏈接,您的支持是我前進(jìn)的最大的動(dòng)力!

特性總覽

以下是 Java 9 中的引入的部分新特性。關(guān)于 Java 9 新特性更詳細(xì)的介紹可參考這里

  • REPL(JShell)
  • 不可變集合的工廠方法
  • 模塊系統(tǒng)
  • 接口支持私有化
  • 鉆石操作符升級(jí)
  • Optional 改進(jìn)
  • Stream API 改進(jìn)
  • 反應(yīng)式流(Reactive Streams)
  • 進(jìn)程 API
  • 升級(jí)的 Try-With-Resources
  • HTTP / 2
  • 多版本兼容 Jar 包
  • 其他
    • 改進(jìn)應(yīng)用安全性能
    • 統(tǒng)一 JVM 日志
    • G1 設(shè)為默認(rèn)垃圾回收器
    • String 底層存儲(chǔ)結(jié)構(gòu)更改
    • CompletableFuture API 改進(jìn)
    • I/O 流新特性
    • JavaScript 引擎 Nashorn 改進(jìn)
    • 標(biāo)識(shí)符增加限制
    • 改進(jìn)的 Javadoc
    • 改進(jìn)的 @Deprectaed 注解
    • 多分辨率圖像 API
    • 變量句柄
    • 改進(jìn)方法句柄(Method Handle)
    • 提前編譯 AOT

一. Java 9 REPL(JShell)

什么是 REPL 以及為什么引入

REPL,即 Read-Evaluate-Print-Loop 的簡(jiǎn)稱。由于 Scala 語(yǔ)言的特性和優(yōu)勢(shì)在小型應(yīng)用程序到大型應(yīng)用程序市場(chǎng)大受追捧,于是引來(lái) Oracle 的關(guān)注,并嘗試將大多數(shù) Scala 功能集成到 Java 中。這在 Java 8 中已經(jīng)完成一部分,比如 Lambda 表達(dá)式。

Scala 的最佳功能之一就是 REPL,這是一個(gè)命令行界面和 Scala 解釋器,用于執(zhí)行 Scala 程序。由于并不需要開啟額外的 IDE (就是一個(gè)命令行),它在減少學(xué)習(xí)曲線和簡(jiǎn)化運(yùn)行測(cè)試代碼方面有獨(dú)特的優(yōu)勢(shì)。

于是在 Java 9 中引入了 Java REPL,也稱為 JShell

JShell 基礎(chǔ)

打開命令提示符,確保您具有 Java 9 或更高版本,鍵入 jshell,然后我們就可以開心的使用了。

下面是簡(jiǎn)單示范:

wmyskxz:~ wmyskxz$ jshell 
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell> 

jshell> System.out.println("Hello World");
Hello World

jshell> String str = "Hello JShell!"
str ==> "Hello JShell!"

jshell> str
str ==> "Hello JShell!"

jshell> System.out.println(str)
Hello JShell!

jshell> int counter = 0
counter ==> 0

jshell> counter++
$6 ==> 0

jshell> counter
counter ==> 1

jshell> counter+5
$8 ==> 6

也可以在 Java Shell 中定義和執(zhí)行類方法:

jshell> class Hello {
   ...> public static void sayHello() {
   ...> System.out.print("Hello");
   ...> }
   ...> }
|  created class Hello

jshell> Hello.sayHello()
Hello
jshell> 

Java REPL - 幫助和退出

要獲得 jshell 工具的幫助部分,請(qǐng)使用/help命令。要從 jshell 退出,請(qǐng)使用 /exit 命令 (或者直接使用 Ctrl + D 命令退出)

jshell> /help
|  Type a Java language expression, statement, or declaration.
|  Or type one of the following commands:
|  /list [<name or id>|-all|-start]
|   list the source you have typed
|  /edit <name or id>
...

jshell> /exit
|  Goodbye
wmyskxz:~ wmyskxz$ 

二. 不可變集合的工廠方法

Java 9 中增加了一些便捷的工廠方法用于創(chuàng)建 不可變 List、Set、Map 以及 Map.Entry 對(duì)象。

在 Java SE 8 和更早的版本中,如果我們要?jiǎng)?chuàng)建一個(gè)空的 不可變不可修改 的列表,需要借助 Collections 類的 unmodifiableList() 方法才可以:

List<String> list = new ArrayList<>();
list.add("公眾號(hào)");
list.add("我沒(méi)有三顆心臟");
list.add("關(guān)注走起來(lái)");
List<String> immutableList = Collections.unmodifiableList(list);

可以看到,為了創(chuàng)建一個(gè)非空的不可變列表,我們需要經(jīng)歷很多繁瑣和冗長(zhǎng)的步驟。為了克服這一點(diǎn),Java 9 在 List 接口中引入了以下有用的重載方法:

static <E> List<E> of(E e1)
static <E> List<E> of(E e1,E e2)    
static <E> List<E> of(E e1,E e2,E e3)
static <E> List<E> of(E e1,E e2,E e3,E e4)
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5) 
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5,E e6)    
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5,E e6,E e7)   
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8)  
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9) 
static <E> List<E> of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9,E e10)

以及可變參數(shù)數(shù)目的方法:

static <E> List<E> of(E... elements)  

可以看到 Java 9 前后的對(duì)比:

// Java 9 之前
List<String> list = new ArrayList<>();
list.add("公眾號(hào)");
list.add("我沒(méi)有三顆心臟");
list.add("關(guān)注走起來(lái)");
List<String> unmodifiableList = Collections.unmodifiableList(list);
// 或者使用 {{}} 的形式
List<String> list = new ArrayList<>() {{
    add("公眾號(hào)");
    add("我沒(méi)有三顆心臟");
    add("關(guān)注走起來(lái)");
}};
List<String> unmodifiableList = Collections.unmodifiableList(list);

// Java 9 便捷的工廠方法
List<String> unmodifiableList = List.of("公眾號(hào)", "我沒(méi)有三顆心臟", "關(guān)注走起來(lái)");

(ps: Set、Map 類似,Map 有兩組方法:of()ofEntries() 分別用于創(chuàng)建 Immutable Map 對(duì)象和 Immutable Map.Entry 對(duì)象)

另外 Java 9 可以直接輸出集合的內(nèi)容,在此之前必須遍歷集合才能全部獲取里面的元素,這是一個(gè)很大的改進(jìn)。

不可變集合的特征

不可變即不可修改。它們通常具有以下幾個(gè)特征:

1、我們無(wú)法添加、修改和刪除其元素;

2、如果嘗試對(duì)它們執(zhí)行添加/刪除/更新操作,將會(huì)得到 UnsupportedOperationException 異常,如下所示:

jshell> immutableList.add("Test")
|  java.lang.UnsupportedOperationException thrown: 
|        at ImmutableCollections.uoe (ImmutableCollections.java:68)
|        at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:74)
|        at (#2:1)

3、不可變集合不允許 null 元素;

4、如果嘗試使用 null 元素創(chuàng)建,則會(huì)報(bào)出 NullPointerException 異常,如下所示:

jshell> List>String> immutableList = List.of("公眾號(hào)","我沒(méi)有三顆心臟","關(guān)注走起來(lái)", null)
|  java.lang.NullPointerException thrown: 
|        at Objects.requireNonNull (Objects.java:221)
|        at ImmutableCollections$ListN. (ImmutableCollections.java:179)
|        at List.of (List.java:859)
|        at (#4:1)

5、如果嘗試添加 null 元素,則會(huì)得到 UnsupportedOperationException 異常,如下所示:

jshell> immutableList.add(null)
|  java.lang.UnsupportedOperationException thrown: 
|        at ImmutableCollections.uoe (ImmutableCollections.java:68)
|        at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:74)
|        at (#3:1)

6、如果所有元素都是可序列化的,那么集合是可以序列化的;

三. 模塊系統(tǒng)

Java 模塊系統(tǒng)是 Oracle 在 Java 9 引入的全新概念。最初,它作為 Java SE 7 Release 的一部分啟動(dòng)了該項(xiàng)目,但是由于進(jìn)行了很大的更改,它被推遲到了 Java SE 8,然后又被推遲了。最終隨著 2017 年 9 月發(fā)布的 Java SE 9 一起發(fā)布。

為什么需要模塊系統(tǒng)?

當(dāng)代碼庫(kù)變得更大時(shí),創(chuàng)建復(fù)雜、糾結(jié)的 “意大利面條代碼” 的幾率成倍增加。在 Java 8 或更早版本交付 Java 應(yīng)用時(shí)存在幾個(gè)基本問(wèn)題:

  1. 難以真正封裝代碼,并且在系統(tǒng)的不同部分(JAR 文件)之間沒(méi)有顯式依賴關(guān)系的概念。每個(gè)公共類都可以由 classpath 上的任何其他公共類訪問(wèn),從而導(dǎo)致無(wú)意中使用了本不應(yīng)該是公共 API 的類。
  2. 再者,類路徑本身是有問(wèn)題的:您如何知道是否所有必需的 JAR 都存在,或者是否存在重復(fù)的條目?
  3. 另外,JDK 太大了rt.jar rt.jar 就是 Java 基礎(chǔ)類庫(kù)——也就是 Java Doc 里面看到的所有類的 class 文件)等 JAR 文件甚至無(wú)法在小型設(shè)備和應(yīng)用程序中使用:因此我們的應(yīng)用程序和設(shè)備無(wú)法支持更好的性能——打包之后的應(yīng)用程序太大了——也很難測(cè)試和維護(hù)應(yīng)用程序。

模塊系統(tǒng)解決了這幾個(gè)問(wèn)題。

什么是 Java 9 模塊系統(tǒng)?

模塊就是代碼、數(shù)據(jù)和一些資源的自描述集合。它是一組與代碼、數(shù)據(jù)和資源相關(guān)的包。

每個(gè)模塊僅包含一組相關(guān)的代碼和數(shù)據(jù),以支持單一職責(zé)原則(SRP)。

image

Java 9 模塊系統(tǒng)的主要目標(biāo)就是支持 Java 模塊化編程(我們將在下面??體驗(yàn)一下模塊化編程)

比較 JDK 8 和 JDK 9

我們知道 JDK 軟件包含什么。安裝 JDK 8 軟件后,我們可以在 Java Home 文件夾中看到幾個(gè)目錄,例如 binjrelib 等。

但是,Oracle 在 Java 9 中對(duì)該文件夾結(jié)構(gòu)的更改有些不同,如下所示。

image

這里的 JDK 9 不包含 JRE。在 JDK 9 中,JRE 分為一個(gè)單獨(dú)的分發(fā)文件夾。JDK 9 軟件包含一個(gè)新文件夾 “ jmods”,它包含一組 Java 9 模塊。在 JDK 9 中,沒(méi)有 rt.jartools.jar(如下所示)

image

注意: 截止今天, jmods 包含了 95 個(gè)模塊。(最終版可能更多)

比較 Java 8 和 Java 9 應(yīng)用程序

我們已經(jīng)使用 Java 5、Java 6、Java 7 或 Java 8 開發(fā)了許多 Java 應(yīng)用程序了,我們知道 Java 8 或更早版本的應(yīng)用程序,頂級(jí)組件是 Package:

image

Java 9 應(yīng)用程序與此沒(méi)有太大的區(qū)別。它剛剛引入了稱為 "模塊" 和稱為模塊描述符(module-info.java)的新組件:

image

像 Java 8 應(yīng)用程序?qū)?Packages 作為頂級(jí)組件一樣,Java 9 應(yīng)用程序?qū)?Module 作為頂級(jí)組件。

注意:每個(gè) Java 9 模塊只有一個(gè)模塊和一個(gè)模塊描述符。與 Java 8 包不同,我們不能在一個(gè)模塊中創(chuàng)建多個(gè)模塊。

HelloModule 示例程序

作為開發(fā)人員,我們首先從 “HelloWorld” 程序開始學(xué)習(xí)新的概念或編程語(yǔ)言。以同樣的方式,我們開始通過(guò) “ HelloModule” 模塊開發(fā)來(lái)學(xué)習(xí) Java 9 新概念“ 模塊化編程 ”。

第一步:創(chuàng)建一個(gè)空的 Java 項(xiàng)目

如果不想額外命名的話一路 Next 就好了:

image

第二步:創(chuàng)建 HelloModule 模塊

右鍵項(xiàng)目,創(chuàng)建一個(gè)新的【Module】,命名為:com.wmyskxz.core

image

并在新 Module 的 src 文件夾下新建包 module.hello,此時(shí)項(xiàng)目結(jié)構(gòu):

.
└── com.wmyskxz.core
    └── src
        └── module
            └── hello

第三步:編寫 HelloModule.java

在剛才創(chuàng)建的包下新建 HelloModule 文件,并編寫測(cè)試用的代碼:

package module.hello;

public class HelloModule {
  
    public void sayHello() {
        System.out.println("Hello Module!");
    }
}

第四步:為 Module 編寫模塊描述符

在 IDEA 中,我們可以直接右鍵 src 文件夾,快捷創(chuàng)建 module-info.java 文件:

image

編寫 module-info.java 文件,將我們剛才的包 module.hello 里面的內(nèi)容暴露出去(給其他 Module 使用):

module com.wmyskxz.core {
    exports module.hello;
}

module 關(guān)鍵字后面是我們的模塊名稱,里面的 exports 寫明了我們想要暴露出去的包。此時(shí)的文件目錄結(jié)構(gòu):

.
└── com.wmyskxz.core
    └── src
        ├── module
        │   └── hello
        │       └── HelloModule.java
        └── module-info.java

第五步:同樣的方法編寫客戶端

用上面同樣的方法,我們?cè)陧?xiàng)目根目錄創(chuàng)建一個(gè) com.wmyskxz.client 的 Module,并新建 module.client 包目錄,并創(chuàng)建好我們的 HelloModuleClient 文件的大概樣子:

// HelloModuleClient.java
package module.client;

public class HelloModuleClient {

    public static void main(String[] args) {

    }
}

如果我們想要直接調(diào)用 HelloModule 類,會(huì)發(fā)現(xiàn) IDEA 并沒(méi)有提示信息,也就是說(shuō)我們無(wú)法直接引用了..

我們需要先在模塊描述符(同樣需要在 src 目錄創(chuàng)建 module-info.java 文件)中顯式的引入我們剛才暴露出來(lái)的 com.wmyskxz.core 模塊:

module com.wmyskxz.client {
    requires com.wmyskxz.core;
}

(ps:在 IDEA 中編寫完成之后需要手動(dòng) alt + enter 引入模塊依賴)

這一步完成之后,我們就可以在剛才的 HelloModuleClient 中愉快的使用 HelloModule 文件了:

package module.client;

import module.hello.HelloModule;

public class HelloModuleClient {

    public static void main(String[] args) {
        HelloModule helloModule = new HelloModule();
        helloModule.sayHello();
    }
}

此時(shí)的項(xiàng)目結(jié)構(gòu):

.
├── com.wmyskxz.client
│   └── src
│       ├── module
│       │   └── client
│       │       └── HelloModuleClient.java
│       └── module-info.java
└── com.wmyskxz.core
    └── src
        ├── module
        │   └── hello
        │       └── HelloModule.java
        └── module-info.java

第六步:運(yùn)行測(cè)試

運(yùn)行代碼:

Hello Module!

成功!

模塊系統(tǒng)小結(jié)

我們從上面的例子中可以看到,我們可以指定我們想要導(dǎo)出和引用的軟件包,沒(méi)有人可以不小心地使用那些不想被導(dǎo)出的軟件包中的類。

Java 平臺(tái)本身也已經(jīng)使用其自己的模塊系統(tǒng)對(duì) JDK 進(jìn)行了模塊化。啟動(dòng)模塊化應(yīng)用程序時(shí),JVM 會(huì)根據(jù) requires 語(yǔ)句驗(yàn)證是否可以解析所有模塊,這比脆弱的類路徑要安全得多。模塊使您能夠通過(guò)強(qiáng)力執(zhí)行封裝和顯式依賴來(lái)更好地構(gòu)建應(yīng)用程序。

四. 接口支持私有方法

在 Java 8 中,我們可以使用 defaultstatic 方法在 Interfaces 中提供方法實(shí)現(xiàn)。但是,我們不能在接口中創(chuàng)建私有方法。

為了避免冗余代碼和提高重用性,Oracle Corp 將在 Java SE 9 接口中引入私有方法。從 Java SE 9 開始,我們就可以使用 private 關(guān)鍵字在接口中編寫私有和私有靜態(tài)方法。

這些私有方法僅與其他類私有方法一樣,它們之間沒(méi)有區(qū)別。以下是演示:

public interface FilterProcess<T> {

    // java 7 及以前 特性  全局常量 和抽象方法
    public static final String a ="22";
    boolean process(T t);

    // java 8 特性 靜態(tài)方法和默認(rèn)方法
    default void love(){
        System.out.println("java8 特性默認(rèn)方法");
    }
    static void haha(){
        System.out.println("java8 特性靜態(tài)方法");
    }

    // java 9 特性 支持私有方法
    private void java9(){}
}

五. 鉆石操作符升級(jí)

我們知道,Java SE 7 引入了一項(xiàng)新功能:Diamond 運(yùn)算符可避免多余的代碼和冗長(zhǎng)的內(nèi)容,從而提高了可讀性。但是,在 Java SE 8 中,Oracle Corp(Java庫(kù)開發(fā)人員)發(fā)現(xiàn)將 Diamond 運(yùn)算符與匿名內(nèi)部類一起使用時(shí)存在一些限制。他們已解決了這些問(wèn)題,并將其作為 Java 9 的一部分發(fā)布。

// java6 及以前
Map<String,String> map7 = new HashMap<String,String>();
// java7 和 8 <> 沒(méi)有了數(shù)據(jù)類型
Map<String,String> map8 = new HashMap<>();
// java9 添加了匿名內(nèi)部類的功能 后面添加了大括號(hào) {}  可以做一些細(xì)節(jié)的操作
Map<String,String> map9 = new HashMap<>(){};

六. Optional 改進(jìn)

在 Java SE 9 中,Oracle Corp 引入了以下三種方法來(lái)改進(jìn) Optional 功能。

  • stream()
  • ifPresentOrElse()
  • or()

可選 stream() 方法

如果給定的 Optional 對(duì)象中存在一個(gè)值,則此 stream() 方法將返回一個(gè)具有該值的順序 Stream。否則,它將返回一個(gè)空流。

Java 9 中添加的stream() 方法允許我們延遲地處理可選對(duì)象,下面是演示:

jshell> long count = Stream.of(
   ...>     Optional.of(1),
   ...>     Optional.empty(),
   ...>     Optional.of(2)
   ...> ).flatMap(Optional::stream)
   ...>     .count();
   ...> System.out.println(count);
   ...>
count ==> 2
2

(Optiona l 流中包含 3 個(gè) 元素,其中只有 2 個(gè)有值。在使用 flatMap 之后,結(jié)果流中包含了 2 個(gè)值。)

可選 ifPresentOrElse() 方法

我們知道,在 Java SE 8 中,我們可以使用 ifPresent()isPresent()orElse() 方法來(lái)檢查 Optional 對(duì)象并對(duì)其執(zhí)行功能。這個(gè)過(guò)程有些繁瑣,Java SE 9 引入了一種新的方法來(lái)克服此問(wèn)題。

下面是示例:

jshell> Optional<Integer> opt1 = Optional.of(4)
opt1 ==> Optional[4]

jshell> opt1.ifPresentOrElse( x -> System.out.println("Result found: " + x), () -> System.out.println("Not Found."))
Result found: 4

jshell> Optional<Integer> opt2 = Optional.empty()
opt2 ==> Optional.empty

jshell> opt2.ifPresentOrElse( x -> System.out.println("Result found: " + x), () -> System.out.println("Not Found."))
Not Found.

可選 or() 方法

在 Java SE 9 中,使用 or() 方法便捷的返回值。如果 Optional 包含值,則直接返回原值,否則就返回指定的值。or() 方法將 Supplier 作為參數(shù)指定默認(rèn)值。下面是 API 的定義:

public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

下面是有值情況的演示:

jshell> Optional<String> opStr = Optional.of("Rams")
opStr ==> Optional[Rams]

jshell> import java.util.function.*

jshell> Supplier<Optional<String>> supStr = () -> Optional.of("No Name")
supStr ==> $Lambda$67/222624801@23faf8f2

jshell> opStr.or(supStr)
$5 ==> Optional[Rams]

下面是為空情況的演示:

jshell> Optional<String> opStr = Optional.empty()
opStr ==> Optional.empty

jshell> Supplier<Optional<String>> supStr = () -> Optional.of("No Name")
supStr ==> $Lambda$67/222624801@23faf8f2

jshell> opStr.or(supStr)
$7 ==> Optional[No Name]

七. Stream API 改進(jìn)

長(zhǎng)期以來(lái),Streams API 可以說(shuō)是對(duì) Java 標(biāo)準(zhǔn)庫(kù)的最佳改進(jìn)之一。在 Java 9 中,Stream 接口新增加了四個(gè)有用的方法:dropWhile、takeWhile、ofNullable 和 iterate。下面我們來(lái)分別演示一下。

takeWhile() 方法

在 Stream API 中,takeWhile() 方法返回與 Predicate 條件匹配的最長(zhǎng)前綴元素。

它以 Predicate 接口作為參數(shù)。Predicate 是布爾表達(dá)式,它返回 truefalse。對(duì)于有序和無(wú)序流,其行為有所不同。讓我們通過(guò)下面的一些簡(jiǎn)單示例對(duì)其進(jìn)行探討。

Stream API 定義:

default Stream<T> takeWhile(Predicate<? super T> predicate)

有序流示例: -

jshell> Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.takeWhile(x -> x < 4).forEach(a -> System.out.println(a))
1
2
3

無(wú)序流示例: -

jshell> Stream<Integer> stream = Stream.of(1,2,4,5,3,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.takeWhile(x -> x < 4).forEach(a -> System.out.println(a))
1
2

從上面的例子中我們可以看出,takeWhile() 方法在遇到第一個(gè)返回 false 的元素時(shí),它將停止向下遍歷。

dropWhile() 方法

takeWhile() 相對(duì)應(yīng),dropWhile() 用于刪除與條件匹配的最長(zhǎng)前綴元素,并返回其余元素。

Stream API 定義:

default Stream<T> dropWhile(Predicate<? super T> predicate)

有序流示例: -

jshell> Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.dropWhile(x -> x < 4).forEach(a -> System.out.println(a))
4
5
6
7
8
9
10

無(wú)序流示例: -

jshell> Stream<Integer> stream = Stream.of(1,2,4,5,3,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.dropWhile(x -> x < 4).forEach(a -> System.out.println(a))
4
5
3
6
7
8
9
10

iterate() 方法

在 Stream API 中,iterate() 方法能夠返回以 initialValue(第一個(gè)參數(shù))開頭,匹配 Predicate(第二個(gè)參數(shù)),并使用第三個(gè)參數(shù)生成下一個(gè)元素的元素流。

Stream API 定義:

static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

IntStream 迭代示例: -

jshell> IntStream.iterate(2, x -> x < 20, x -> x * x).forEach(System.out::println)
2
4
16

這里,整個(gè)元素流以數(shù)字 2 開始,結(jié)束條件是 < 20,并且在下一次迭代中,遞增值是自身值的平方。

而這在 Java SE 8 中需要輔助 filter 條件才能完成:

jshell> IntStream.iterate(2, x -> x * x).filter(x -> x < 20).forEach(System.out::println)
2
4
16

ofNullable() 方法

在 Stream API 中,ofNullable() 返回包含單個(gè)元素的順序 Stream(如果非null),否則返回空 Stream。

Java SE 9 示例: -

jshell> Stream<Integer> s = Stream.ofNullable(1)
s ==> java.util.stream.ReferencePipeline$Head@1e965684

jshell> s.forEach(System.out::println)
1

jshell> Stream<Integer> s = Stream.ofNullable(null)
s ==> java.util.stream.ReferencePipeline$Head@3b088d51

jshell> s.forEach(System.out::println)

jshell>

注意:Stream 的子接口(如 IntStream、LongStream 等..)都繼承了上述的 4 種方法。

八. 反應(yīng)式流(Reactive Streams)

反應(yīng)式編程的思想最近得到了廣泛的流行。在 Java 平臺(tái)上有流行的反應(yīng)式庫(kù) RxJava 和 Reactor。反應(yīng)式流規(guī)范的出發(fā)點(diǎn)是提供一個(gè)帶非阻塞負(fù)壓( non-blocking backpressure ) 的異步流處理規(guī)范。

Java SE 9 Reactive Streams API 是一個(gè)發(fā)布/訂閱框架,用于實(shí)現(xiàn) Java 語(yǔ)言非常輕松地實(shí)現(xiàn)異步操作,可伸縮和并行應(yīng)用程序。

image

(從上圖中可以很清楚地看到,Processor既可以作為訂閱服務(wù)器,也可以作為發(fā)布服務(wù)器。)

反應(yīng)式流規(guī)范的核心接口已經(jīng)添加到了 Java9 中的 java.util.concurrent.Flow 類中。

反應(yīng)流示例

讓我們從一個(gè)簡(jiǎn)單的示例開始,在該示例中,我們將實(shí)現(xiàn) Flow API Subscriber 接口并使用 SubmissionPublisher 創(chuàng)建發(fā)布者并發(fā)送消息。

流數(shù)據(jù)

假設(shè)我們有一個(gè) Employee 類,它將用于創(chuàng)建要從發(fā)布者發(fā)送到訂閱者的流消息。

package com.wmyskxz.reactive.beans;

public class Employee {

    private int id;
    private String name;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Employee(int i, String s) {
        this.id = i;
        this.name = s;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "[id=" + id + ",name=" + name + "]";
    }
}

我們還有一個(gè)實(shí)用的工具類,可以為我們創(chuàng)建一個(gè)雇員列表:

package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.List;

public class EmpHelper {

    public static List<Employee> getEmps() {
        return List.of(
            new Employee(1, "我沒(méi)有三顆心臟"),
            new Employee(2, "三顆心臟"),
            new Employee(3, "心臟")
        );
    }
}

訂閱者

package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;

public class MySubscriber implements Subscriber<Employee> {

    private Subscription subscription;

    private int counter = 0;

    @Override
    public void onSubscribe(Subscription subscription) {
        System.out.println("Subscribed");
        this.subscription = subscription;
        this.subscription.request(1); // requesting data from publisher
        System.out.println("onSubscribe requested 1 item");
    }

    @Override
    public void onNext(Employee item) {
        System.out.println("Processing Employee " + item);
        counter++;
        this.subscription.request(1);
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("Some error happened");
        e.printStackTrace();
    }

    @Override
    public void onComplete() {
        System.out.println("All Processing Done");
    }

    public int getCounter() {
        return counter;
    }
}
  • Subscription變量以保留引用,以便可以在onNext方法中提出請(qǐng)求。
  • counter變量以保持已處理項(xiàng)目數(shù)的計(jì)數(shù),請(qǐng)注意,其值在 onNext 方法中增加了。在我們的 main 方法中將使用它來(lái)等待執(zhí)行完成,然后再結(jié)束主線程。
  • onSubscribe方法中調(diào)用訂閱請(qǐng)求以開始處理。還要注意,onNext在處理完項(xiàng)目后再次調(diào)用該方法,要求發(fā)布者處理下一個(gè)項(xiàng)目。
  • onErroronComplete在這里沒(méi)有太多作用,但在現(xiàn)實(shí)世界中的場(chǎng)景,他們應(yīng)該被使用時(shí)出現(xiàn)的錯(cuò)誤或資源的清理成功處理完成時(shí)進(jìn)行糾正措施。

反應(yīng)式流測(cè)試程序

我們將SubmissionPublisher作為示例使用 Publisher,因此讓我們看一下反應(yīng)流實(shí)現(xiàn)的測(cè)試程序:

package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.List;
import java.util.concurrent.SubmissionPublisher;

public class MyReactiveApp {

    public static void main(String[] args) throws InterruptedException {

        // Create Publisher
        SubmissionPublisher<Employee> publisher = new SubmissionPublisher<>();

        // Register Subscriber
        MySubscriber subs = new MySubscriber();
        publisher.subscribe(subs);

        List<Employee> emps = EmpHelper.getEmps();

        // Publish items
        System.out.println("Publishing Items to Subscriber");
        for (Employee employee : emps) {
            publisher.submit(employee);
            Thread.sleep(1000);// simulate true environment
        }

        // logic to wait till processing of all messages are over
        while (emps.size() != subs.getCounter()) {
            Thread.sleep(10);
        }
        // close the Publisher
        publisher.close();

        System.out.println("Exiting the app");
    }
}

上面代碼中最重要的部分就是 subscribesubmit 方法的調(diào)用了。另外,我們應(yīng)該在使用完之后關(guān)閉發(fā)布者,以避免任何內(nèi)存泄漏。

當(dāng)執(zhí)行上述程序時(shí),我們將得到以下輸出:

Subscribed
onSubscribe requested 1 item
Publishing Items to Subscriber
Processing Employee [id=1,name=我沒(méi)有三顆心臟]
Processing Employee [id=2,name=三顆心臟]
Processing Employee [id=3,name=心臟]
Exiting the app
All Processing Done

以上所有代碼均可以在「MoreThanJava」項(xiàng)目下的 demo-project 下找到:傳送門

另外,如果您想了解更多內(nèi)容請(qǐng)?jiān)L問(wèn):https://www.journaldev.com/20723/java-9-reactive-streams

九. 進(jìn)程 API

Java 9 增加了 ProcessHandle 接口,可以對(duì)原生進(jìn)程進(jìn)行管理,尤其適合于管理長(zhǎng)時(shí)間運(yùn)行的進(jìn)程。

在使用 ProcessBuilder 來(lái)啟動(dòng)一個(gè)進(jìn)程之后,可以通過(guò) Process.toHandle() 方法來(lái)得到一個(gè) ProcessHandle 對(duì)象的實(shí)例。通過(guò) ProcessHandle 可以獲取到由 ProcessHandle.Info 表示的進(jìn)程的基本信息,如命令行參數(shù)、可執(zhí)行文件路徑和啟動(dòng)時(shí)間等。ProcessHandle 的 onExit() 方法返回一個(gè) CompletableFuture 對(duì)象,可以在進(jìn)程結(jié)束時(shí)執(zhí)行自定義的動(dòng)作。

下面是進(jìn)程 API 的使用示例:

final ProcessBuilder processBuilder = new ProcessBuilder("top")
    .inheritIO();
final ProcessHandle processHandle = processBuilder.start().toHandle();
processHandle.onExit().whenCompleteAsync((handle, throwable) -> {
    if (throwable == null) {
        System.out.println(handle.pid());
    } else {
        throwable.printStackTrace();
    }
});

十. 升級(jí)的 Try-With-Resources

我們知道,Java SE 7 引入了一種新的異常處理結(jié)構(gòu):Try-With-Resources 以自動(dòng)管理資源。這一新聲明的主要目標(biāo)是 “自動(dòng)的更好的資源管理”。

Java SE 9 將對(duì)該語(yǔ)句進(jìn)行一些改進(jìn),以避免更多的冗長(zhǎng)和提高可讀性。

Java SE 7示例

void testARM_Before_Java9() throws IOException{
   BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
   try (BufferedReader reader2 = reader1) {
     System.out.println(reader2.readLine());
   }
}

Java SE 9示例:

void testARM_Java9() throws IOException{
   BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
   try (reader1) {
     System.out.println(reader1.readLine());
   }
}

十一. HTTP / 2

Java 9 提供了一種執(zhí)行 HTTP 調(diào)用的新方法。這種過(guò)期過(guò)期的替代方法是舊的HttpURLConnection。API 也支持 WebSockets 和 HTTP / 2。需要注意的是:新的 HttpClient API 在 Java 9 中以所謂的 incubator module 的形式提供。這意味著該API尚不能保證最終實(shí)現(xiàn) 100%。盡管如此,隨著Java 9的到來(lái),您已經(jīng)可以開始使用此API:

HttpClient client = HttpClient.newHttpClient();

HttpRequest req =
   HttpRequest.newBuilder(URI.create("http://www.google.com"))
              .header("User-Agent","Java")
              .GET()
              .build();


HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());

十二. 多版本兼容 Jar 包

多版本兼容 JAR 功能能讓你創(chuàng)建僅在特定版本的 Java 環(huán)境中運(yùn)行庫(kù)程序時(shí)選擇使用的 class 版本。

通過(guò) --release 參數(shù)指定編譯版本。

具體的變化就是 META-INF 目錄下 MANIFEST.MF 文件新增了一個(gè)屬性:

Multi-Release: true

然后 META-INF 目錄下還新增了一個(gè) versions 目錄,如果是要支持 Java 9,則在 versions 目錄下有 9 的目錄。

multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class

具體的例子可以在這里查看到:https://www.runoob.com/java/java9-multirelease-jar.html,這里不做贅述。

其他更新

改進(jìn)應(yīng)用安全性能

Java 9 新增了 4 個(gè) SHA-3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。另外也增加了通過(guò) java.security.SecureRandom 生成使用 DRBG 算法的強(qiáng)隨機(jī)數(shù)。下面給出了 SHA-3 哈希算法的使用示例:

final MessageDigest instance = MessageDigest.getInstance("SHA3-224");
final byte[] digest = instance.digest("".getBytes());
System.out.println(Hex.encodeHexString(digest));

統(tǒng)一 JVM 日志

Java 9 中 ,JVM 有了統(tǒng)一的日志記錄系統(tǒng),可以使用新的命令行選項(xiàng) -Xlog 來(lái)控制 JVM 上所有組件的日志記錄。該日志記錄系統(tǒng)可以設(shè)置輸出的日志消息的標(biāo)簽、級(jí)別、修飾符和輸出目標(biāo)等。

G1 設(shè)為默認(rèn)回收器實(shí)現(xiàn)

Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合(比如 ParNew + SerialOld),同時(shí)把 G1 設(shè)為默認(rèn)的垃圾回收器實(shí)現(xiàn)(32 位和 64 位系統(tǒng)都是)。 另外,CMS 垃圾回收器已經(jīng)被聲明為廢棄。Java 9 也增加了很多可以通過(guò) jcmd 調(diào)用的診斷命令。

String 底層存儲(chǔ)結(jié)構(gòu)更改

String 底層從 char[] 數(shù)組換位了 byte[]

為了對(duì)字符串采用更節(jié)省空間的內(nèi)部表示,String類的內(nèi)部表示形式從 UTF-16 char數(shù)組更改為byte帶有編碼標(biāo)記字段的數(shù)組。新String類將存儲(chǔ)基于字符串內(nèi)容編碼為 ISO-8859-1 / Latin-1(每個(gè)字符一個(gè)字節(jié))或 UTF-16(每個(gè)字符兩個(gè)字節(jié))的字符。編碼標(biāo)志將指示使用哪種編碼。

(ps: 另外內(nèi)部大部分方法也多了字符編碼的判斷)

CompletableFuture API 的改進(jìn)

在 Java SE 9 中,Oracle Corp 將改進(jìn) CompletableFuture API,以解決 Java SE 8 中提出的一些問(wèn)題。它們將被添加以支持某些延遲和超時(shí),某些實(shí)用程序方法以及更好的子類化。

Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);

這里的 delayExecutor() 是一種靜態(tài)實(shí)用程序方法,用于返回新的 Executor,該 Executor 在給定的延遲后將任務(wù)提交給默認(rèn)的執(zhí)行程序。

I/O 流新特性

java.io.InputStream 中增加了新的方法來(lái)讀取和復(fù)制 InputStream 中包含的數(shù)據(jù)。

  • readAllBytes:讀取 InputStream 中的所有剩余字節(jié)。
  • readNBytes: 從 InputStream 中讀取指定數(shù)量的字節(jié)到數(shù)組中。
  • transferTo:讀取 InputStream 中的全部字節(jié)并寫入到指定的 OutputStream 中 。

下面是新方法的使用示例:

public class TestInputStream {
    private InputStream inputStream;
    private static final String CONTENT = "Hello World";
    @Before
    public void setUp() throws Exception {
        this.inputStream =
            TestInputStream.class.getResourceAsStream("/input.txt");
    }
    @Test
    public void testReadAllBytes() throws Exception {
        final String content = new String(this.inputStream.readAllBytes());
        assertEquals(CONTENT, content);
    }
    @Test
    public void testReadNBytes() throws Exception {
        final byte[] data = new byte[5];
        this.inputStream.readNBytes(data, 0, 5);
        assertEquals("Hello", new String(data));
    }
    @Test
    public void testTransferTo() throws Exception {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        this.inputStream.transferTo(outputStream);
        assertEquals(CONTENT, outputStream.toString());
    }
}

JavaScript 引擎 Nashorn 改進(jìn)

Nashorn 是 Java 8 中引入的新的 JavaScript 引擎。Java 9 中的 Nashorn 已經(jīng)實(shí)現(xiàn)了一些 ECMAScript 6 規(guī)范中的新特性,包括模板字符串、二進(jìn)制和八進(jìn)制字面量、迭代器 和 for..of 循環(huán)和箭頭函數(shù)等。Nashorn 還提供了 API 把 ECMAScript 源代碼解析成抽象語(yǔ)法樹( Abstract Syntax Tree,AST ) ,可以用來(lái)對(duì) ECMAScript 源代碼進(jìn)行分析。

標(biāo)識(shí)符增加限制

JDK 8 之前 String _ = "hello; 這樣的標(biāo)識(shí)符可以使用,JDK 9 之后就不允許使用了。

改進(jìn)的 Javadoc

有時(shí)候,微小的事情會(huì)帶來(lái)很大的不同。您是否之前一直像我一樣一直使用 Google 查找正確的 Javadoc 頁(yè)面?現(xiàn)在將不再需要。Javadoc 現(xiàn)在在 API 文檔本身中包含了搜索功能。另外,Javadoc 輸出現(xiàn)在兼容 HTML 5。另外,您會(huì)注意到每個(gè) Javadoc 頁(yè)面都包含有關(guān)類或接口來(lái)自哪個(gè) JDK 模塊的信息。

image

改進(jìn)的 @Deprecated 注解

注解 @Deprecated 可以標(biāo)記 Java API 狀態(tài),可以是以下幾種:

  • 使用它存在風(fēng)險(xiǎn),可能導(dǎo)致錯(cuò)誤
  • 可能在未來(lái)版本中不兼容
  • 可能在未來(lái)版本中刪除
  • 一個(gè)更好和更高效的方案已經(jīng)取代它。

Java 9 中注解增加了兩個(gè)新元素:sinceforRemoval

  • since: 元素指定已注解的API元素已被棄用的版本。
  • forRemoval: 元素表示注解的 API 元素在將來(lái)的版本中被刪除,應(yīng)該遷移 API。

以下實(shí)例為 Java 9 中關(guān)于 Boolean 類的說(shuō)明文檔,文檔中 @Deprecated 注解使用了 since 屬性:Boolean Class

JavaDoc 關(guān)于 Boolean 的說(shuō)明截取

多分辨率圖像 API

在 Java SE 9 中,Oracle Corp 將引入一個(gè)新的 Multi-Resolution Image API。此 API 中的重要接口是MultiResolutionImage。在 java.awt.image 包中可用。

MultiResolutionImage 封裝了一組具有不同高度和寬度(即不同分辨率)的圖像,并允許我們根據(jù)需求查詢它們。

變量句柄

變量句柄(VarHandle)是對(duì)于一個(gè)變量的強(qiáng)類型引用,或者是一組參數(shù)化定義的變量族,包括了靜態(tài)字段、非靜態(tài)字段、數(shù)組元素等,VarHandle 支持不同訪問(wèn)模型下對(duì)于變量的訪問(wèn),包括簡(jiǎn)單的 read/write 訪問(wèn),volatile read/write 訪問(wèn),以及 CAS 訪問(wèn)。

VarHandle 相比于傳統(tǒng)的對(duì)于變量的并發(fā)操作具有巨大的優(yōu)勢(shì),在 JDK 9 引入了 VarHandle 之后,JUC 包中對(duì)于變量的訪問(wèn)基本上都使用 VarHandle,比如 AQS 中的 CLH 隊(duì)列中使用到的變量等。

了解更多戳這里:https://kknews.cc/code/amqz5on.html

改進(jìn)方法句柄(Method Handle)

java.lang.invoke.MethodHandles 增加了更多的靜態(tài)方法來(lái)創(chuàng)建不同類型的方法句柄:

  • arrayConstructor: 創(chuàng)建指定類型的數(shù)組。
  • arrayLength: 獲取指定類型的數(shù)組的大小。
  • varHandleInvoker 和 varHandleExactInvoker: 調(diào)用 VarHandle 中的訪問(wèn)模式方法。
  • zero: 返回一個(gè)類型的默認(rèn)值。
  • empty: 返回 MethodType 的返回值類型的默認(rèn)值。
  • loop、countedLoop、iteratedLoop、whileLoop 和 doWhileLoop: 創(chuàng)建不同類型的循環(huán),包括 for 循環(huán)、while 循環(huán) 和 do-while 循環(huán)。
  • tryFinally: 把對(duì)方法句柄的調(diào)用封裝在 try-finally 語(yǔ)句中。

提前編譯 AOT

借助 Java 9,特別是JEP 295,JDK 獲得了提前(ahead-of-time,AOT) 編譯器 jaotc。該編譯器使用 OpenJDK 項(xiàng)目 Graal 進(jìn)行后端代碼生成,這樣做的原因如下:

JIT 編譯器速度很快,但是Java程序可能非常龐大,以至于JIT完全預(yù)熱需要很長(zhǎng)時(shí)間。很少使用的Java方法可能根本不會(huì)被編譯,由于重復(fù)的解釋調(diào)用可能會(huì)導(dǎo)致性能下降

原文鏈接:openjdk.java.net/jeps/295

Graal OpenJDK 項(xiàng)目 演示了用純 Java 編寫的編譯器可以生成高度優(yōu)化的代碼。使用此 AOT 編譯器和 Java 9,您可以提前手動(dòng)編譯 Java 代碼。這意味著在執(zhí)行之前生成機(jī)器代碼,而不是像 JIT 編譯器那樣在運(yùn)行時(shí)生成代碼,這是第一種實(shí)驗(yàn)性的方法。

# using the new AOT compiler (jaotc is bundeled within JDK 9 and above)
jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base
 
# with Java 9 you have to manually specify the location of the native code
java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld

這將改善啟動(dòng)時(shí)間,因?yàn)?JIT 編譯器不必?cái)r截程序的執(zhí)行。這種方法的主要缺點(diǎn)是生成的機(jī)器代碼依賴于程序所在的平臺(tái)(Linux,MacOS,windows...)。這可能導(dǎo)致 AOT 編譯代碼與特定平臺(tái)綁定。

image

了解更多戳這里:https://juejin.im/post/6850418120570437646

更多...

完整特性列表:https://openjdk.java.net/projects/jdk9/

參考資料

  1. OpenJDK 官方文檔 - https://openjdk.java.net/projects/jdk9/
  2. Java 9 Modules | JournalDev - https://www.journaldev.com/13106/java-9-modules
  3. JDK 9 新特性詳解 - https://my.oschina.net/mdxlcj/blog/1622984
  4. Java SE 9:Stream API Improvements - https://www.journaldev.com/13204/javase9-stream-api-improvements
  5. 9 NEW FEATURES IN JAVA 9 - https://www.pluralsight.com/blog/software-development/java-9-new-features
  6. Java 9 新特性概述 | IBM - https://developer.ibm.com/zh/articles/the-new-features-of-Java-9/
  7. Java 9 多版本兼容 jar 包 | 菜鳥教程 - https://www.runoob.com/java/java9-multirelease-jar.html

文章推薦

  1. 這都JDK15了,JDK7還不了解? - https://www.wmyskxz.com/2020/08/18/java7-ban-ben-te-xing-xiang-jie/
  2. 全網(wǎng)最通透的 Java 8 版本特性講解 - https://www.wmyskxz.com/2020/08/19/java8-ban-ben-te-xing-xiang-jie/
  3. 你記筆記嗎?關(guān)于最近知識(shí)管理工具革新潮心臟有話要說(shuō) - https://www.wmyskxz.com/2020/08/16/ni-ji-bi-ji-ma-guan-yu-zui-jin-zhi-shi-guan-li-gong-ju-ge-xin-chao-xin-zang-you-hua-yao-shuo/
  4. 黑莓OS手冊(cè)是如何詳細(xì)闡述底層的進(jìn)程和線程模型的? - https://www.wmyskxz.com/2020/07/31/hao-wen-tui-jian-hei-mei-os-shou-ce-shi-ru-he-xiang-xi-chan-shu-di-ceng-de-jin-cheng-he-xian-cheng-mo-xing-de/
  5. 「MoreThanJava」系列文集 - https://www.wmyskxz.com/categories/MoreThanJava/
  • 本文已收錄至我的 Github 程序員成長(zhǎng)系列 【More Than Java】,學(xué)習(xí),不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
  • 個(gè)人公眾號(hào) :wmyskxz,個(gè)人獨(dú)立域名博客:wmyskxz.com,堅(jiān)持原創(chuàng)輸出,下方掃碼關(guān)注,2020,與您共同成長(zhǎng)!
image

非常感謝各位人才能 看到這里,如果覺(jué)得本篇文章寫得不錯(cuò),覺(jué)得 「我沒(méi)有三顆心臟」有點(diǎn)東西 的話,求點(diǎn)贊,求關(guān)注,求分享,求留言!

創(chuàng)作不易,各位的支持和認(rèn)可,就是我創(chuàng)作的最大動(dòng)力,我們下篇文章見(jiàn)!

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