lambda

lambda

lambda的产生

  • 不想在一个类中把一个方法写死,这样每次需求修改都要重写这个方法,因此使用接口的多态来实现。
  • 根据需求我们需要去实现接口,但是这样需要定义很多只会在一个地方使用一次的类,因此使用匿名内部类。
  • 当一个接口中只有一个抽象方法的时候(称为函数式接口),使用匿名内部类也会显得有些地方代码多余,因此使用lambda来优化。

lambda的使用注意

  • lambda所实现的接口我们称为目标类型,同一个Lambda表达式在不同的地方可能目标类型不一样,但是能够使用lambda的前提是编译器能用通过环境推断出目标类型。
  • 函数式接口是相当简单的,因此我们没有必要去在我们的应用中去定义它,JDK8的util.function下专门定义了函数式接口来让我们使用,结合泛型来使用。
  • 内部类中通过继承得到的成员(包括来自 Object 的方法)可能会把外部类的成员掩盖(shadow),此外未限定(unqualified)的 this 引用会指向内部类自己而非外部类。而lambda是词法作用域,和for,if等一样。

词法作用域 和 动态作用域

词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的(this也是!)。
词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。

方法引用

  • 在lambda中实现的方法的逻辑,已经由其他已经存在的方法实现,这时候只需要调用这个方法,并且直接返回结果。
    这个过程可以有方法引用来简化。
Kind Example
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new
  • 方法引用相当于调用已有的方法作为该Lambda的实现,注意返回的依然是一个对象。
  • 引用的方法存在重载的使用,将会通过函数式接口中的方法来判断具体调用哪一个。

默认方法

  • 默认方法使我们可以给库里的接口添加新的方法而不会影响这个接口的老代码。
  • 以往的解决方案是在实现类中写一个抽象方法,现在可以在接口中写一个默认方法。实现类可以选择实现与否。

聚合操作

  • 使用集合不仅仅是为了存储数据(读取元素数据),也是为了检索数据(对这些数据整体做操作)。
  • java8有一种更简洁的方式来检索集合,就是聚合操作。
  • 聚合操作不能读取数据元素
  • 聚合操作的中间操作时迟加载的,在终端执行时才会按顺序一起执行。
  • 聚合操作中不要更改流的源的值,否则会发生线程干扰。

stream

  • stream是元素的序列,但是不是存储数据的,而是以管道的方式从源获取的值。不会改变源的值。

管道 pipeline

  • 管道是聚合操作的一个序列。
  1. 源:stream的来源,可以是集合,数组, a generator function, or an I/O channel。
  2. 中间操作:过滤stream数据,生成一个新的stream.
  3. 终端操作:返回基本数据类型,返回一个值得也叫reduction operations。

Stream.reduce

1
2
3
4
5
reduce(0,(a, b) -> a + b);
//需要两个参数,第二个参数是个lambda表达式
//第一个参数是初始值以及无元素的返回值
//第二个参数a是当前结果,b是下一个元素值
//返回一个新的当前结果

Stream.collect

1
2
3
4
5
6
7
8
9
collect(supplier, accumulator, combiner);
//需要3个参数,第一个参数是一个lambda表达式
//第一个参数提供结果容器
//第二个参数是一个“函数”,“函数”的参数是每一个元素值
//第三个参数也是一个“函数”,“函数”的参数是另一个结果容器,作为平行操作时把子结果合在一起的逻辑。

collect(Collectors)
//这个类封装在collect操作中用作参数的函数,该函数需要三个参数(supplier,accumulator和combiner函数)。
//Collectors中海油很多特别的操作

迟加载的好处

  • java编译和运行时优化,将管道中的操作一起操作,这样就只需要遍历一次,而不是每个聚合操作都遍历一次。
  • 对于无限数据源,我们可以满足某个条件就停止。

聚合操作和迭代器的区别

  • 迭代是外部迭代,外部迭代需要指定迭代的对象和迭代的方式(next)。只能按顺序迭代,否则有数据问题。
  • 聚合操作是内部操作,只需要指定迭代的对象。可以平行操作(parallelStream)。

线程平行执行

使用Iterators迭代集合,会有县城干扰和数据不一致问题。

  • 线程干扰:多线程同时执行一个对象中的非原子操作,会出现数据最终一致性错误的问题。比如一个+1,一个-1,最终结果是-1.
  • 解决一致性问题:设置变量可见性:单线程,同步块,volatile,thread.start规则,thread.join方式

虽然集合提供synchronization wrappers来解决线程安全问题,但是这样会产生线程争夺,线程争夺必然不能平行执行。
聚合操作和并行流使您可以实现与非线程安全集合的并行性,前提是您在操作集合时不要修改集合。

更多线程平行的学习:https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html

参考资料

[java lambda文档] https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#syntax

[Shadowing] https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#shadowing

[聚合操作] https://docs.oracle.com/javase/tutorial/collections/streams/index.html

[java8 新特性]https://blog.csdn.net/Hatsune_Miku_/article/details/73556641.html

并行操作:https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html

本文标题:lambda

文章作者:Sun

发布时间:2018年09月21日 - 15:09

最后更新:2020年06月23日 - 15:06

原始链接:https://sunyi720.github.io/2018/09/21/Java/lambda/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。