流提供一种比集合更高级指定计算的视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它。我们将操作的调度留给具体的实现去解决。例如,假设我们想要计算某个属性的平均值,那么我们可以指定数据源和该属性,然后,流库可以对计算进行优化。例如,实用多线程来计算总和与个数,并将其合并。
1.1 从迭代到流的操作
在处理集合时,我们通常会迭代遍历集合所有的元素,并在集合的每个元素上执行某项操作。例如,假设我们想要对某本书中的所有长单词进行计数。首先,将所有单词放进一个列表中:
String content = new String(Files.getAllBytes(Paths.get("1.txt"))
,StandardCharsets.UTF-8);
List<String> words = Arrays.aList(content.split("//PL+"));
//用普通的方法迭代
long count=0;
for(String word : words){
if(word.length()>12){
count++;
}
}
//使用流后
long count = words.stream()
.filter(w -> w.length()>12)
.count();
可以看出流的版本你循环更易于阅读,因此我们没必要扫描整个代码去查找过滤和计数操作,方法名就可以直接告述我们其代码意欲何为。而且,循环需要非常详细的指定操作的顺序,而流却能将以想要的任何方式来调度这些操作,只要结果正确即可。
1.2 流的特点
- 流并不储存其元素。这些元素可能储存在底层的集合中,或者按需生成。
- 流的操作不会修改数据源。例如,filter方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含被过滤的元素。
- 流的操作是尽可能惰性执行的。这意味着直到需要其结果时,操作才会被执行。例如,我们只想要查找前5个长单词而不是所有长单词,那么filter方法就会匹配到第5个单词后就会停止过滤。因此,我们可以操作无限流。
1.3 流的创建
如果有一个数组可以使用静态的Stream.of方法来转换流。
Stream<String> words = Stream.of(content.split("//PL+"));
of方法具有可变长参数,因此我们可以构建具有任意数量引元的流:
Stream<String> songs = Stream.of("gently","down","the","stram");
使用Arrays.stream(array,from,to)可以从数组中位于from(包括)到to(不包括)的元素创建一个流。为了创建不包含任意元素的流可以使用可以使用静态的Stream.empty方法:
Stream<String> empty = Stream.empty();
Stream接口有两个用于创建无限流的静态方法。generate方法会接受一个不包含任意引元的函数(从技术上讲,是一个Supplier<T>接口的对象)。无论何时,只需要一个流类型的值,该函数就会调用以产生一个这样的值。我们可像下面这样获取一个常量值的流:
Stream<String> echos = Stream.generate(() -> "Echo");
或者像下面获取一个随机数流
Stream<Integer> random = Stream.generate(Math::random);
为了产生无限序列,例如0,1,2,3....,可以使用iterate方法,它会接受一个"种子"值,以及一个函数(从技术上讲,是一个UnaryOperation<T>接口) ,并会反复的将该函数应用到之前的结果上。
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO,
n -> add(BigInteger,ONE));
该元素的第一个种子BigInteger.ZERO,第二个f(speed),即1(作为大整数),下一个元素是f(f(speed)),即2,以此类推。
1.4 filter,map和flatMap方法
流的转换会产生一个新的流,它的元素派生自另一个流中的元素。我们已经看到了filter的转换会产生一个流,它的条件与某种条件相匹配。下面将一个字符串流转换成只包含长单词的另一个流:
List<String> wordList = new ArrayList<>();
wordList.add("teacher");
wordList.add("student1");
wordList.add("teacher1");
wordList.add("student");
Stream<String> stream = wordList.stream().filter(w -> w.length()>7);
filter的引元是Predicate<T>,即T到boolean类型的函数。
通常我们想要以某种方式转换流中的值,此时,可以使用map方法并传递转换流中元素的函数。例如,下面将所有单词转换小写:
Stream<String> stream = wordList.stream()
.map(String::toLowerCase);
在使用map时,会有一个函数应用到每个元素上,并且其结果是包含了应用该函数所产生的所有结果的流。现在,假设我们有一个函数,他返回的不是一个值,而是一个包含众多值的流:
Public static Stream<String> letters(String s){
List<String> list = new ArrayList<>();
for(int i=0; i<s.lenght(); i++){
list.add(s.substring(i,i+1));
}
return list.stream();
}
假设我们在一个字符串的流上映射letters方法:
Stream<Stream<String>> stream = word.stream().map(w -> letters(w));
那么就会得到类似于二维数组包含流的流。为了将其摊平成一维数组,要使用flatMap而不是map:
Stream<Stream<String>> stream = word.stream().flatMap(w -> letters(w));
1.5 抽取子流和连接流
调用stream.limit(n)会返回一个新的流,它会在n个元素之后结束(如果原来的流更短,那么就会在原来的流结束时结束)。这个方法对于裁剪无限流的尺寸显的特别有用。例如,
Stream<Double> random = Stream.generate(Math::random).limit(100);
掉用stream.skip(n)正好相反:它会丢弃前n个元素。这个方法将文本分隔成单词时,显的特别方便,因为按照split的工作方式,第一个元素是没什么用的空字符串。可以通过skip跳过它:
Stream<String> words = Stream.of(content.split("//PL+")).skip(1);
也可以使用Stream的静态方法cancat把两个流连接起来:
Stream<String> word = Stream.concat(letter("teacher"),letter("student"));