首页 > 代码库 > 第一章 Lambda表达式

第一章 Lambda表达式

1.1 Why Lambdas?

当你操作多线程的时候,你会像下面这样将要处理的代码放到run()函数中:

class Worker implements Runnable {    public void run() {        for (int i = 0; i < 1000; i++)              doWork();      }    ...}

然后,当你想要执行这段代码的时候,需要构造Worker的实例来执行它。你可以把放入到线程池或者简单处理启动一个线程:

Worker w = new Worker();new Thread(w).start();

这段代码的重点在于你要将你想要处理的逻辑放入到run()方法中。

再来考虑一个场景,自定义排序。如果你不想按照字典的默认排序,而是想要按照字符串的长度来进行排序的话,你需要通过一个Comparator对象来进行排序:

class LengthComparator implements Comparator<String> {    public int compare(String first, String second) {        return Integer.Compare(first.length(), second.length());    }}Arrays.sort(strings, new LengthComparator());

sort函数会一直调用compare方法,保证数组被重新按照长度进行排序。

 


 

注意:如果Integer.compare(x, y)中的x.equals(y)==true则返回0,如果x<y则返回负数,x>y则返回正数。这个静态方法已经被添加到Java7中。千万不要计算x-y然后和x或者y比较,因为x-y可能会发生溢出。


还有一种场景就是按钮的方法回调。你把回调的处理放入实现的监听接口函数中,构造一个实例,然后将这个实例注册到按钮上:
button.setOnAction(new EventHandler<ActionEvent>) {    public void handle(ActionEvent event) {        System.out.println("Thanks for clicking");      }});

当这个按钮被点击的时候,handle方法将被执行。

从上面几个例子中你可以看到,这些处理都是需要一大段的代码来处理。这么复杂的处理不是每个人都可以非常容易理解的,所以在Java8中添加了一个非常重大的特性,那就是Lambda表达式。

1.2 Lambda表达式语法

现在咱们再来看一下之前排序的例子:

Integer.compare(first.length(), second.length());

first和second都是字符串数组,Java是一个强类型的语言,所以我们必须这样来处理:

(String first, String second)    -> Integer.compare(first.length(), second.length());

这就是你的第一个lambda表达式了!这样的表达式非常简单。

可以看到,在这个lambda表达式中有->符号。如果这段代码不能用一个简单的表达式来展示的话,我们可以用{}来封装一段代码,比如:

(String first, String second) -> {    if (first.length() < second.length()) return -1;    else if (first.length() > second.length()) return 1;    else return 0;}

当一个lambda表达式没有参数的时候,我们可以这样来做:

() -> { for (int i = 0; i < 1000; i++) doWorker(); }

如果一个lambda表达式中的参数可以推断出它的类型,那么我们还可以这样:

Comparator<String> comp    =  (first, second)  // 等价于(String first, String second)         -> Integer.compare(first.length(), second.length());

 


 

你可以再lambda表达式的参数添加标注或者final修饰符:

    (final String name) -> ...

    (@NonNull String name) -> ...


 

lambda表达式中的返回类型我们一直没有提及,那是因为在lambda上下文中,可以推断出它,比如:

(String first, String second) -> Integer.compare(first.length(), second.length())

从这里就可以看出,返回类型就是int。

1.3 功能接口

Java中已经封装了一些存在的接口代码,想Runnable和Comparator。Lambda对于这些接口是向下兼容的。

当一个单实例抽象方法的接口对象,我们可以用lambda表达式来展示出来,我们就把这个接口叫做功能接口。

为了展示功能接口,我们来看一下Arrays.sort()方法。它的第二个参数需要一个Comparator的实例,可以用lambda这样做:

Arrays.sort(words,    (first, second) -> Integer.compare(first.length(), second.length()));

和传统的内部类相比,lambda表达式可以非常高效的完成它。lambda表达式最好的理解为它是一个函数,而不是对象。

lambda的语法非常简短和简单,再来一例:

button.setOnAction(event ->    System.out.println("Thanks for clicking")); 

和内部类相比,可读性大幅提升。

事实上,在Java中,你只能针对功能接口应用lambda表达式。

Java API在java.util.function中定义了一些常用的功能接口。比如,BiFunction<T, U, R>,这个接口通过参数类型T和U,返回类型R,我们可以应用在刚才的例子上:

BiFunction<String, String, Integer> comp    = (first, second) -> Integer.compare(first.length(), second.length());

当然,这里只是构造了一个比较器,只有Arrays.sort方法调用的时候才能进行排序。

1.4 方法引用

有些时候,一个方法你不得不带上一些多余的代码。比如:当你想要打印一个按钮点击之后的事件对象:

button.setOnAction(event->System.out.println(event));

如果可以只通过println方法来做的话就更nice了,比如:

button.setOnAction(System.out::println);

System.out::println表达式是一个方法引用,它等价于x->System.out.println(x).

再比如,我们想要对忽略大小写的数组进行排序:

Arrays.sort(strings, String::compareToIgnoreCase);

这些例子中,::操作符的规则为:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

前2个例子中,方法引用等价于lambda表达式中的函数参数,System.out::println等价于x->System.out.println(x),相应的,Math::pow等价于(x, y) -> Math.pow(x, y).

第三个例子中,第一个参数变成了函数的对象,String::compareToIgnoreCase等价于(x, y) -> x.compareToIgnoreCase(y).

你也可以使用this,比如:this::equals等价于x->this.equals(x),当然也可以使用super.

super::instanceMethod

举个例子:

class Greeter {    public void greet() {        System.out.println("Hello world");    }}class ConcurrentGreeter extends Greeter {    public void greet() {        Thread t = new Thread(super::greet);        t.start();    }}

1.5 构造引用

除了new方法,构造引用类似方法引用。举个例子,Button::new是一个Button的构造器。

List<String> labels = ...;Stream<Button> stream = labels.stream().map(Button::new);List<Button> buttons = stream.collect(Collections.toList());

1.6 变量域

当你在lambda中想要从闭包函数或者类中获取变量,如下:

public static void repeatMessage(String text, int count) {    Runnable r = () -> {        for (int i = 0; i < count; i++) {            System.out.println(text);            Thread.yield();        }    };    new Thread(r).start();}

 

1.7 默认方法

待续