【Java8实战】笔记

概念

  • 谓词:类似函数的东西,接受一个参数值,返回true或false (Predicate )

  • 流,内部迭代

  • 函数式接口:只定义一个抽象方法的接口(@FunctionalInterface注解注释的接口表示为函数式接口,若不是会报错)

  • Comparator 比较器:Comparator.comparing(User::getAge)

行为参数化

原本一个函数通过传递一个接口的实现类,通过传入不同的实现类达到实现不同的效果,优化后可以以匿名类的方式传入参数,再优化演变成用Lambda表达式传参,即行为参数化

1
List<User> users = dosomething(users,(User user) -> user.weigh>100);

Lambda表达式

语法

  • (parameters) -> expression

  • (parameters) -> { statements; }

  • a -> expression (当Lambda表达式仅有一个类型需要推断的参数时,参数名称两边的括号可以省略)

局部变量必须显式声明为final或者事实上是final,不可二次赋值

设计模式:

  • 策略模式:使用Lambda表达式可以不需要声明新的类来实现不同的策略,同一个接口

  • 模板方法:方法内部定义函数式接口参数,传入Lambda表达式

  • 观察者模式:通过传入Lambda表达式注册一个新的观察者

  • 责任链模式:UnaryOperator

  • 工厂模式

函数式接口

只定义一个抽象方法的接口,一般用于行为参数化

  • Predicate:传入一个参数,执行操作,返回true或false

可以用于判断一堆苹果中颜色为红色的苹果

  • Consumer:传入一个参数,执行操作,返回void

可以用于加工每个苹果,不返回

  • Function:传入一个参数,执行操作,返回一个结果

可以用于判断一堆苹果中颜色为红色的苹果

可使用Bi...传多个参数,例如:BiPredicate<Integer, Integer> bi = (x, y) -> x > y;

方法引用

  • User::getName 等价于 (User user) -> user.getName()

  • 当调用一个已存在的外部变量时,可以直接()-> a.getValue() 等价于 a::getValue

  • 构造方法 Classname::new

想利用多核架构并行执行,只需要把stream()换成parallelStream()

与集合的区别:集合讲的是数据,流讲的是计算,流利用了内部迭代

只能遍历一遍,和迭代器一样

分为两大操作:中间操作、终端操作

重要的是,除非流上触发一个终端操作,否则中间操作不会执行任何处理,中间操作可以合并起来一次解决

中间操作:

  • filter:过滤

  • limit:截取

  • sorted:排序

  • distinct:去重

  • skip:跳过

  • map:映射某个值

  • flatMap:将各个生成流扁平化为单个流,效果是将多个结果映射成流的一部分,最后合并成一个流

终端操作:

  • forEach:消费每个元素

  • anyMatch:存在一个符合条件就返回true

  • noneMatch:全不符合条件就返回true

  • allMatch:全符合条件就返回true

  • findAny:返回任意一个符合条件的数据

  • findFirst:返回第一个符合条件的数据

  • count:返回流中的个数

  • collect:归约,转化流(拼接:joining、转List:toList、分组:groupingBy)

  • reduce:将流中所有元素结合处理成一个值返回,可以传一个初始值

原始流:IntStream、DoubleStream、LongStream

  • 自带操作:max、min、sum、average、range(不包含结束值)、rangeClosed(包含结束值)

  • 转原始流操作:mapToInt

创建流

  • 由值创建:Stream.of(“aaaa”,”bbbb”)

  • 空流:Stream.empty()

  • 由数组创建:Arrays.stream(x);

  • 由函数生成流:无限流

1
2
Stream.iterate(0,n->n+2).limit(10):每次对新生成的值应用到下一个值中
Stream.generate(Math::random).limit(10):每次新生成的值不应用到下一个值中

用流收集数据

collect:方法

Collector:收集器接口

主要功能:将流元素归约和汇总为一个值、元素分组、元素分区

归约和汇总

  • toList:转list

  • toSet:转set

  • joinging:拼接,可以传入分隔符

  • counting:数量

  • maxBy:最大值

  • summingInt:求和

  • averagingInt:求平均数

  • summarizingInt:一次返回个数、总和、平均数、最大值、最小值

  • reducing:广义归约,可以比较求最大值,求总和等

  • toMap:转map

可以使用如下方式来过滤重复key

1
Collectors.toMap(EventReply::getEventOrderId, EventReply::getSubjectStr,(str1,str2)->str2)
1
Map<Long, ObjectSchemeFieldDTO> fieldMap = fieldDTOS.stream().collect(Collectors.toMap(ObjectSchemeFieldDTO::getId, Function.identity()))
  • toArray:转数组
1
String[] fieldCodes.stream().toArray(String[]::new)

分组

  • groupingBy:返回Map,对数据进行分组,可以再传入第二个参数为groupingBy,形成多级分组,若不传默认为toList()。一般与maping结合使用,放在第二个参数
1
2
Map<String, List<Long>> projectConfigMap = projectConfigs
.stream().collect(Collectors.groupingBy(ProjectConfig::getApplyType,Collectors.mapping(ProjectConfig::getProjectId,Collectors.toList())));
  • maping:分组映射值

  • collectingAndThen:将收集器的结果转换为另一种类型

分区

partitioningBy:分区为分组的特殊情况,由一个谓词作为分类函数,即是只返回true和false两个分组

Collector接口声明了五个方法:

1
2
3
4
5
1、建立新的结果容器:supplier方法
2、将元素添加到结果容器:accumulator方法
3、对结果容器应用最终转换:finisher方法
4、合并两个结果容器:combiner方法,应用于并行处理时,各个子部分归约所得的累加器要如何合并
5、characteristics方法返回一个不可变的集合,它定义了收集器的行为

流的日志调试

stream().peek()方法可以输出在流的操作之前与操作之后的中间值

并行流

  • 顺序流转并行流:stream.parallel()

  • 并行流转顺序流:parallelStream.sequential()

  • 并行流内部使用默认的线程数量就是你的处理器数量

  • 很重要的一点是要保证在内核中并行执行工作的时间比在内核之间传输数据的时间长

  • 错用并行流而产生错误的首要原因是使用的算法改变了某些共享状态

  • 装箱操作会影响并行流的效果

  • 分支/合并框架:fork(拆分)、join(合并)、工作窃取算法;把任务拆分成许多小任务,某个线程完成自己的任务后窃取其他线程的任务

  • Spliterator接口:可拆分迭代器

    1、tryAdvance():类似于普通的指针,按顺序遍历
    2、trySplit():试图把一些元素划分出去
    3、estimateSize():估计还剩下多少元素要遍历
    4、characteristics():特性
    

默认方法

默认方法以一种兼容的方式改进API,java8的接口支持在声明方法的同时提供实现。

1
2
3
default void sort(){
xx
}

行为的多继承:这样一来一个类可以实现多个接口,就可以实现接口默认方法的多继承

解决冲突的三条原则:

  • 类中的方法优先级最高

  • B继承A,则B比A更具体

  • 通过显示覆盖或调用:B.super.hello()

Optional取代null

变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会建模成一个空的Optional对象

创建Optional对象

  • 空对象:Optional.empty()

  • 非空值:Optional.of(car)

  • 可能为空:Optional.ofNullable(car)

通过map取值

1
2
Optional<Car> ocar = Optional.ofNullable(car)
ocar.map(Car::getName)

flatMap扁平化

.map.map无法取出值来,需要使用flatMap扁平化取值

1
2
List<StatusDTO> addStatuses = changeItems
.stream().map(StateMachineSchemeChangeItem::getAddStatuses).flatMap(x -> x.stream()).distinct().collect(Collectors.toList())

无法直接序列化

行为

  • get():获取值,不存在会报错

  • orElse():对象不包含值时提供一个默认值

  • ifPresent(XX):存在值时执行传入的方法

  • isPrensent():存在值返回true,否则false

不推荐使用基础类型的Optional,很多方法不能用

CompletableFuture组合式异步编程

1
2
并发的关键是你有处理多个任务的能力,不一定要同时
并行的关键是你有同时处理多个任务的能力
  • future接口:异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方

  • 使用工厂方法supplyAsync创建CompletableFuture

1
2
3
4
5
6
7
8
9
//异步任务
CompletableFuture<Void> task = CompletableFuture
.supplyAsync(() -> 异步操作)
.thenAccept(result -> {
返回结果
});
//进行其他操作
//等待异步任务结果返回
CompletableFuture.allOf(task).join();
  • 使用异步请求,获取异步结果要单独一个流出来,因为流具有延迟特性

  • CompletableFuture与流自带的并行流优势不明显,但它可以使用定制的执行器来提升性能,第二个参数传入executor

  • thenCompose方法:允许对两个异步操作进行流水线,当第一个操作完成时会把结果传入第二个操作

  • thenCombine方法:定义两个CompletableFuture对象计算完成后,结果如何合并

  • thenAccept方法:接受CompletableFuture执行完毕后的返回值做参数

性能考量

经过实验测试外部迭代与内部迭代的性能测试,得到以下结论:

  • 对于简单操作推荐使用外部迭代手动实现

  • 对于复杂操作,推荐使用Stream API

  • 在多核情况下,推荐使用并行Stream API来发挥多核优势

  • 单核情况下不建议使用并行Stream API

map中新增的函数computeIfAbsent

1
2
3
4
5
6
7
8
9
/*
只有在当前 Map 中 key 对应的值不存在或为 null 时
才调用 mappingFunction
并在 mappingFunction 执行结果非 null 时
将结果跟 key 关联.
mappingFunction 为空时 将抛出空指针异常
*/
// 函数原型 支持在 JDK 8 以上
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Map<String, List<String>> map = new HashMap<>();
List<String> list;

// 一般这样写
list = map.get("list-1");
if (list == null) {
list = new LinkedList<>();
map.put("list-1", list);
}
list.add("one");

// 使用 computeIfAbsent 可以这样写
list = map.computeIfAbsent("list-1", k -> new ArrayList<>());
list.add("one");

参考文献