1. 什么是流
1.1 概念
Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。
1.2 特点
- 流并不存储其元素
- 流的操作不会修改其数据源
- 流的操作是尽可能惰性执行的,就是说到了非要有结果的时候,它才会执行
2. 流的操作步骤
如Demo所示,这个工作流是操作流时的典型流程,其统计了“alice.txt”文件中单词字母长度大于10的单词数量。我们建立了一个包含三个阶段的操作管道:
- 创建一个流
- 指定初始流转换为其他流的中间操作,可能包含多个步骤
- 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作,从此之后这个流就再也不能用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package example1;
import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.List;
public class Demo { public static void main(String[] args) throws IOException { String contents = new String(Files.readAllBytes( Paths.get("alice.txt")), StandardCharsets.UTF_8); List<String> words = Arrays.asList(contents.split("\\PL+")); long count = words.parallelStream() .filter(w -> w.length() > 10) .count(); System.out.println(count); } }
|
3. 流的创建
Collection接口的stream方法可以将任何集合转换成流。如果有一个数组就使用静态的Stream.of方法。
1 2 3 4 5 6 7 8 9 10
| List<String> words = ...; Stream<String> s1 = words.stream();
String[] array = ...; Stream<String> s2 = Stream.of(array);
Stream<String> s3 = Stream.of(array, 0, 5);
Stream<String> s4 = Stream.empty();
|
创建无限流有两种方法:
- 调用Stream.generate()方法
- 调用Stream.iterate()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package example2;
import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream;
public class Create { public static void main(String[] args) throws IOException { Stream<String> words = Stream.of(new String( Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8) .split("\\PL+")); show("words", words);
Stream<String> song = Stream.of("gently", "down", "the", "stream"); show("song", song);
Stream<String> empty = Stream.empty(); show("empty", empty);
Stream<String> echos = Stream.generate(() -> "Echo"); show("echos", echos);
Stream<Double> randoms = Stream.generate(Math::random); show("randoms", randoms);
Stream<Integer> integers = Stream.iterate(0, n -> n + 1); show("integers", integers);
Stream<String> wordsAnotherWay = Pattern.compile("\\PL+") .splitAsStream(new String( Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8)); show("wordsAnotherWay", wordsAnotherWay);
Stream<String> lines = Files.lines(Paths.get("alice.txt")); show("lines", lines); }
public static <T> void show(String title, Stream<T> stream) { final int SIZE = 10; List<T> firstElements = stream.limit(10).collect(Collectors.toList()); System.out.print(title + ": "); for (int i = 0; i < firstElements.size(); i++) { if (i > 0) { System.out.print(","); } if (i < SIZE) { System.out.print(firstElements.get(i)); } else { System.out.print("..."); } } System.out.println(); } }
|
3. 流的转换
流的转换会产生一个新的流,它的元素派生自另外一个流中的元素。
3.1 filter、map、flatMap方法
1 2 3 4 5 6
| Stream<T> filter(Predicate<? super T> predicate)
<R> Stream<R> map(Function<? super T,? extends R> mapper)
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
|
filter方法转换一个流,它的元素与给定的条件相匹配
map方法转换一个流,它的作用是按照给定的函数来转换流中的值
flatMap方法转换一个流,它的作用是将当前流中所有元素产生的结果连接到一起
3.2 抽取子流和连接流
抽取子流有两个方法:limit()和skip()
1 2 3 4 5 6 7
|
Stream<Double> randoms = Stream.generate(Math::random).limit(100);
Stream<Integer> integers = Stream.iterate(0, x -> x + 1).skip(100);
|
连接流方法:concat()
1 2 3
|
Stream<String> combined = Stream.concat(letters("Hello"), letters("World"));
|
注意:连接两个流时,前一个流不能是一个无限流
4. 流的终结
4.1 min和max
1 2 3 4 5 6 7
| Optional<String> smallest = words.min(String::compareToIgnoreCase); System.out.println("smallest = " + smallest);
Optional<String> largest = words.max(String::compareToIgnoreCase); System.out.println("largest = " + largest);
|
4.2 findFirst和findAny
1 2 3 4 5 6 7
|
Optional<String> first = words.filter(s -> s.startsWith("Q")).findFirst();
Optional<String> any = words.filter(s -> s.startsWith("Q")).findAny();
|
4.3 anyMatch, allMatch和noneMatch
1 2 3 4 5 6 7 8
| boolean anymatch = words.parallel().anymatch(s->s.contains("e"));
boolean allmatch = words.parallel().allmatch(s->s.contains("e"));
boolean nonematch = words.parallel().nonematch(s->s.contains("e"));
|
5. Optional类型
Optional对象是一种包装器对象,要么包装了T类型对象,要么没有包装任何对象。Optional类型被当作一种更安全的方式,用来代替类型T的引用,这种引用要么引用某个对象,要么为null。
5.1 如何使用Optional值
Optional在值存在的时候,才会使用这个值;在值不存在时,它会有一个可替代物。我们先看看Optional类的三个简单方法:
1 2 3 4 5 6 7 8 9
|
String result = optionalString.orElse("");
String result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());
String result = optionalString.orElseThrow(IllegalStateException::new);
|
还有一个ifPresent()方法,ifPresent(v->Process v)该方法会接收一个函数,如果在值存在的情况下,它会将值传递给该函数,但是如果在值不存在的情况下,它不会做任何事。
1 2 3 4
| optionalValue.ifPresent(v->result.add(v));
optionalValue.ifPresent(result::add);
|
5.2 不适合使用Optional值的方式
不适合使用Optional值的情况有两种:
需要用到get()方法,因为Optional值在不存在的情况下,使用get方法会抛出NoSuchElementException异常。
1 2 3 4 5
| Optional<T> optionalValue = ...; optionalValue.get().somemethod();
T value = ...; value.somemethod();
|
需要用到isPresent()方法作非空判断时。
1 2 3 4 5 6 7
| if(optionalValue.isPresent()){ optionalValue.get().somemethod(); }
if(value != null){ value.somemethod(); }
|
5.3 创建Optional值
三种方式创建Optional值:
1 2 3 4 5 6 7 8 9
| Optional<String> empty = Optional.empty();
if(value != null){ Optional<String> os = Optional.of(value); }
Optional<String> ofNullable = Optional.ofNullable(value);
|
5.4 用flatMap来构建Optional值的函数
假设你有一个可以产生Optional<T> 对象的方法f,并且目标类型T有一个可以产生Optional<U>的方法g,如果他们都是普通方法,那么你可以使用s.f().g()来调用。但是现在s.f()的返回类型是Optional<T>,而不是类型T,无法调用g方法,这个时候我们就可以利用flatMap方法来组合这两个方法。
1
| Optional<U> result = s.f().flatMap(T::g);
|