为什么要用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); } }
从技术角度来说,这是完全合法的,只是看起来违反了接口作为一个抽象定义的理念。接口中的静态方法,可以减少不必要的辅助类。