天天看點

Java 8 新特性(一)lambda表達式

Java 9 好像也快出了,不過我連Java 8的新特性都還沒認真研究過,是以這幾篇文章就是來介紹Java 8的新特性的。首先,第一個重要的特性就是傳說中的lambda表達式了,雖然初學可能覺得這東西很難了解,但是一旦學會了,你就會發現離不開它了。

在現代程式設計語言中,lambda表達式這個語言特性已經成為了标配。當然不同語言中的lambda表達式概念和實作形式可能會稍有差别。不過不管在哪種語言中,我們都可以把lambda表達式簡單的了解為匿名函數。在Java中,lambda表達式和隻有一個方法的接口是差不多的,是以這樣的接口又叫做SAM接口(Single Abstract Method interfaces)。

初識lambda表達式

說了半天,下面先來看看實際例子吧。我們在編寫多線程代碼的時候,常常需要編寫實作

Runnable

的類。有時候為了省事,我們會寫成匿名内部類的形式。

Runnable task1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("任務一");
    }
};
           

匿名内部類不好了解,而且寫起來也非常醜陋。由于

Runnable

接口内隻有一個方法,是以它是一個SAM接口,我們可以用lambda表達式改寫它。那麼改寫之後是什麼樣子的呢?不要驚訝,隻需要一行代碼。

Runnable task2 = () -> System.out.println("任務二");
           

是以,我們可以看到lambda表達式的簡潔之處。下面就來詳細介紹一下lambda表達式的形式。

lambda表達式形式

lambda表達式可以用在需要SAM接口對象的地方,由于編譯器這時候知道所需要的接口和對應方法的簽名。是以lambda表達式的類型聲明可以省略,具體類型由編譯器自行推斷。lambda表達式的基本形式如下。

(參數清單) -> {方法體}
           

為了友善說明,我這裡定義了一組接口,用于之後的lambda表達式實作。

@FunctionalInterface
interface Interface1 {
    void f();
}


@FunctionalInterface
interface Interface2 {
    void f(int a);
}

@FunctionalInterface
interface Interface3 {
    void f(int a, int b);
}

@FunctionalInterface
interface Interface4 {
    int f(int a, int b);
}
           

無參lambda

先來看看無參數的lambda表達式。特别地,如果方法體隻有一行代碼,可以省略方法體的大括号。

Interface1 interface1 = new Interface1() {
    @Override
    public void f() {
        System.out.println("f");
    }
};
Interface1 lambda1 = () -> {
    System.out.println("f");
};
//如果方法體隻有一行代碼,可以省略大括号
Interface1 lambda1a = () -> System.out.println("f");
           

多個參數的lambda

我們可以看到由于編譯器可以進行類型推斷,是以lambda表達式的參數清單不需要參數類型聲明,而且由于我隻有一行代碼,是以大括号也省略了。

//多個參數lambda
Interface3 interface3 = new Interface3() {
    @Override
    public void f(int a, int b) {
        System.out.println(a + b);
    }
};
Interface3 lambda3 = (a, b) -> System.out.println(a + b);
           

帶傳回值的lambda

帶傳回值的lambda表達式其實和普通的一樣,隻不過需要在方法體的最後使用

return

語句。值得注意的是最後一個例子,如果方法體本身足夠簡單,可以直接将傳回值表達式寫在lambda表達式的右邊,編譯器也會正确對待這種情況。

//帶有傳回值的lambda
Interface4 interface4 = new Interface4() {
    @Override
    public int f(int a, int b) {
        return a + b;
    }
};
Interface4 lambda4 = (a, b) -> {
    return a + b;
};
Interface4 lambda4a = (a, b) -> a + b;
           

單個參數的lambda

如果lambda表達式隻有一個參數,那麼參數清單的小括号也可以省略,例如下面的第二個例子。下面第三個例子示範了另一個新文法方法引用,如果lambda表達式的形式是

單個參數a -> 某個隻有一個參數的方法(a)

,那麼就可以簡寫成方法引用的形式

類::方法

。方法引用是一個新文法,如果見到了不要奇怪,這也是正确的Java代碼。

//一個參數的lambda
Interface2 interface2 = new Interface2() {
    @Override
    public void f(int a) {
        System.out.println(a);
    }
};
Interface2 lambda2 = (a) -> System.out.println(a);
Interface2 lambda2a = a -> System.out.println(a);
Interface2 methodReference = System.out::println;
           

函數接口

@FunctionalInterface

注解

如果檢視JDK源碼的一些接口,會發現它們上面添加了

@FunctionalInterface

注解。例如上面提到的

Runnable

接口。

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}
           

這個注解用于标注函數接口,也就是上面提到的SAM接口。如果一個接口中包含了不止一個方法,那麼函數接口注解就會觸發編譯錯誤。這個功能類似于

@Override

注解。當然并不是說要使用lambda表達式必須标記這個注解,隻要接口中隻有一個方法,那麼就可以使用lambda表達式。

其實函數接口可以包括不止一個方法,除了一個普通的抽象方法之外,函數接口還可以包含一個在

java.lang.Object

類中聲明的方法。Java 8新增了預設方法特性,如果接口的實作類沒有實作

java.lang.Object

類中的這些基本方法,那麼就會調用

java.lang.Object

類中的實作作為預設實作。

@FunctionalInterface
interface Interface5 {
    void f();

    String toString();
}

           

預定義的函數接口

java.util.function

包下定義了大量預定義的函數接口,它們包含了各種各樣的形式,基本涵蓋了程式設計的各個方面。同學們最好對這些函數接口的形式稍作了解,以後碰到的時候就不會一頭霧水了。

Java 8 新特性(一)lambda表達式

lambda表達式應用

編寫線程

前面提到了,可以使用lambda表達式簡化線程代碼的編寫,不再需要冗長的匿名内部類。

//編寫線程
Runnable task = () -> System.out.println("簡單的任務");
Executor executor = Executors.newCachedThreadPool();
executor.execute(task);
           

編寫比較器

比方說我們有一個學生類,有姓名和年齡兩個字段。如果需要按照某種規則來進行排序,可以使用比較器

Comparator

接口來比較,利用lambda表達式同樣可以簡化這部分代碼的編寫。

class Student {
    private String name;
    private int age;
    //其餘方法已省略
}
           

可以看到比較規則隻需要一條lambda表達式即可傳入,非常友善。後面兩個例子是

Comparator

接口新增的工具方法,可以幫助我們簡化比較代碼的編寫,這裡使用了方法引用來簡化代碼。

comparing

等方法的傳回值仍然是

Comparator

,是以可以鍊式調用進行多級比較。

List<Student> students = new ArrayList<>();
students.add(new Student("yitian", 25));
students.add(new Student("anna", 26));
students.add(new Student("wang5", 24));

students.sort((a, b) -> a.getAge() - b.getAge());
System.out.println(students);

students.sort(Comparator.comparing(Student::getName));
System.out.println(students);

students.sort(
        Comparator.comparing(Student::getName)
                .thenComparing(Student::getAge));
System.out.println(students);