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表达式由三部分组成:
- 一个代码块
- 参数
- 自由变量的值,这里是指非参数切不在代码块内被定义的变量
当lambda体使用了外部的变量时,称为这个变量被捕获(captured) 捕获capture自由变量的代码块称为闭包closure。lambda表达式就是闭包。
捕获变量的限制(这是为了并发安全考虑):
- 在lambda体中,不可以改变捕获的变量。
- 捕获的变量不可能在外部改变
- 捕获的变量必须是事实最终变量(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来指明你写了一个函数式接口。
内部类
定义在一个类中的类称为内部类。 内部类的特性:
- 可以对同一个包中的其它类隐藏
- 可以访问定义它的类的作用域中的数据,包括原本私有的数据。
静态内部类:只可访问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的方式使用静态内部类。