读书笔记,《Java 8实战》第五章,使用流
本章我们将会了解到关于Stream API的很多操作,这些操作能够完成很多复杂的查询,比如,比如筛选,切片,映射,查找,匹配和规约,
第一节,筛选和切片
首先我们来看看用谓词来选择流动的元素,主要介绍了几个基础操作,包括:filter, distinct, limit, skip等。
比如对于filter,这里给了上一章的一个例子,用一个谓词来判断一个菜单中是否有蔬菜:
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
第二节, 映射
主要有两种意思,一种是对流中的每一个元素应用映射,第二种就是流的扁平化
map, flatMap
所谓流量变化,主要是为了解决流嵌套流在这种场景,而它们最终变成一个流。
这里给了一个例子,让我们返回一个句子中,每一个单词里面的,各不相同的字符,他最终是这么写的:
List<String> uniqueChar = words.stream().map(w->split("")).flatMap(Arrays::stream).distinct.collect(toList());
一言以蔽之,流的扁平化就是让你把一个流中的每一个值都转换成另外一个流,然后把所有的流连接起来成为一个流,比如上面的这个例子,map(w-> split(""))把每个单词都转化为一个字符串数组,然后通过Arrays::stream把每个字符串数组转换成一个流,然后再通过flatMap把它们合并成一个流。
本节的末尾作者给了一个例子,坐在给了两个数组,然后要用线性代数里面的数字相乘的,逻辑来生成一系列的数对,这个例子我一开始没有做出来,所以我觉得,可以用这个例子,来作为面试题。当我没有说出来,是因为一开始我对于这个flatMap理解不够深刻,现在应该没什么问题,简单写一个示意代码。
List<Integer> array1 = Arrays.asList(1,2,3);
List<Integer> array2 = Arrays.asList(3, 4);
List<int[]> result = array1.stream().flatMap( a-> array2.stream(b-> new int[]{a, b})).collect(toList());
第三节,查找和匹配
这一节主要介绍了以下几种常见的处理函数:
allMatch, anyMatch, noneMatch, findFirst, findAny.
这里提到了,短路的概念,所谓短路,也就是说不管表达是有多惨,你只需要找到一个表达式为false,就可以推断整个表达式微false,所以用不着去计算整个表达式。
接下来在介绍查找元素的时候,作者提到了一个新的类 Optional<T>,这是一个容器类,代表1个值存在或者不存在,比如findAny可能什么都没找到,有了这个类我们就可以不用返回众所周知容易出问题的null,从而避免了和null相关的一些bug。
这里我们可以记住,Optional类的一些API:
isPresent()
ifPresent(Comsumer<T>),会在值存在的时候,执行一段指定的代码块。
T get()
T orElse(T other) 值存在的时候就返回值,不存在的时候就返回默认值,
本节等最后作者分析了findFirst 和findAny之间的区别,两者在并行计算的时候性能是不一样的。
第四节,规约
本节你将看到,如何把一个流中的所有的元素组合起来,使用reduce操作来表达一个更复杂的查询,此类查询需要把流中的所有元素反复结合起来,得到一个值,比如一个整形。这样的查询可以被归类为规约操作,如果用函数式编程的术语来说叫做折叠。
第一小节,求和操作
int sum = numbers.stream().reduce(0, (a, b) -> a+b));
等效于:
int sum = numbers.stream().reduce(Integer::sum);
这里用到了reduce操作就是当年google的赫赫有名的map-reduce中的reduce
上文用到的reduce等原型是:函数介绍一个初始值,然后用一个,二元操作符,
其实reduce还有另外一个变体,就是只介绍一个二操作符,这个时候,他就必须返回上文提到的那个Optional类了,因为没有默认初始值啊,所以可能是空的。
想这个辩题呢,就还可以用这个reduce操作计算最大最小值
然后本节的最后作者提出了规约操作的并行化:
int sum = numbers.parallelStream().reduce(0, Integer::sum);
按作者同时也提出,规约操作要有并行化的话要付出一定的代价的,也就是这个规约函数,必须是无状态的,而且操作本身必须,满足结合定律才可以按照任意顺序执行。
流操作做其实可以分为无状态、有状态有界和有状态无界3种,
1)比如map和filter,就属于无状态的,
2)reduce操作中等max、sum这样的操作则是有状态的。但是他们内部状态所需要的存储空间很小,所以属于有界的。
3)而像sort、distinct这些操作,他们基本上是输入一个流说出一个流,如果流比较大,或者是无限的,那么他们所需要的存储空间也是无限的。
第五节,付诸实践,
这一节以一个交易员在日常工作中所遇到的一些关于交易的例子来说明就操作的相关api的应用。
题目如下,怎么做我就不说了,
第六节,数据流
感觉一开始作者就给了一个例子,如果没有数据流,我们,所使用的球和操作可能会有什么问题,比如:
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
这里面主要按了一个装箱的成本, 也就是说所有的数字在计算的时候必须被转换成Integer类型,计算完之后再转换会int。这样如果留,比较大的时候,其实对性能的,影响还是比较大的,所以,在java8中,Stream API提供了原始类型的流特化。专门支持处理数据流的方法。
第一小节,原始类型流特化
在java8中,引入了,三个,原始类型特化流接口: IntStream, DoubleStrream, LongStream,分别将流中等元素特化为int, double, long类型,从而避免了暗含的装箱成本。
将流转换为特化版本的常用方法有:mapToInt, mapToDouble, mapToLong,比如:
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
其中sum是IntStream特化流中特有的接口。
如果要计算一个特化流中的最大值,那么他就会返回一个Optional<T>,因为流可能是空的,那么这个时候就没有最大值了。
既然可以将流转化为一个特化流,同样的也可以讲一个特化流转化为非特化流,相应等方法就是:boxed()
第二小节,数值范围
主要有两个方法:
IntStream.range(1, 100)
IntStream.rangeClosed(1, 100)
一个是开区间,一个是闭区间。
第三小节,数据流的应用,勾股数
Stream<int[]> pythagoreamTriples = IntStream.rangeClosed(1, 100)
.flatMap(a -> IntStream.rangeClosed(a, 100).map(b->new double[] {a, b, Math.sqrt(a*a + b*b)}))
.filter(a->a[2] %1 == 0);
第七节,构建流
本节我们将介绍如何从值序列、数组、文件来创建流,甚至可以通过一个函数来创建无限流。
第一种,通过值来创建
Stream<String> ss = Stream.of("Hello", "world", "ni hao");
或者你可以直接创建一个空流
Stream<String> ss = Stream.empty();
牛逼吧
第二种,通过数组来创建,
int[] numbers = {2, 3, 4, 5, 6};
int sum = Array.stream(numbers).sum();
这里它实际上返回的是一个特化流:IntStream,不然应该没有求和方法。
第三种是通过文件来创建流
因为我对文件操作还不是太了解,所以这里就简单提一下,
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
//xxxx
}catch(xxxx){
//yyyy
}
第四种,通过函数来生成流,这是一种无限流,
Stream API 提供了两个静态方法来从函数生成硫,Stream.iterator, Stream.generate
1)迭代方法接受一个初始值,还有一个应用在每一个产生的新值上的表达式:
Stream.iterator(0, n-> n+2).limit(10).forEach(System.out::println());
接下来,作者给了一个生成斐波那契序列的例子,这里的关键就是要明白,入参可以是一个数组。
2)生成。和iterate方法类似,generate可以让你,按需生产一个无限流,
Stream.generate(Math::random).limit(5).forEach(System.out::println);
上面这个例子给的函数,是一个,无状态的,然后接下来做事还给了一个用匿名类,来实现有状态的流生成函数的例子,还是以上文提到的斐波那契序列为例。
如果这种内部带状态的,流处理函数不是一种好的实践,在实际编程中应该尽量少用,因为一旦带了内部的状态,以后就很难去做并行话处理。