为什么要用Lambda表达式
方便在传递代码块,并且使得语言的API更简单、更一致、更强大。Java8之前,传递代码块是用一个内部类封装,然后调用内部类执行方法。
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Dialog1 dialog = new Dialog1(parent.getShell());
dialog.open();
}
});
}
Lambda表达式语法
只有一个语句,不用加封号
(String s1, String s2) -> return s1.length() – s2.length()
有多个语句,就用大括号包起来
(String str1, String str2) -> {
int length1 = str1.length();
int length2 = str2.length();
return length1 - length2;
}
如果没有参数,就用一对小括号
() -> System.out.println("Hello.")
当参数类型可以被推导出来时,可以省略参数类型,只有一个参数时,可以不加小括号
EventHandler<ActionEvent> listener = event -> System.out.println("Click event");
可以向对待方法一样,加注解
(final @Nonnull String str) -> str.length()
函数式接口
只包含一个抽象方法的接口,我们可以通过Lambda表达式创建该接口的实例。
@FunctionalInterface
interface Add {
int add(int i, int j);
public default boolean preCheck(int i, int j) {
return i > 0 && j > 0;
}
public static void sayHello() {
System.out.println("Hello.");
}
}
Java中,Lambda表达式唯一能做的就是创建函数式接口的实例。Lambda表达式没有类型,不能用Object对象引用,只能用函数式接口引用。
// Old way to create Interface instance.
Add add1 = new Add() {
@Override
public int add(int i, int j) {
return i + j;
}
};
// New way to create Interface instance.
Add add2 = (i, j) -> i + j;
Lambda表达式需要匹配函数式接口抽象方法的参数,返回值,以及是否抛异常,有一个不匹配,Eclipse都会报错。
// 参数,返回值,Exception都和Runnnable不匹配
Thread thread = new Thread((String s) -> {
if (s == null) {
return false;
} else {
throws Exception();
}
});
方法引用
可以将已经有的方法实现传递给其他代码。本质上是使用一个已有方法实现函数式接口的抽象接口,从而创建函数式接口的实例。如果有多个重载的同名方法,编译器根据函数式接口抽象方法的参数自动匹配,如果没有匹配的,就报错。
- 对象::实例方法
- 类::静态方法
- 类::实例方法
前两钟方法的方法,等同于提供方法参数的Lambda表达式。如:
Math::pow等同于(x, y) -> Math.pow(x, y).
第三种情况,会掉用第一个参数对象的代码来执行。
String::comparetoIgnoreCase等同于(x, y) -> x.comparetoIgnoreCase(y)
同样可以使用this::methodName, super::methodName, ClassName.this::methodName, ClassName.super::methodName.
构造器引用
同方法引用,只是方法名为new。
List<String> labels = ...; Stream<Button> buttons labels.stream().map(Button::new);
数组构造器引用,解决Java不能创建泛型数组,new T[n]是错误的。
Button[] buttons stream.toArray(Button[]::new)
Lambda表达式变量作用域
和匿名内部类一样,可以访问外面类的变量和方法。Lambda表达式不能修改外面变量的值,但是外面变量的定义可以不用加final。在匿名内部类中,是必须加final才能访问的。注意:Lambda中的this,不是指接口实例,而是Lambda表达式所在的类。
class Application {
public void doWork() {
Runnable runner = () -> {
System.out.println(this.toString());
};
}
public String toString() {
return Application.class.getName();
}
}
接口中的默认方法
interface Person {
long getId();
default String getName() {
return "Name";
}
}
默认方法终结了以前的一种经典模式,即提供一个接口,以及一个实现接口的大多数或者全部方法的抽象类。
如果一个接口定义了一个默认方法,二另外一个父类或者接口中又定义了一个同名的方法,应该选择哪个呢?
- 如果父类提供了具体的实现方法,选择父类中的方法,接口中的方法都被忽略。
- 如果多个接口提供了相同的默认方法,那么必须覆盖此方法来解决冲突。
接口中的静态方法
interface Path {
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
}
从技术角度来说,这是完全合法的,只是看起来违反了接口作为一个抽象定义的理念。接口中的静态方法,可以减少不必要的辅助类。