读书笔记:Effective Java-第7章 Lambda和Stream

11 篇文章 0 订阅
11 篇文章 0 订阅

目录

Item 42: Prefer lambdas to anonymous classes

Item 43: Prefer method references to lambdas

Item 44: Favor the use of standard functional interfaces

Item 45: Use steams judiciously

Item 46: Prefer side-effect-free functions in streams

Item 47: Prefer Collection to Stream as a return type

Item 48: Use caution when making streams parallel


Item 42: Prefer lambdas to anonymous classes

Lambda优先于匿名类

术语:

  • function types:函数类型
  • function objects:函数对象
  • anonymous class:匿名类
  • functional interfaces:函数接口,只包含一个抽象方法的接口(非抽象方法可以有多个)。定义函数接口时,推荐加注解@FunctionInterface,帮助检查一些错误(也可不加)。
  • Lambda expressions:Lambda表达式,简称Lambda,用来创建函数接口的实例。
  • type inference:类型推断

Lambdas会自动进行类似推断,除非无法推断,否则建议不用写入参或返回参数类型。

Lambda不适用场景:

计算过程比较复杂、代码多行(1行理想、3为上限)。

Lambda VS anonymous class:

  • 优先Lambda,用不了再考虑匿名类。
  • Lambda内部的this指向外围实例,匿名类的this指向匿名类自身实例。
  • 需要序列或反序列化的场景,不要用Lambda和匿名类(无法序列和反序列化类的性质),而是用私有嵌套类。

Item 43: Prefer method references to lambdas

方法引用优先于Lambda(前提:方法引用能更简洁时)

术语:

  • method references:方法引用,用法:类名::方法名,或者 类实例::方法名,调用处的函数入参和返回类型需和方法引用的要一致。(可参考 Java 8 方法引用 | 菜鸟教程

方法引用的5种类型:

方法引用类型范例Lambda等式
静态(Static)Integer::parseIntstr->Integer.parseInt(str)
有限制(Bound)Instant.now()::isAfter

Instant then=Instant.now();

t->then.isAfter(t)

无限制(Unbound)String::toLowerCasestr->str.toLowerCase()
类构造器TreeMap<K,V>::new()->new TreeMap<K,V>
数组构造器int[]:new()->new int[len]

方法引用 VS Lambda:

  • Lambda可读性和维护性更强。
  • 方法引用总体而言更简洁(有时不是)。
  • 方法引用能做的事,Lambda也可实现(严格讲,有个例外)。

Item 44: Favor the use of standard functional interfaces

坚持使用标准的函数接口

标准的函数接口在包java.util.function中,如断言接口Predicate,应优先使用里面,而非专门构建。标准的函数接口有43个,但基础接口有6个,其余的可根据这6个基础接口推断出:

函数接口函数签名范例说明
UnaryOperator<T>T apply(T t)String::toLowerCase参数和返回类型一致
BinaryOperator<T>T apply(T t1, T t2)BigInteger::add参数有2个
Predicate<T>boolean test(T t)Collection::isEmpty有参数,返回boolen
Function<T>R apply(T t)Arrays::asList参数和返回类型不同
Supplier<T>T get()Instant::now无参数,有返回值
Comsumer<T>void accept(T t)System.out::println有参数,无返回值

现在倾向于用函数接口替代模板方法模式,即通过提供一个函数对象给静态工厂或类构造器来实现相同的功能。

注意:

  • 大多数标准函数接口支持基本类型,虽然也可用封装类型,但不建议,因为容易出现性能问题。
  • 不要在调用函数接口的方法出现重载情况,因为容易导致客户端出现歧义。

Item 45: Use steams judiciously

谨慎使用Stream

术语:

  • stream:流,代表数据元素有限或无限序列。
  • steam pipeline:流管道,一个管道代表数据元素的一个多级计算。包含1个源stream、0或多个中间操作(intermediate operation)和1个终止操作(terminal operation),具体操作可查看java.util.stream.Stream包,每各方法的注释会标注操作类别。

流管道是懒惰(lazy)计算,直到调用终止操作时才会执行中间操作。

stream应该避免滥用,在保证可读性和维护性的前提下尽可能使用。

建议使用stream的场景:转换、过滤、搜索、分组、对元素间作某种计算(如加)。

flatMap()用于将每个stream的元素平铺,最后合并成一个新的stream。比如,[[1,2],[3,4]]中含2个数组,flatMap()可将内部数组[1,2]平铺为1,2,最后构成新的数组[1,2,3,4]。

List<List<Integer>> llInt = new ArrayList<>();  // 用于存储:[[1,2],[3,4]]
llInt.add(Arrays.asList(1, 2));
llInt.add(Arrays.asList(3, 4));
llInt.stream().flatMap(Collection::stream).forEach(System.out::print);  // 打印:1234

Item 46: Prefer side-effect-free functions in streams

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

术语:

pure function:纯函数,函数结果只取决于输入的函数,不改变也不依赖任何状态。

downstream collector:下游收集器,用于将stream中的所有元素转换为一个值的方法,如counting()用于计算stream中的元素个数。

stream中的每级操作建议调用纯函数(pure function),如:

// 不推荐用法(虽然结果是对的),非纯函数,每次迭代都依赖freq上一次的状态,且会改变freq的状态
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    });
}

// 推荐用法,
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
    freq = words.collect(groupingBy(String::toLowerCase (), counting()));
}

stream的终端操作forEach()只应该用在报告由stream计算的结果(或偶尔用于其他目的),而不是用来迭代计算。

收集器(collector)中提供了许多供stream使用的集合/Map类方法(Collectors API)。

Item 47: Prefer Collection to Stream as a return type

Stream要优先用Collection作为返回类型

返回序列的类型主要有4种:Collection(List、Set等)、Iterable、数组、Stream。

  • 1 若明确返回序列的使用场景类型,则返回对应的类型;
  • 2-1 若不明确序列的返回类型,在不用考虑性能的前提下,优先选择Collection作为返回类型,因为Collection继承了Iterable且有Stream方法,可满足Iterable和Stream的使用场景;
  • 2-2 若不明确序列的返回类型,当需要考虑性能时(序列很大),则应该使用数组或者专门构建的集合(如幂集power set、连续子集),可以通过Arrays.asList和Stream.of方法将数组转换为Collection和Stream类型。

Item 48: Use caution when making streams parallel

谨慎使用Stream并行

如果stream的源source使用了Stream.iterate或者中间操作使用了limit(),则不要使用并行。

适合stream并行的场景:终端操作terminal operations适合使用归并类reduction函数(和大数据中的map-reduce中的reduce一个含义),如reduce()、min()、max()、count()、sumdeng()等。 

即使某个场景各条件已经适合stream并行操作,stream处理的元素数量要达到代码行数的十几万倍,才能抵消并行相关的成本。

开启并行方法:在stream源和中间操作间加上“.paraller()”,或者直接用parallelStream()代替stream(),将一个串行流转换成并行流:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.stream().reduce(0, Integer::sum);
int sumP1 = numbers.stream().parallel().reduce(0, Integer::sum); // 在source后加.parallel()
int sumP2 = numbers.parallelStream().reduce(0, Integer::sum); // 将stream()替换成parallelStream()
System.out.println("串行计算:" + sum + ";并行计算1:" + sumP1 + ";并行计算2:" + sumP2);
// 打印:串行计算:55;并行计算1:55;并行计算2:55

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值