本文目录:
- 从迭代到流的操作
- 流的创建
- filter、map和flatMap方法
- 抽取子流和连接流
- 其他的流转换
- 简单约简
流提供了一种让我们可以在比集合更高的概念级上指定计算的数据视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它。我们将操作的调度留给具体实现去解决。例如,假设我们想计算某个属性的平均值,那么我们就可以指定数据源和该属性,然后流库就可以对计算进行优化,例如,使用多线程来计算总和与个数,并将结果合并。
java8的流库,用来以 “做什么而非怎么做” 的方式处理集合。
PS: 个人感悟:要重点关注每个方法参数是哪个函数是接口,方便整体理解。
1. 从迭代到流的操作
在处理集合时,我们通常会迭代遍历它的元素,并在每个元素上执行某项操作。例如,假设我们想对某本书的长单词进行计数。
//把文件读进字符串
String contents = new String(Files.readAllBytes(
Paths.get("alice.txt")), StandardCharsets.UTF_8);
//分割成单词,非字母是分隔符
List<String> words = Arrays.asList(contents.split("\\PL+"));
现在我们可以迭代它了:
通常做法:
long count = 0;
for (String w: words){
if(w.length() > 12) count++;
}
流的写法:
long count = words.stream()
.filter(w -> w.length()>12)
.count();
显然流要好读一点(个人觉得符合阅读习惯,代码越长越明显),方法名可以告诉我们它意欲何为。而且循环需要详细指定操作的顺序,流却能以想要的任何方式来调度这些操作。将stream修改为parallelStream就可以让流库以并行的方式来执行过滤和计数。
流和集合存在显著差异:
Attention:
1.流不存储元素。这些元素可能存储在底层集合中,或者是按需生成的。
2.留的操作不会修改其数据源。例如,filter方法不会从新的流中移除元素,而是会生成一个新的流,其中不包括被过滤掉的元素。
3.流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。例如,我们想查找前5个长单词而不是所有长单词,filter方法会在匹配到第5个单词后停止过滤。因此,我们甚至可以操作无限流。
下面这个也应该是一个attention:
前面的代码我们建立了一个包含三个阶段的操作管道,这个工作流是操作流时的典型流程。
- 创建一个流。
- 指定将初始流转换为其他流的中间操作,可能包含多个步骤。
- 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作。从此以后,这个流就再也不能用了。
2.流的创建
能创建流的方法真的是灰常多啊,下面直接列举方法了。
还有很多,这边只是列举几个
#java.util.stream.Stream 8
//产生一个元素为给定值的流
static <T> Stream<T> of(T... values)
//产生一个不包含任何元素的流
static <T> Stream<T> empty()
//产生一个无限流,它的值是‘反复调用’函数s而构建的。
static <T> Stream<T> generate(Supplier<T> s)
//产生一个无限流,它的元素包含种子、在种子上面调用f产生的值、在前一个元素上调用f产生的值,等等。just like s, f(s), f(f(s))...
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
#java.util.Arrays 1.2
//产生一个流,它的元素是由数组中指定范围的元素构成的
static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
#java.util.regex.Pattern 1.4
//产生一个流,它的元素是输入中由该模式界定的部分。
Stream<String> splitAsStream(final CharSequence input)
#java.nio.file.Files 7
//产生一个流,它的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集。
static Stream<String> lines(Path path)
static Stream<String> lines(Path path, Charset cs)
3. filter、map和flatMap方法
流的转换会产生新的流,它的元素派生自另一个流中的元素。下面三个实例方法都有产生新的流。注意他们参数是什么函数式接口。
#java.util.stream.Stream 8
//产生一个流,包含当前流中所有满足断言条件的元素
Stream<T> filter(Predicate<? super T> predicate)
//产生一个流,它包含将mapper应用于当前流中所有元素所产生的结果。
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
//产生一个流,它是通过将mapper应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流才行。)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
看一下它们的用法:
//筛选长单词
List<String> words = . . .;
Stream<String> longWords = words.stream().filter(x -> x.length()>12);
//所有单词转为小写
Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
//通常,这里可以使用lambda表达式来代替:
Stream<String> firstLetters = words.stream().map(s -> s.substring(0, 1));
现在假设map中得到的每个元素结果是一个包含众多值的Stream:
public static Stream<String> letters(String s) {
List<String> result = new ArrayList<String>();
for (int i=0; i<s.length(); i++) {
result.add(s.substring(i, i+1));
return result.stream();
}
letters("boot")的返回值的流包含的值是["b", "o", "o", "t"]。
假设我们在一个字符串上映射letters方法:
Stream<Stream<String>> result = words.stream().map(x -> letters(x));
那么就会得到一个包含流的流,像这样[...["b", "o", "o", "t"],["s", "t", "r", "a", "p"],...]
,为了摊平为字母流,like this[..."b", "o", "o", "t","s", "t", "r", "a", "p",...]
,可以使用flatMap方法而不是map方法:
Stream<String> flatrResult = word.stream().flatMap(x -> letters(x))
4.抽取子流和连接流
同样先来看下Stream类的几个方法
#java.util.stream.Stream 8
//产生一个流,其中包含了当前流中最前面的maxSize个元素,如果当前元素少于maxSize个,则包含当前流中的全部元素
Stream<T> limit(long maxSize)
//产生一个流,它的元素是当前流中除了前n个元素之外的所有元素
Stream<T> skip(long n)
//产生一个流,它的元素是a的元素和b的元素连接起来的结果。
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
limit方法对于裁剪无限流的尺寸会显得特别有用。例如下面的代码会产生一百个随机数。
Stream<Double> randoms = Stream.generate(Math::random).limit(100);
skip方法正好相反,它丢弃前n个元素。这在将文本分隔成单词显得很方便,因为按照split方法的工作方式,第一个元素是没什么用的空字符串。我们可以调用skip来跳过它:
Stream<String> words = Stream.of(contens.spilt("\\PL+")).skip(1);
concat静态方法用于将两个流连接起来:
//得到流[“h”,“e”,“l”,“l”,“o”,“w”,“o","r","l","d"]
Stream<String> combined = Stream.concat(
letters("hello"), letters("words"));
当然第一个流不应当是无限的,否则第二个流将永远得不到处理。
5. 其他流的转换
//产生一个流,包含当前流中所有不同的元素
Stream<T> distinct()
//产生一个流,它的元素是当前流中所有元素按照顺序排列的。第一个方法要求元素实现了Comparable类。
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
//产生一个和当前流具有同样元素的流,只不过在获取每个元素时,会将它传递给action
Stream<T> peek(Consumer<? super T> action)
方法总是用起来才能看门道,每个方法都有特定的用处,应努力记住有点印象,下面让我们来看一下它们的使用实例:
1.去除多余的"merrily"
Stream<String> uniqueWords = Stream.of("merrily","merrily","merrily","gently").distinct();
只有一个"merrily"被保留。
2.使得最长的字符串排在最前面:
Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());
3.peek方法对于调试来说很方便:
Object[] powers = Stream.iterate(1.0, p -> p*2).peek(e -> System.out.prinln("Fetching " + e)).limit(20).toArray();
当实际访问一个元素时,就会打印一条消息。通过这种方式,你可以验证iterate返回的无限流是被惰性处理的。对于调试,你可以让peek调用一个你设置了断点的方法。
6.简单约简(终于写到最后一节了)
现在你已经知道如何创建和转换流,我们终于可以讨论最重要的内容了,即从数据中获得答案。这个方法被称为约简
,约简是一种终结操作
,它们会将流约简成可以在程序中使用的非流值。
这里直接贴方法了:
//分别产生这个流的最大值和最小值,不过是被Optional包装器对象包裹着,
//使用comparator比较器定义的排序规则,如果这个流为空,则产生一个空的Optional对象
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
//产生这个流的第一个和任意一个元素,如果这个流为空,则产生一个空的Optional对象
Optional<T> findFirst()
Optional<T> findAny()
//分别在这流中任意元素、所有元素和没有任何元素匹配给定断言predicate时返回true。
boolean anyMatch(Predicate<? super T> predicate)
boolean allMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
网友评论