Effective Java 阅读笔记

第七章 Lambda 和 Stream

第 42 条 Lambda 优先于匿名类

匿名类过于繁琐,使用 Lambda 可以使代码更清晰、更轻量。

但是,Lambda 没有名称和文档,如果一个实现过于复杂,那就不要放在 Lambda 中。

并且,Lambda 中不能获取函数本身的引用,Lambda 中的 this 获取的是外围 this,如果需要获取那就用匿名内部类。

尽可能不要(除非迫不得已)序列化一个 Lambda,如果有需要,应该使用私有静态嵌套类(即private static class)。

第 43 条 方法引用优先于 Lambda

方法引用也是在 Java 8 中添加进来的,用::表示,比如引用System.out.println()方法,就是这样:System.out::println

方法引用比 Lambda 更简洁,也能消除更多的样板代码,同时可读性也更好。

比如:

map.merge(key, 1, (count, incr) -> count + incr);

这个就可以使用方法引用来替换:

map.merge(key, 1, Integer::sum);

这样既可以消除重复变量 count、incr,可读性也更好。

大部分情况下都可以使用方法引用来代替 Lambda 方法,但是也有例外,比如在一个类名很长的类中,写方法实现时:

service.execute(GoshThisClassNameIsHumongus::action);

并不比下面这种好:

service.execute(() -> action());

类似的还有Function.identity()并不比x -> x更简洁,也没有更清晰,此时就可以考虑使用x -> x这种。

简而言之,如果方法引用更简洁、清晰,那就用方法引用,如果方法引用并不简洁,那就用 Lambda。

第 44 条 坚持使用标准的函数接口

Java 8 中加入函数式之后,java.util.function 包中加入了大量预定义的函数接口,不需要记住每个函数,只需要记住常用的 6 个就行:

接口函数签名例子
UnaryOperatorT apply(T t)String::toLowerCase
BinaaryOperatorT apply(T t1, T t2)BigInteger::add
Predicateboolean test(T t)Collections::isEmpty
Function<T, R>R apply(T t)Array::asList
SupplierT get()Instant::new
Consumervoid accept(T t)System.out::println

这 6 个接口还各自有 3 中变体,分别对应 int、long、double,命名方式是在原本的名称前面加上类型,比如IntPredicate<T>

Function 有 9 中变体,分别对应参数和返回值为 int、long、double 类型,命名方式是入参类型To返回类型,比如IntToLongFunction,如果结果是对象那就是ToObj,比如IntToObjFunction

三种更基础的类型FunctionConsumerPredicate还有两个参数的变体,以及两个单数对应基础类型的变体,例如:

  • BiFunction<T, U, R>
  • BiConsumer<T, U>
  • BiPredicate<T, U>

对应的基础类型命名方式如下:

  • ToIntBiFunction<T, U>
  • ObjIntConsumer<T>

然后还有BooleanSupplier,总计 43 个接口。

PS:真鸡儿个复杂,Java这里点处理的不是很好,基础类型、参数数量和类型、返回值类型掺杂在一块,超级混乱。
C#这一点处理的还算可以,但是也不是完美的,函数分裂成Func<T>Action<T>,因为void不能作为泛型。

在使用时,如果可以使用基本类型的接口,那就不要用包装类型的接口,因为会带来很大的性能问题。

当满足以下条件时就可以考虑自己定义函数式接口,而不是使用内置的:

  • 比较通用,并且名称可以很好的说明这个接口的作用时
  • 有比较严格的约定时
  • 需要特殊默认方法时

而且自己生命函数式接口时,需要用@FunctionalInterface来标记,这个注解和@Override类似。

第 45 条 使用 Stream 时需谨慎

Stream 配合函数式接口写起来很爽,但是滥用会让代码更难读、难维护。

在没有显式类型的情况下,仔细命名 lambda 的参数会让 Stream Pipeline 可读性更好。

Java 中没有 charStream,char 在 Stream 中会变成 int,因此最好不好用 Stream 处理char。

部分情况只能通过普通循环来完成,不能使用 Stream 处理:

  • 读取或修改局部变量时:Stream 只能读取 final 变量,并且也不能修改变量(虽然可以通过传入 final 的数组来实现,但是并不建议)。
  • 需要 return、break、continue,或者抛出受检异常时

下面这些情况用 Stream 非常好处理:

  • 统一转换元素列表
  • 过滤元素列表
  • 合并元素序列(最大值、最小值、求和时)
  • 元素列表分组
  • 搜索某些条件的元素

第 46 条 优先选择 Stream 中无副作用的函数

避免在 forEach 函数中操作外部数据,如果可以的话,使用 collect 方法去收集,而不是在 forEach 中去一个一个 add。

第 47 条 Stream 优先用 Collection 作为返回类型

这个意思是用 Collection 或者其子类型作为返回值更合适,不要返回 Iterable。

第 48 条 使用 Stream 并行应谨慎

任意使用parallel()方法不仅可能降低性能,还有可能调用失败甚至结果出错。

适当的情况下使用并行流可以提高效率。一般来说,如果每个元素互不干扰,那么并行流就可以提高效率。但是有状态的时候就很有可能出错,比如在 reduce 时。

标签: Java

添加新评论