Java8极简手册-03

系列文章链接:

Java8极简手册-01
Java8极简手册-02
Java8极简手册-03
Java8极简手册-04

Optionals

Optionals不是功能接口,而是用于防止NullPointerException的漂亮工具。 这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。

Optional是一个值的简单容器,可以为null或非null。 试想,一个可能返回显示为非null结果,有时却其实任何内容都没有。 所以,我们可以不返回null,在Java 8中可以返回Optional来避免这类尴尬。

1
2
3
4
5
6
7
Optional<String> optional = Optional.of("bam");

optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

Streams

java.util.Stream 表示的是可以在其上执行一个或多个操作的元素序列。流操作分为中间操作和终端操作(可以对应惰性求值和及早求值),终端操作就是返回整个流操作应该返回的值类型,如List、String等具体的类型;而中间操作返回的是自己本身(也就是说它还是一个流),所以中间操作是可以操作一连串的多个方法(使用.)。流有创建的源头,比如List、Map等java.util.Collection 。流操作可以被并行或者顺序执行。

让我们先来看看顺序执行的流是怎么样工作的,首先我们创建一个字符串类型的List的源。

1
2
3
4
5
6
7
8
9
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Java8已经扩展了Collections功能(如Collection.stream()Collection.parallelStream()),使得我们可以非常轻松地创建流。接下来我们来介绍下一些常用的流操作方法。

Filter

Filter接受谓词(predicate)以过滤流中所有元素。 此操作是一类中间操作,如果需要得到结果,我们需要调用另一个操作forEach来获得。 ForEach接受为Filter流中的每个元素执行的使用者,ForEach是一个终端操作。 它是void返回值,所以我们不能调用另一个流操作,流操作就结束了。

1
2
3
4
5
6
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);

// "aaa2", "aaa1"

Sorted

Sorted是一个中间操作,它返回流的排序视图。 除非您传递自定义Comparator,否则元素按自然顺序排序。

1
2
3
4
5
6
7
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);

// "aaa1", "aaa2"

请记住,sorted只会创建流的排序视图,而不会支持修改集合内的顺序,因此stringCollection的顺序是不变的:

1
2
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map

中间操作map通过给定函数将每个元素转换为另一个对象。 以下示例将每个字符串转换为大写字符串。 但您也可以使用map将每个对象转换为另一种类型。 返回结果中流的泛型类型取决于传递给map的函数的泛型类型。

1
2
3
4
5
6
7
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

可以使用各种匹配操作来检查某个谓词(predicate)是否与流匹配。 所有这些操作都是终端操作并返回一个布尔结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA); // true

boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA); // false

boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ); // true

Count

Count是一个终端操作,返回流中元素的个数,返回值是long

1
2
3
4
5
6
7
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();

System.out.println(startsWithB); // 3

Reduce

该终端操作使用给定的功能执行流的元素的归并(我更习惯用归并,而不是减少)。 返回结果是一个Optional来存储归并的值。(译者注reduce可能是一个较难理解的操作,其实它内部有一个累加器,会将内部元素做累加操作,如,第一次是1+2,第二次是(1+2)+3,持续下去。当然里面的操作不一定都是+,还可以是其他的,如球平均等等。)

1
2
3
4
5
6
7
8
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

并行流(Parallel Streams)

如上所述,Streams可以是顺序执行的也可以是并行的。 对顺序流的操作在单个线程上执行,而并行流上的操作在多个线程上同时执行。

以下示例演示了如何使用并行流来简单地提高性能。

首先,我们创建一个大的独特元素列表:

1
2
3
4
5
6
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}

现在我们测量对此集合的流进行排序所需的时间。

Sequential Sort

1
2
3
4
5
6
7
8
9
10
11
long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// 顺序排序操作花费: 899 ms

Parallel Sort

1
2
3
4
5
6
7
8
9
10
11
long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// 并行排序花费: 472 ms

正如您所看到的,两个代码片段几乎完全相同,但并行排序大约快了50%. 所有的改动仅仅是将stream() 改成 parallelStream().