Lambda表达式和函数式接口

Lambda表达式语法的本质上就是一种表达式,而非对象。 Lambda表达式的返回值会被自动推导。如果参数被省略,那么也会尝试自动推导。

函数式接口(Functional Interface)指的是只有一个抽象方法的接口。(可以有一些非抽象方法,如重写或默认) Lambda表达式是被设计为快速地转换为函数式接口的实例。程序员不用手动初始化函数式接口的实例了,这些对象和类的管理由编译器处理。

方法引用和构造器引用

方法引用(Method Reference)是一种简便方式,可以在只想调用一个方法而不做其他操作时,直接使用某个方法的逻辑。 原理类似于Lambda表达式,编译器自动为你创建函数式接口的实例。 例如: var timer = new Timer(1000, System.out::println)

要注意方法重载的选择受函数式接口的签名影响。 方法引用支持三种语法: object::instanceMethod:lambda表达式的参数列表原样传送到这个对象的这个方法的参数中。 Class::instanceMethod:lambda表达式的第一个参数成为方法的隐式参数,也就是说:String::compareToIgnoreCase, 等价于(a, b) -> a.compareToIgnoreCase(b) Class::staticMethod: 所有参数传递到静态方法。

构造器引用 格式:Class::new 解决的问题:将类型被擦除的数组重新构建为特定对象数组。 例如:Person[] people = stream.toArray(Person::new)

变量作用域和闭包

Lambda表达式由三部分组成:

  1. 一个代码块
  2. 参数
  3. 自由变量的值,这里是指非参数切不在代码块内被定义的变量

当lambda体使用了外部的变量时,称为这个变量被捕获(captured) 捕获capture自由变量的代码块称为闭包closure。lambda表达式就是闭包。

捕获变量的限制(这是为了并发安全考虑):

  1. 在lambda体中,不可以改变捕获的变量。
  2. 捕获的变量不可能在外部改变
  3. 捕获的变量必须是事实最终变量(efectively final)。aka,变量初始化后不会再被赋新值。

lambda表达式的体与它的上一层嵌套块有相同的作用域。也就是说,lambda表达式不新建作用域,参数和内部变量不能和上一层重名。 这也是为什么你可以直接使用外层方法的this

Lambda表达式的应用

《Core Java 中文版》6.2.7

为什么要使用Lambda表达式?一个重要原因是支持代码的延迟执行(deferred execution) 换句话说,如果不需要延迟执行,那么很可能不需要lambda表达式。

  • 在一个单独的线程运行代码
  • 在算法的适当位置运行代码(比如排序的比较操作)
  • 发生某种事件时才触发代码
  • 只在必要时才执行代码

常用的函数式接口:

Runnable
Supplier<T>
Comsumer<T>
BiConsumer<T, U>
Function<T,R>
BiFunction<T, U, R>
UnaryOperator<T>
BynaryOperator<T>
Predicate<T>
BiPredicator<T, U>

除了Runnable,它们都在java.util.function里面

基本类型int, long, double还有34个特殊化接口,它们比上述的一般化接口更高效。 你可以用@FunctionalInterface来指明你写了一个函数式接口。

内部类

定义在一个类中的类称为内部类。 内部类的特性:

  1. 可以对同一个包中的其它类隐藏
  2. 可以访问定义它的类的作用域中的数据,包括原本私有的数据。 静态内部类:只可访问static字段 非静态内部类:可以访问静态和实例字段 这是一个由编译器处理的语法糖,对所有内部类编译器会隐式创建一个字段,获取外部类的实例指针。然后改写内部类的所有构造器,将实例指针作为第一个参数传递。 在外部类中初始化时,可以隐式传递初始化上下文处的this指针:InnerClass inner = new Inner(123, 456);。 在其他类中初始化时,必须指明外部类实例(显式传递):OuterClass.InnerClass inner = outerObj.new Inner(123, 456);

局部内部类

局部内部类没有访问说明符,作用域限定在代码块中。 除了可以访问外部类字段,还可以访问代码块中的任何事实最终变量。 厉害的是,和闭包一样,即使局部变量在代码块中的生命周期死了,他居然还可以访问这个变量创建时的值。

匿名内部类


var instance = new Interface() {
  // implementations
};

var instance2 = new SuperClass(initParam, initParam2) { // 构造参数传给超类的构造器
{
  // 可以有对象初始化块
}
  // extensions
};

传统上匿名内部类用于实现事件监听器和其它回调。但现在最好还是使用Lambda表达式。 匿名内部类的类型通常是“不可指示的”(nondenoteble),所以要用var关键字让编译器自己推导。

成员内部类

成员内部类指声明在外部类的作用域内、独立于方法或代码块之外,且未被 static 修饰的类。 成员内部类持有外部类的实例引用,且生命周期与外部类绑定。支持和字段相同的四种访问控制符。

静态内部类

声明为static的内部类,不持有外部类的实例引用。 访问限制符只有private和默认 可以拥有static属性和方法 在接口中声明的类自动是public和static 类中声明的接口、记录和枚举自动是static

可以通过OuterClass.InnerStatic的方式使用静态内部类。