JDK1.8的新特性

# JDK1.8的新特性

## 1. 前言

JDK1.8已經(jīng)發(fā)布很久了,在很多企業(yè)中都已經(jīng)在使用。并且Spring5、SpringBoot2.0都推薦使用JDK1.8以上版本。所以我們必須與時俱進,擁抱變化。

Jdk8這個版本包含語言、編譯器、庫、工具和JVM等方面的十多個新特性。在本文中我們將學(xué)習(xí)以下方面的新特性:

- [Lambda表達式](#2. Lambda表達式)

- [函數(shù)式接口](#3. 函數(shù)式接口)

- [方法引用](#4. 方法引用)

- [接口的默認(rèn)方法和靜態(tài)方法](#5. 接口的默認(rèn)方法和靜態(tài)方法)

- [Optional](#6. Optional)

- [Streams](#7. Streams)

- [并行數(shù)組](#8. 并行數(shù)組)

## 2. Lambda表達式

函數(shù)式編程

Lambda 表達式,也可稱為閉包,它是推動 Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)。可以使代碼變的更加簡潔緊湊。

### 2.1 基本語法:

```

(參數(shù)列表) -> {代碼塊}

```

需要注意:

- 參數(shù)類型可省略,編譯器可以自己推斷

- 如果只有一個參數(shù),圓括號可以省略

- 代碼塊如果只是一行代碼,大括號也可以省略

- 如果代碼塊是一行,且是有結(jié)果的表達式,`return`可以省略

**注意:**事實上,把Lambda表達式可以看做是匿名內(nèi)部類的一種簡寫方式。當(dāng)然,前提是這個匿名內(nèi)部類對應(yīng)的必須是接口,而且接口中必須只有一個函數(shù)!Lambda表達式就是直接編寫函數(shù)的:參數(shù)列表、代碼體、返回值等信息,**`用函數(shù)來代替完整的匿名內(nèi)部類`**!

### 2.2 用法示例

##### 示例1:多個參數(shù)

準(zhǔn)備一個集合:

```java

// 準(zhǔn)備一個集合

List<Integer> list = Arrays.asList(10, 5, 25, -15, 20);

```

假設(shè)我們要對集合排序,我們先看JDK7的寫法,需要通過匿名內(nèi)部類來構(gòu)造一個`Comparator`:

```java

// Jdk1.7寫法

Collections.sort(list,new Comparator<Integer>() {

? ? @Override

? ? public int compare(Integer o1, Integer o2) {

? ? ? ? return o1 - o2;

? ? }

});

System.out.println(list);// [-15, 5, 10, 20, 25]

```

如果是jdk8,我們可以使用新增的集合API:`sort(Comparator c)`方法,接收一個比較器,我們用Lambda來代替`Comparator` 的匿名內(nèi)部類:

```java

// Jdk1.8寫法,參數(shù)列表的數(shù)據(jù)類型可省略:

list.sort((i1,i2) -> { return i1 - i2;});

System.out.println(list);// [-15, 5, 10, 20, 25]

```

對比一下`Comparator`中的`compare()`方法,你會發(fā)現(xiàn):這里編寫的Lambda表達式,恰恰就是`compare()`方法的簡寫形式,JDK8會把它編譯為匿名內(nèi)部類。是不是簡單多了!

別著急,我們發(fā)現(xiàn)這里的代碼塊只有一行代碼,符合前面的省略規(guī)則,我們可以簡寫為:

```java

// Jdk8寫法

// 因為代碼塊是一個有返回值的表達式,可以省略大括號以及return

list.sort((i1,i2) -> i1 - i2);

```

##### 示例2:單個參數(shù)

還以剛才的集合為例,現(xiàn)在我們想要遍歷集合中的元素,并且打印。

先用jdk1.7的方式:

```java

// JDK1.7遍歷并打印集合

for (Integer i : list) {

? ? System.out.println(i);

}

```

jdk1.8給集合添加了一個方法:`foreach()` ,接收一個對元素進行操作的函數(shù):

```java

// JDK1.8遍歷并打印集合,因為只有一個參數(shù),所以我們可以省略小括號:

list.forEach(i -> System.out.println(i));

```

##### 實例3:把Lambda賦值給變量

Lambda表達式的實質(zhì)其實還是匿名內(nèi)部類,所以我們其實可以把Lambda表達式賦值給某個變量。

```java

// 將一個Lambda表達式賦值給某個接口:

Runnable task = () -> {

? ? // 這里其實是Runnable接口的匿名內(nèi)部類,我們在編寫run方法。

? ? System.out.println("hello lambda!");

};

new Thread(task).start();

```

不過上面的用法很少見,一般都是直接把Lambda作為參數(shù)。

##### 示例4:隱式final

Lambda表達式的實質(zhì)其實還是匿名內(nèi)部類,而匿名內(nèi)部類在訪問外部局部變量時,要求變量必須聲明為`final`!不過我們在使用Lambda表達式時無需聲明`final`,這并不是說違反了匿名內(nèi)部類的規(guī)則,因為Lambda底層會隱式的把變量設(shè)置為`final`,在后續(xù)的操作中,一定不能修改該變量:

正確示范:

```java

// 定義一個局部變量

int num = -1;

Runnable r = () -> {

? ? // 在Lambda表達式中使用局部變量num,num會被隱式聲明為final

? ? System.out.println(num);

};

new Thread(r).start();// -1

```

錯誤案例:

```java

// 定義一個局部變量

int num = -1;

Runnable r = () -> {

? ? // 在Lambda表達式中使用局部變量num,num會被隱式聲明為final,不能進行任何修改操作

? ? System.out.println(num++);

};

new Thread(r).start();//報錯

```

## 3. 函數(shù)式接口

經(jīng)過前面的學(xué)習(xí),相信大家對于Lambda表達式已經(jīng)有了初步的了解??偨Y(jié)一下:

- Lambda表達式是接口的匿名內(nèi)部類的簡寫形式

- 接口必須滿足:內(nèi)部只有一個函數(shù)

其實這樣的接口,我們稱為函數(shù)式接口,我們學(xué)過的`Runnable`、`Comparator`都是函數(shù)式接口的典型代表。但是在實踐中,函數(shù)接口是非常脆弱的,只要有人在接口里添加多一個方法,那么這個接口就不是函數(shù)接口了,就會導(dǎo)致編譯失敗。Java 8提供了一個特殊的注解`@FunctionalInterface`來克服上面提到的脆弱性并且顯示地表明函數(shù)接口。而且jdk8版本中,對很多已經(jīng)存在的接口都添加了`@FunctionalInterface`注解,例如`Runnable`接口:

![1](runnable.png)

另外,Jdk8默認(rèn)提供了一些函數(shù)式接口供我們使用:

### 3.1 Function類型接口

```java

@FunctionalInterface

public interface Function<T, R> {

// 接收一個參數(shù)T,返回一個結(jié)果R

? ? R apply(T t);

}

```

Function代表的是有參數(shù),有返回值的函數(shù)。還有很多類似的Function接口:

| 接口名? ? ? ? ? ? ? ? | 描述? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| :--------------------- | ----------------------------------------------- |

| `BiFunction<T,U,R>`? ? | 接收兩個T和U類型的參數(shù),并且返回R類型結(jié)果的函數(shù) |

| `DoubleFunction<R>`? ? | 接收double類型參數(shù),并且返回R類型結(jié)果的函數(shù)? ? |

| `IntFunction<R>`? ? ? | 接收int類型參數(shù),并且返回R類型結(jié)果的函數(shù)? ? ? ? |

| `LongFunction<R>`? ? ? | 接收long類型參數(shù),并且返回R類型結(jié)果的函數(shù)? ? ? |

| `ToDoubleFunction<T>`? | 接收T類型參數(shù),并且返回double類型結(jié)果? ? ? ? ? |

| `ToIntFunction<T>`? ? | 接收T類型參數(shù),并且返回int類型結(jié)果? ? ? ? ? ? ? |

| `ToLongFunction<T>`? ? | 接收T類型參數(shù),并且返回long類型結(jié)果? ? ? ? ? ? |

| `DoubleToIntFunction`? | 接收double類型參數(shù),返回int類型結(jié)果? ? ? ? ? ? |

| `DoubleToLongFunction` | 接收double類型參數(shù),返回long類型結(jié)果? ? ? ? ? ? |

看出規(guī)律了嗎?這些都是一類函數(shù)接口,在Function基礎(chǔ)上衍生出的,要么明確了參數(shù)不確定返回結(jié)果,要么明確結(jié)果不知道參數(shù)類型,要么兩者都知道。

### 3.2 Consumer系列

```java

@FunctionalInterface

public interface Consumer<T> {

// 接收T類型參數(shù),不返回結(jié)果

? ? void accept(T t);

}

```

Consumer系列與Function系列一樣,有各種衍生接口,這里不一一列出了。不過都具備類似的特征:那就是不返回任何結(jié)果。

### 3.3 Predicate系列

```java

@FunctionalInterface

public interface Predicate<T> {

// 接收T類型參數(shù),返回boolean類型結(jié)果

? ? boolean test(T t);

}

```

Predicate系列參數(shù)不固定,但是返回的一定是boolean類型。

### 3.4 Supplier系列

```java

@FunctionalInterface

public interface Supplier<T> {

// 無需參數(shù),返回一個T類型結(jié)果

? ? T get();

}

```

Supplier系列,英文翻譯就是“供應(yīng)者”,顧名思義:只產(chǎn)出,不收取。所以不接受任何參數(shù),返回T類型結(jié)果。

## 4. 方法引用

方法引用使得開發(fā)者可以將已經(jīng)存在的方法作為變量來傳遞使用。方法引用可以和Lambda表達式配合使用。

### 4.1 語法:

總共有四類方法引用:

| 語法? ? ? ? ? ? ? ? ? | 描述? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| ---------------------- | -------------------------------- |

| 類名::靜態(tài)方法名? ? ? | 類的靜態(tài)方法的引用? ? ? ? ? ? ? |

| 類名::非靜態(tài)方法名? ? | 類的非靜態(tài)方法的引用? ? ? ? ? ? |

| 實例對象::非靜態(tài)方法名 | 類的指定實例對象的非靜態(tài)方法引用 |

| 類名::new? ? ? ? ? ? ? | 類的構(gòu)造方法引用? ? ? ? ? ? ? ? |

### 4.2 示例

首先我們編寫一個集合工具類,提供一個方法:

```java

? ? public class CollectionUtil{

? ? ? ? /**

? ? ? ? * 利用function將list集合中的每一個元素轉(zhuǎn)換后形成新的集合返回

? ? ? ? * @param list 要轉(zhuǎn)換的源集合

? ? ? ? * @param function 轉(zhuǎn)換元素的方式

? ? ? ? * @param <T> 源集合的元素類型

? ? ? ? * @param <R> 轉(zhuǎn)換后的元素類型

? ? ? ? * @return

? ? ? ? */

? ? ? ? public static <T,R> List<R> convert(List<T> list, Function<T,R> function){

? ? ? ? ? ? List<R> result = new ArrayList<>();

? ? ? ? ? ? list.forEach(t -> result.add(function.apply(t)));

? ? ? ? ? ? return result;

? ? ? ? }

? ? }

```

可以看到這個方法接收兩個參數(shù):

- `List<T> list`:需要進行轉(zhuǎn)換的集合

- `Function<T,R>`:函數(shù)接口,接收T類型,返回R類型。用這個函數(shù)接口對list中的元素T進行轉(zhuǎn)換,變?yōu)镽類型

接下來,我們看具體案例:

#### 4.2.1 類的靜態(tài)方法引用

```java

List<Integer> list = Arrays.asList(1000, 2000, 3000);

```

我們需要把這個集合中的元素轉(zhuǎn)為十六進制保存,需要調(diào)用`Integer.toHexString()`方法:

```java

public static String toHexString(int i) {

? ? return toUnsignedString0(i, 4);

}

```

這個方法接收一個 i 類型,返回一個`String`類型,可以用來構(gòu)造一個`Function`的函數(shù)接口:

我們先按照Lambda原始寫法,傳入的Lambda表達式會被編譯為`Function`接口,接口中通過`Integer.toHexString(i)`對原來集合的元素進行轉(zhuǎn)換:

```java

// 通過Lambda表達式實現(xiàn)

List<String> hexList = CollectionUtil.convert(list, i -> Integer.toHexString(i));

System.out.println(hexList);// [3e8, 7d0, bb8]

```

上面的Lambda表達式代碼塊中,只有對`Integer.toHexString()`方法的引用,沒有其它代碼,因此我們可以直接把方法作為參數(shù)傳遞,由編譯器幫我們處理,這就是靜態(tài)方法引用:

```java

// 類的靜態(tài)方法引用

List<String> hexList = CollectionUtil.convert(list, Integer::toHexString;

System.out.println(hexList);// [3e8, 7d0, bb8]

```

#### 4.2.2 類的非靜態(tài)方法引用

接下來,我們把剛剛生成的`String`集合`hexList`中的元素都變成大寫,需要借助于String類的toUpperCase()方法:

```java

public String toUpperCase() {

? ? return toUpperCase(Locale.getDefault());

}

```

這次是非靜態(tài)方法,不能用類名調(diào)用,需要用實例對象,因此與剛剛的實現(xiàn)有一些差別,我們接收集合中的每一個字符串`s`。但與上面不同然后`s`不是`toUpperCase()`的參數(shù),而是調(diào)用者:

```java

// 通過Lambda表達式,接收String數(shù)據(jù),調(diào)用toUpperCase()

List<String> upperList = CollectionUtil.convert(hexList, s -> s.toUpperCase());

System.out.println(upperList);// [3E8, 7D0, BB8]

```

因為代碼體只有對`toUpperCase()`的調(diào)用,所以可以把方法作為參數(shù)引用傳遞,依然可以簡寫:

```java

// 類的成員方法

List<String> upperList = CollectionUtil.convert(hexList, String::toUpperCase);

System.out.println(upperList);// [3E8, 7D0, BB8]

```

#### 4.2.3 指定實例的非靜態(tài)方法引用

下面一個需求是這樣的,我們先定義一個數(shù)字`Integer num = 2000`,然后用這個數(shù)字和集合中的每個數(shù)字進行比較,比較的結(jié)果放入一個新的集合。比較對象,我們可以用`Integer`的`compareTo`方法:

```java

public int compareTo(Integer anotherInteger) {

? ? return compare(this.value, anotherInteger.value);

}

```

先用Lambda實現(xiàn),

```java

List<Integer> list = Arrays.asList(1000, 2000, 3000);

// 某個對象的成員方法

Integer num = 2000;

List<Integer> compareList = CollectionUtil.convert(list, i -> num.compareTo(i));

System.out.println(compareList);// [1, 0, -1]

```

與前面類似,這里L(fēng)ambda的代碼塊中,依然只有對`num.compareTo(i)`的調(diào)用,所以可以簡寫。但是,需要注意的是,這次方法的調(diào)用者不是集合的元素,而是一個外部的局部變量`num`,因此不能使用 `Integer::compareTo`,因為這樣是無法確定方法的調(diào)用者。要指定調(diào)用者,需要用 `對象::方法名`的方式:

```java

// 某個對象的成員方法

Integer num = 2000;

List<Integer> compareList = CollectionUtil.convert(list, num::compareTo);

System.out.println(compareList);// [1, 0, -1]

```

#### 4.2.4 構(gòu)造函數(shù)引用

最后一個場景:把集合中的數(shù)字作為毫秒值,構(gòu)建出`Date`對象并放入集合,這里我們就需要用到Date的構(gòu)造函數(shù):

```java

/**

? * @param? date? the milliseconds since January 1, 1970, 00:00:00 GMT.

? * @see? ? java.lang.System#currentTimeMillis()

? */

public Date(long date) {

? ? fastTime = date;

}

```

我們可以接收集合中的每個元素,然后把元素作為`Date`的構(gòu)造函數(shù)參數(shù):

```java

// 將數(shù)值類型集合,轉(zhuǎn)為Date類型

List<Date> dateList = CollectionUtil.convert(list, i -> new Date(i));

// 這里遍歷元素后需要打印,因此直接把println作為方法引用傳遞了

dateList.forEach(System.out::println);

```

上面的Lambda表達式實現(xiàn)方式,代碼體只有`new Date()`一行代碼,因此也可以采用方法引用進行簡寫。但問題是,構(gòu)造函數(shù)沒有名稱,我們只能用`new`關(guān)鍵字來代替:

```java

// 構(gòu)造方法

List<Date> dateList = CollectionUtil.convert(list, Date::new);

dateList.forEach(System.out::println);

```

注意兩點:

- 上面代碼中的System.out::println 其實是 指定對象System.out的非靜態(tài)方法println的引用

- 如果構(gòu)造函數(shù)有多個,可能無法區(qū)分導(dǎo)致傳遞失敗

## 5. 接口的默認(rèn)方法和靜態(tài)方法

Java 8使用兩個新概念擴展了接口的含義:默認(rèn)方法和靜態(tài)方法。

### 5.1 默認(rèn)方法

默認(rèn)方法使得開發(fā)者可以在 不破壞二進制兼容性的前提下,往現(xiàn)存接口中添加新的方法,即不強制那些實現(xiàn)了該接口的類也同時實現(xiàn)這個新加的方法。

默認(rèn)方法和抽象方法之間的區(qū)別在于抽象方法需要實現(xiàn),而默認(rèn)方法不需要。接口提供的默認(rèn)方法會被接口的實現(xiàn)類繼承或者覆寫,例子代碼如下:

```java

private interface Defaulable {

? ? // Interfaces now allow default methods, the implementer may or

? ? // may not implement (override) them.

? ? default String notRequired() {

? ? ? ? return "Default implementation";

? ? }? ? ? ?

}

private static class DefaultableImpl implements Defaulable {

}

private static class OverridableImpl implements Defaulable {

? ? @Override

? ? public String notRequired() {

? ? ? ? return "Overridden implementation";

? ? }

}

```

Defaulable接口使用關(guān)鍵字default定義了一個默認(rèn)方法notRequired()。DefaultableImpl類實現(xiàn)了這個接口,同時默認(rèn)繼承了這個接口中的默認(rèn)方法;OverridableImpl類也實現(xiàn)了這個接口,但覆寫了該接口的默認(rèn)方法,并提供了一個不同的實現(xiàn)。

### 5.2 靜態(tài)方法

Java 8帶來的另一個有趣的特性是在接口中可以定義靜態(tài)方法,我們可以直接用接口調(diào)用這些靜態(tài)方法。例子代碼如下:

```java

private interface DefaulableFactory {

? ? // Interfaces now allow static methods

? ? static Defaulable create( Supplier< Defaulable > supplier ) {

? ? ? ? return supplier.get();

? ? }

}

```

下面的代碼片段整合了默認(rèn)方法和靜態(tài)方法的使用場景:

```java

public static void main( String[] args ) {

? ? // 調(diào)用接口的靜態(tài)方法,并且傳遞DefaultableImpl的構(gòu)造函數(shù)引用來構(gòu)建對象

? ? Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );

? ? System.out.println( defaulable.notRequired() );

// 調(diào)用接口的靜態(tài)方法,并且傳遞OverridableImpl的構(gòu)造函數(shù)引用來構(gòu)建對象

? ? defaulable = DefaulableFactory.create( OverridableImpl::new );

? ? System.out.println( defaulable.notRequired() );

}

```

這段代碼的輸出結(jié)果如下:

```

Default implementation

Overridden implementation

```

由于JVM上的默認(rèn)方法的實現(xiàn)在字節(jié)碼層面提供了支持,因此效率非常高。默認(rèn)方法允許在不打破現(xiàn)有繼承體系的基礎(chǔ)上改進接口。該特性在官方庫中的應(yīng)用是:給`java.util.Collection`接口添加新方法,如`stream()`、`parallelStream()`、`forEach()`和`removeIf()`等等。

盡管默認(rèn)方法有這么多好處,但在實際開發(fā)中應(yīng)該謹(jǐn)慎使用:在復(fù)雜的繼承體系中,默認(rèn)方法可能引起歧義和編譯錯誤。如果你想了解更多細節(jié),可以參考官方文檔。

## 6. Optional

Java應(yīng)用中最常見的bug就是空值異常。

`Optional`僅僅是一個容器,可以存放T類型的值或者`null`。它提供了一些有用的接口來避免顯式的`null`檢查,可以參考Java 8官方文檔了解更多細節(jié)。

接下來看一點使用Optional的例子:可能為空的值或者某個類型的值:

```java

Optional< String > fullName = Optional.ofNullable( null );

System.out.println( "Full Name is set? " + fullName.isPresent() );? ? ? ?

System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );

System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

```

如果`Optional`實例持有一個非空值,則`isPresent()`方法返回`true`,否則返回`false`;如果`Optional`實例持有`null`,`orElseGet()`方法可以接受一個lambda表達式生成的默認(rèn)值;`map()`方法可以將現(xiàn)有的`Optional`實例的值轉(zhuǎn)換成新的值;`orElse()`方法與`orElseGet()`方法類似,但是在持有null的時候返回傳入的默認(rèn)值,而不是通過Lambda來生成。

上述代碼的輸出結(jié)果如下:

```

Full Name is set? false

Full Name: [none]

Hey Stranger!

```

再看下另一個簡單的例子:

```java

Optional< String > firstName = Optional.of( "Tom" );

System.out.println( "First Name is set? " + firstName.isPresent() );? ? ? ?

System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );

System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

System.out.println();

```

這個例子的輸出是:

```

First Name is set? true

First Name: Tom

Hey Tom!

```

如果想了解更多的細節(jié),請參考官方文檔。

## 7. Streams

新增的Stream API(java.util.stream)將生成環(huán)境的函數(shù)式編程引入了Java庫中。這是目前為止最大的一次對Java庫的完善,以便開發(fā)者能夠?qū)懗龈佑行?、更加簡潔和緊湊的代碼。

Steam API極大得簡化了集合操作(后面我們會看到不止是集合),首先看下這個叫Task的類:

```java

public class Streams? {

? ? private enum Status {

? ? ? ? OPEN, CLOSED

? ? };

? ? private static final class Task {

? ? ? ? private final Status status;

? ? ? ? private final Integer points;

? ? ? ? Task( final Status status, final Integer points ) {

? ? ? ? ? ? this.status = status;

? ? ? ? ? ? this.points = points;

? ? ? ? }

? ? ? ? public Integer getPoints() {

? ? ? ? ? ? return points;

? ? ? ? }

? ? ? ? public Status getStatus() {

? ? ? ? ? ? return status;

? ? ? ? }

? ? ? ? @Override

? ? ? ? public String toString() {

? ? ? ? ? ? return String.format( "[%s, %d]", status, points );

? ? ? ? }

? ? }

}

```

Task類有一個points屬性,另外還有兩種狀態(tài):OPEN或者CLOSED。現(xiàn)在假設(shè)有一個task集合:

```java

final Collection< Task > tasks = Arrays.asList(

? ? new Task( Status.OPEN, 5 ),

? ? new Task( Status.OPEN, 13 ),

? ? new Task( Status.CLOSED, 8 )

);

```

首先看一個問題:在這個task集合中一共有多少個OPEN狀態(tài)的?計算出它們的points屬性和。在Java 8之前,要解決這個問題,則需要使用foreach循環(huán)遍歷task集合;但是在Java 8中可以利用steams解決:包括一系列元素的列表,并且支持順序和并行處理。

```java

// Calculate total points of all active tasks using sum()

final long totalPointsOfOpenTasks = tasks

? ? .stream()

? ? .filter( task -> task.getStatus() == Status.OPEN )

? ? .mapToInt( Task::getPoints )

? ? .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );

```

運行這個方法的控制臺輸出是:

```

Total points: 18

```

這里有很多知識點值得說。首先,`tasks`集合被轉(zhuǎn)換成`steam`表示;其次,在`steam`上的`filter`操作會過濾掉所有`CLOSED`的`task`;第三,`mapToInt`操作基于`tasks`集合中的每個`task`實例的`Task::getPoints`方法將`task`流轉(zhuǎn)換成`Integer`集合;最后,通過`sum`方法計算總和,得出最后的結(jié)果。

在學(xué)習(xí)下一個例子之前,還需要記住一些steams(點此更多細節(jié))的知識點。Steam之上的操作可分為中間操作和晚期操作。

中間操作會返回一個新的steam——執(zhí)行一個中間操作(例如filter)并不會執(zhí)行實際的過濾操作,而是創(chuàng)建一個新的steam,并將原steam中符合條件的元素放入新創(chuàng)建的steam。

晚期操作(例如forEach或者sum),會遍歷steam并得出結(jié)果或者附帶結(jié)果;在執(zhí)行晚期操作之后,steam處理線已經(jīng)處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對steam進行遍歷。

steam的另一個價值是創(chuàng)造性地支持并行處理(parallel processing)。對于上述的tasks集合,我們可以用下面的代碼計算所有task的points之和:

```java

// Calculate total points of all tasks

final double totalPoints = tasks

? .stream()

? .parallel()

? .map( task -> task.getPoints() ) // or map( Task::getPoints )

? .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );

```

這里我們使用parallel方法并行處理所有的task,并使用reduce方法計算最終的結(jié)果??刂婆_輸出如下:

```

Total points(all tasks): 26.0

```

對于一個集合,經(jīng)常需要根據(jù)某些條件對其中的元素分組。利用steam提供的API可以很快完成這類任務(wù),代碼如下:

```java

// Group tasks by their status

final Map< Status, List< Task > > map = tasks

? ? .stream()

? ? .collect( Collectors.groupingBy( Task::getStatus ) );

System.out.println( map );

```

控制臺的輸出如下:

```java

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

```

最后一個關(guān)于tasks集合的例子問題是:如何計算集合中每個任務(wù)的點數(shù)在集合中所占的比重,具體處理的代碼如下:

```java

// Calculate the weight of each tasks (as percent of total points)

final Collection< String > result = tasks

? ? .stream()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Stream< String >

? ? .mapToInt( Task::getPoints )? ? ? ? ? ? ? ? ? ? // IntStream

? ? .asLongStream()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // LongStream

? ? .mapToDouble( points -> points / totalPoints )? // DoubleStream

? ? .boxed()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Stream< Double >

? ? .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream

? ? .mapToObj( percentage -> percentage + "%" )? ? ? // Stream< String>

? ? .collect( Collectors.toList() );? ? ? ? ? ? ? ? // List< String >

System.out.println( result );

```

控制臺輸出結(jié)果如下:

```

[19%, 50%, 30%]

```

最后,正如之前所說,Steam API不僅可以作用于Java集合,傳統(tǒng)的IO操作(從文件或者網(wǎng)絡(luò)一行一行得讀取數(shù)據(jù))可以受益于steam處理,這里有一個小例子:

```java

final Path path = new File( filename ).toPath();

try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {

? ? lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );

}

```

Stream的方法`onClose()` 返回一個等價的有額外句柄的Stream,當(dāng)Stream的`close()`方法被調(diào)用的時候這個句柄會被執(zhí)行。Stream API、Lambda表達式還有接口默認(rèn)方法和靜態(tài)方法支持的方法引用,是Java 8對軟件開發(fā)的現(xiàn)代范式的響應(yīng)。

## 8. 并行數(shù)組

Java8版本新增了很多新的方法,用于支持并行數(shù)組處理。最重要的方法是`parallelSort()`,可以顯著加快多核機器上的數(shù)組排序。下面的例子論證了parallexXxx系列的方法:

```java

package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;

import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {

? ? public static void main( String[] args ) {

? ? ? ? long[] arrayOfLong = new long [ 20000 ];? ? ? ?

? ? ? ? Arrays.parallelSetAll( arrayOfLong,

? ? ? ? ? ? index -> ThreadLocalRandom.current().nextInt( 1000000 ) );

? ? ? ? Arrays.stream( arrayOfLong ).limit( 10 ).forEach(

? ? ? ? ? ? i -> System.out.print( i + " " ) );

? ? ? ? System.out.println();

? ? ? ? Arrays.parallelSort( arrayOfLong );? ? ? ?

? ? ? ? Arrays.stream( arrayOfLong ).limit( 10 ).forEach(

? ? ? ? ? ? i -> System.out.print( i + " " ) );

? ? ? ? System.out.println();

? ? }

}

```

上述這些代碼使用parallelSetAll()方法生成20000個隨機數(shù),然后使用parallelSort()方法進行排序。這個程序會輸出亂序數(shù)組和排序數(shù)組的前10個元素。上述例子的代碼輸出的結(jié)果是:

```

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378

Sorted: 39 220 263 268 325 607 655 678 723 793

```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容