行为参数化,就是一个方法接收多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
流,是从支持数据处理操作的源,生成的,元素序列。
- 集合是数据结构,目的是以特定的时间/空间复杂度存储和访问元素。
- 流的目的,在于表达计算。
与迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。
Stream
短路 循环合并 filter + map
函数式接口,就是只定义一个抽象方法的接口。
- 即使接口有很多默认方法,只要接口只定义了一个抽象方法,仍然是一个函数式接口。
Lambda 表达式,允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。
- 用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。
函数式接口的抽象方法的签名,基本上就是 Lambda 表达式的签名。我们将这种抽象方法叫做函数描述符。
@FunctionalInterface
- java.util.function.Predicate<T>
- test(T t) : boolean
- java.util.function.Consumer<T>
- accept(T t) : void
- java.util.function.Function<T, R>
- apply(T t) : R
- java.util.function.Supplier<T>
- get() : T
泛型,只能绑定到引用类型,由泛型内部实现方式造成的。
避免自动装箱、拆箱的性能损耗及内存空间的额外要求:
- IntPredicate
- test(int i) : boolean
- DoublePredicate
- IntConsumer
- LongBinaryOperator
- IntFunction
- ToIntFunction<T>
- IntToDoubleFunction
JDK 提供的函数式接口不允许抛出受检异常(checked exception),解决方案:
- 定义一个自己的函数式接口,并声明受检异常
- 把 Lambda 表达式包在一个 try/catch 块中
Lambda 的类型是从使用 Lambda 的上下文推断出来的。
Lambda 表达式需要的类型,称为目标类型。
如果 Lambda 表达式的主体是一个语句表达式,它就和一个返回 void 的函数描述符兼容(当然需要参数列表也兼容)。
List<String> list = new ArrayList<>();
Predicate<String> predicate = s -> list.add(s);
Consumer<String> consumer = s -> list.add(s);Lambda 可以没有限制地捕获实例变量和静态变量。
- 但局部变量必须声明为 final,或事实上是 final。
- Lambda 表达式只能捕获指派给它们的局部变量一次。
- 捕获实例变量可以被看做捕获最终局部变量 this。
对局部变量的限制,是因为:
- 实例变量都保存在堆中,而局部变量保存在栈上。
- 不鼓励使用改变外部变量的典型命令式编程模式,会阻碍并行处理。
闭包,是一个函数的实例,且可以无限制地访问那个函数的非本地变量。
- 闭包可以作为参数传递给另一个函数。
- 也可以访问和修改其作用域之外的变量。
Java 8 的 Lambda 和匿名类可以做类似于闭包的事情:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。
- 但有一个限制:它们不能修改定义 Lambda 的方法的局部变量的内容,这些变量必须是隐式最终的。
- 可以认为 Lambda 是对值封闭,而不是对变量封闭。
- 这种限制存在的原因在于局部变量保存在栈上,并且隐式表示他们仅限于其所在线程。
- 如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性,而这是我们不想看到的(实例变量可以,因为它们保存在堆中,而堆是在线程之间共享的)。
可以把方法引用看作针对仅仅涉及单一方法的 Lambda 的语法糖。
方法引用分类:
- 指向静态方法,如 Integer::parseInt
- 指向任意类型实例方法,如 String::length
- 指向现有对象的实例方法
- 构造函数引用,如 ClassName::new
线程,是操作系统进行计算调度的基本单元。 进程,是操作系统进行资源分配的基本单元。
改善代码的可读性
- 从匿名类到 Lambda 的转换
- 匿名类与 Lambda 中 this 和 supper 含义不同
- 匿名类中,this 代表类自身;
- Lambda 中,this 代表包含类;
- 匿名类,可以屏蔽包含类的变量;
- Lambda 表达式不能,会编译错误;
- 设计重载上下文中,将匿名类转换为 Lambda 表达式可能会更加晦涩;
- 匿名类初始化时确定
- Lambda 表达式依赖上下文
- 匿名类与 Lambda 中 this 和 supper 含义不同
- 从 Lambda 表达式到方法引用的转换
- 从命令式数据处理切换到 Stream
- 增加代码的灵活性
- 采用函数接口
- 有条件的延迟执行
- 环绕执行
接口默认方法的使用方式:
- 可选方法 为方法的实现留白
- 行为的多继承
遵循三条准则解决所有默认方法带来的冲突:
- 类或父类中显式声明的方法,其优先级高于所有的默认方法;
- 如果用第一条无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口;
- 如果冲突依旧无法解决,就只能在类中覆盖该默认方法,显式地指定在类中使用哪一个接口中的方法;