Java8 Lambda 表达式

By | 2016年4月12日

为什么要用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";
  }
}

默认方法终结了以前的一种经典模式,即提供一个接口,以及一个实现接口的大多数或者全部方法的抽象类。

如果一个接口定义了一个默认方法,二另外一个父类或者接口中又定义了一个同名的方法,应该选择哪个呢?

  1. 如果父类提供了具体的实现方法,选择父类中的方法,接口中的方法都被忽略。
  2. 如果多个接口提供了相同的默认方法,那么必须覆盖此方法来解决冲突。

接口中的静态方法

interface Path {
  public static Path get(String first, String... more) {
    return FileSystems.getDefault().getPath(first, more);
  }
}

从技术角度来说,这是完全合法的,只是看起来违反了接口作为一个抽象定义的理念。接口中的静态方法,可以减少不必要的辅助类。