1. 语法
1. (Integer i)->return "A"+i; //无效的lambda :return是一个流程控制语句,若想此lambda生效,需要加{}
2. (String s)-> {"A"} // 无效的lambda : "a"是一个表达式而不是语句,若想此lambda生效
① 使用{return "A";} ②去掉{}
2.特殊的lambda
若一个lambda的主体是一个语句表达式,相当于一个void的函数
3.减少装箱拆箱的操作
原始类型特化
1. Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类 型时避免自动装箱的操作。
一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前,比 如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。
public interface IntPredicate{ boolean test(int t); }
// 无装箱
IntPredicate evenNumbers = (int i) -> i % 2 == 0; evenNumbers.test(1000);
//装箱
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1; oddNumbers.test(1000);
2. 在操作基本类型的流时,会有隐含的装箱操作
可以使用mapToInt/Double/Long 会返回对应的IntStream/DoubleStream/LongStream
IntStream 原始类型特化的流,考虑的是装箱操作带来的性能损耗
4.Comparator具有一个叫作comparing的静态辅助方法,
它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象
import java.util.Comparator.comparing;
// 做一个列表中的对象排序搭配方法引用
inventory.sort(comparing(Apple::getWeight));
5.lambda表达式的复合操作
/**
* 比较器复合
*/
// 排序
inventory.sort(comparing(Apple::getWeight));
// 逆序
inventory.sort(comparing(Apple::getWeight).reversed());
// 比较器链
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry));
/**
* 谓词复合 predicate boolean test(t);
谓词接口包括三个方法:negate、and和or.可以重用已有的Predicate来创建更复杂的谓词
*/
Predicate<Integer> c = i->i.intValue()>0; //查找大于0的
Predicate<Integer> c1 = c.negate(); // 查询小于等于0的
a.or(b).and(c)--->(a||b)&&c
/**
* 函数复合 function R apply(T t);
function为此配备了andThen和compose两个默认方法
*/
Function<String,Integer> f = (s)->Integer.parseInt(s);
Function<Integer,Integer> g = (i)->i+1;
f.andThen(g)-->相当于数学概念上的复合函数 g(f(x))
f.compose(g)-->相当于数学概念上的复合函数 f(g(x))
6.创建流的方式:
1.由值创建
Stream.of("","") ;
2.创建空流
Stream.empty();
3.由数组创建流
Arrays.Stream(数组)
4.文件创建流
Stream<String> lines = Files.lines(Paths.get(路径)) //得到了文件每一行对应的流
5.创建无限流-iteator
Stream.iterate(初始值,UnaryOperator<T>)
6.创建无限流-generate
Stream.generate(Supplier<T>)
与iterator类似,但是generate类似于python的generate 每次都是计算生成
必须要使用Limit(x)限制生成多少个。不然会无限生成数据
7. 对于流的操作:可以将操作分成 有状态 与 无状态 两种
这么划分的依据是:lambda 或 方法引用没有内部可变状态【map/filter】
reduce/sum/max/distinct 等操作需要内部状态来累计结果
8.关于流操作中的flatMap方法
map(Function<T,R> f)方法是让流中的元素都执行map中的 f 方法。且返回的流是 f 对应的返回类型的流Stream<R>
flatMap是将map生成的流扁平化,生成的是流中的内容
一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流
【map是将映射后的流将入到生成的流中,flatMap则是将映射后流中的元素加入到生成的流中】
9.关于reduce
reduce(T identity, BinaryOperator) ->返回T类型 / reduce(BinaryOperator) ->返回Optional
规约操作:可以将流中的元素反复结合起来,得到一个值
collect()方法也是规约操作 就像reduce一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。
10.关于fork/Join
一是拆分 二是汇总
优点:充分利用cpu的核数优势,执行速度快
缺点:因为充分利用了cpu的核数,如果执行线程数超过cpu核数,执行速度爆降,且cpu资源很容易跑满,其他业务响应就会变慢
1.使用
extends RecursiveTask<T>
11.关于终端操作collect()以及其Collectors方法
首先要区分Collection , Collector ,collect(), Collectors的区别
第一个是集合
第二个是收集器接口 [一般叫做预定义收集器]
第三个是Stream中的规约操作
collect(Collector) 两者搭配进行Stream中的数据处理
第四个则是提供了很多Collector的实现
Collectors 类中提供了很多工厂方法(例如groupingBy)创建的收集器,主要分三类:
1.将流元素规约和汇总得到一个值
2.元素分组 groupingBy
3.元素分区 partitioningBy
Collectors中的规约和汇总操作包括:
1.最大小值[maxBy/minBy]
menu.stream().collect(maxBy(dishCaloriesComparator));
2.计算总和[ collect(Collectors.counting())/stream().count() ]
3.连接字符串[ collect(joining()) ]
joining方法有重载版本
joining("X","Y","Z");
X:在各个字符串中连接符号
Y:在字符串开始连接符号
Z:在字符串结束连接符号
4.Collectors.summingInt/Long/Double
5.Collectors.averagingInt/Long/Double
6.分组groupingBy
groupingBy(function)
-重载实现多级分组:
groupingBy(function,groupingBy())
-使用 Collectors.collectingAndThen工厂方法返回的收集器(一般是处理返回Optional<T>的问题)
//查找每个子组中热量高的Dish
menu.stream().collect(
groupingBy(Dish::getType,// 分类函数
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)) // 将Optional<T>中的数据拿出来
);
结果: {FISH=salmon, OTHER=pizza, MEAT=pork}
7.分区 partitioningBy() 只接受Predicate
自定义的预定义收集器:使用reducing()方法实现
第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值 和而言0是一个合适的值。
第二个参数就是你在6.2.2节中使用的函数,将菜肴转换成一个表示其所含热量的int。
第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是 对两个int求和
这里会引出一个新的问题 Stream.reduce()方法与stream.collect(reducing())方法的辨别
12.关于随机数的操作
Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。
这两个方法都是第一个参数接受起始值,第二个参数接受结束值。
但 range是不包含结束值的,而rangeClosed则包含结束值
13.详解收集器接口Collector
public interface Collector<T, A, R> {
Supplier<A> supplier(); //建立一个结果容器
BiConsumer<A, T> accumulator(); // 向容器中添加元素
Function<A, R> finisher(); // 对结果容器应用最终转换
BinaryOperator<A> combiner(); // 合并两个结果容器
Set<Characteristics> characteristics(); // 对该收集器信息的手机 ,包含该收集器的优化提示
}
T是流中要收集的项目的泛型。
A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
R是收集操作得到的对象(通常但并不一定是集合)的类型
finisher:
通常,就像ToListCollector的情况一样,累加器对象恰好符合预期的终结果,因此无需进行转换
直接调用 Function.identity(); 即可
14. 关于并行流
对顺序流调用parallel()方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志
对并行流调用sequential方法就可以把它变成顺序流
最后一次parallel或sequential调用会影响整个流水线
stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();
并行流内部使用了默认的ForkJoinPool
线程数量就是你的处理器数量 Runtime.getRuntime().availableProcessors()
请注意availableProcessors方法虽然看起来是处理器, 但它实际上返回的是可用内核的数量,包括超线程生成的虚拟内核。
你可以通过系统属性java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
使用并行流一定要避免共享可变状态
--> 高效使用并行流 要注意:
1. 留意装箱
2. 有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元 素顺序的操作,它们在并行流上执行的代价非常大[ 例如 : findAny会比findFirst性 能好,因为它不一定要按顺序来执行]
3. 要考虑流背后的数据结构是否易于分解
15.Fork/join框架详解
1. 是ExecutorService的一个实现
2.使用:
RecursiveTask<R> 有返回值
RecursiveAction 无返回值
实现 protected abstract R compute()方法,该方法同时定义了将任务拆分成子任务的逻辑,以及无法再拆分或不方便再拆分时,生成 单个子任务结果的逻辑
if (任务足够小或不可分) {
顺序计算该任务
} else {
将任务分成两个子任务递归调用本方法,
拆分每个子任务,等待所有子任务完成
合并每个子任务的结果
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
return computeSequentially();
}
ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);
leftTask.fork();//注意点
ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);
Long rightResult = rightTask.compute(); //注意点
Long leftResult = leftTask.join();// 注意点
return leftResult + rightResult;// 注意点
}
16.Spliterator 接口
-->和Iterator一样,Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的
主要还是在处理并行流的时候发挥作用
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
T是Spliterator遍历的元素的类型。
tryAdvance方法的行为类似于普通的 Iterator,因为它会按顺序一个一个使用Spliterator中的元素,并且如果还有其他元素要遍 历就返回true。
trySplit是专为Spliterator接口设计的,因为它可以把一些元素划出去分 给第二个Spliterator(由该方法返回),让它们两个并行处理。
Spliterator还可通过 estimateSize方法估计还剩下多少元素要遍历,因为即使不那么确切,能快速算出来是一个值 也有助于让拆分均匀一点
将Stream拆分成多个部分的算法是一个递归过程。
第一步是对第一个 Spliterator调用trySplit,生成第二个Spliterator。
第二步对这两个Spliterator调用 trysplit,这样总共就有了四个Spliterator。
这个框架不断对Spliterator调用trySplit 直到它返回null,表明它处理的数据结构不能再分割。
最后,这个递归拆分过 程到第四步就终止了,这时所有的Spliterator在调用trySplit时都返回了null