在之前的章節(jié)中,我們的討論基本都是基于Java 7的,從本節(jié)開(kāi)始,我們探討Java 8的一些特性,主要內(nèi)容包括:

  • 傳遞行為代碼 - Lambda表達(dá)式

  • 函數(shù)式數(shù)據(jù)處理 - 流

  • 組合式異步編程 - CompletableFuture

  • 新的日期和時(shí)間API

本節(jié),我們先討論Lambda表達(dá)式,它是什么?有什么用呢?

Lambda表達(dá)式是Java 8新引入的一種語(yǔ)法,是一種緊湊的傳遞代碼的方式,它的名字來(lái)源于學(xué)術(shù)界的λ演算,具體我們就不探討了。

理解Lambda表達(dá)式,我們先回顧一下接口、匿名內(nèi)部類和代碼傳遞。

通過(guò)接口傳遞代碼

我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">19節(jié)介紹過(guò)接口以及面向接口的編程,針對(duì)接口而非具體類型進(jìn)行編程,可以降低程序的耦合性、提高靈活性、提高復(fù)用性。接口常被用于傳遞代碼,比如,在59節(jié),我們介紹過(guò)File的如下方法:

public String[] list(FilenameFilter filter)public File[] listFiles(FilenameFilter filter)

list和listFiles需要的其實(shí)不是FilenameFilter對(duì)象,而是它包含的如下方法:

boolean accept(File dir, String name);

或者說(shuō),list和listFiles希望接受一段方法代碼作為參數(shù),但沒(méi)有辦法直接傳遞這個(gè)方法代碼本身,只能傳遞一個(gè)接口。

再比如,我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">53節(jié)介紹過(guò)Collections的一些算法,很多方法都接受一個(gè)參數(shù)Comparator,比如:

public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)public static <T> void sort(List<T> list, Comparator<? super T> c)

它們需要的也不是Comparator對(duì)象,而是它包含的如下方法:

int compare(T o1, T o2);

但是,沒(méi)有辦法直接傳遞方法,只能傳遞一個(gè)接口。

我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">77節(jié)介紹過(guò)異步任務(wù)執(zhí)行服務(wù)ExecutorService,提交任務(wù)的方法有:

<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

Callable和Runnable接口也用于傳遞任務(wù)代碼。

通過(guò)接口傳遞行為代碼,就要傳遞一個(gè)實(shí)現(xiàn)了該接口的實(shí)例對(duì)象,在之前的章節(jié)中,最簡(jiǎn)潔的方式是使用匿名內(nèi)部類,比如:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

//列出當(dāng)前目錄下的所有后綴為.txt的文件File f = new File(".");
File[] files = f.listFiles(new FilenameFilter(){
    @Override    public boolean accept(File dir, String name) {        if(name.endsWith(".txt")){            return true;
        }        return false;
    }
});

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

將files按照文件名排序,代碼為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Arrays.sort(files, new Comparator<File>() {

    @Override    public int compare(File f1, File f2) {        return f1.getName().compareTo(f2.getName());
    }
});

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

提交一個(gè)最簡(jiǎn)單的任務(wù),代碼為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

ExecutorService executor = Executors.newFixedThreadPool(100);
executor.submit(new Runnable() {
    @Override    public void run() {
        System.out.println("hello world");
    }
});

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Lambda表達(dá)式

語(yǔ)法

Java 8提供了一種新的緊湊的傳遞代碼的語(yǔ)法 - Lambda表達(dá)式。對(duì)于前面列出文件的例子,代碼可以改為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

File f = new File(".");
File[] files = f.listFiles((File dir, String name) -> {    if (name.endsWith(".txt")) {        return true;
    }    return false;
});

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

可以看出,相比匿名內(nèi)部類,傳遞代碼變得更為直觀,不再有實(shí)現(xiàn)接口的模板代碼,不再聲明方法,也名字也沒(méi)有,而是直接給出了方法的實(shí)現(xiàn)代碼。Lambda表達(dá)式由->分隔為兩部分,前面是方法的參數(shù),后面{}內(nèi)是方法的代碼。

上面代碼可以簡(jiǎn)化為:

File[] files = f.listFiles((File dir, String name) -> {    return name.endsWith(".txt");
});

當(dāng)主體代碼只有一條語(yǔ)句的時(shí)候,括號(hào)和return語(yǔ)句也可以省略,上面代碼可以變?yōu)椋?/p>

File[] files = f.listFiles((File dir, String name) -> name.endsWith(".txt"));

注意,沒(méi)有括號(hào)的時(shí)候,主體代碼是一個(gè)表達(dá)式,這個(gè)表達(dá)式的值就是函數(shù)的返回值,結(jié)尾不能加分號(hào);,也不能加return語(yǔ)句。

方法的參數(shù)類型聲明也可以省略,上面代碼還可以繼續(xù)簡(jiǎn)化為:

File[] files = f.listFiles((dir, name) -> name.endsWith(".txt"));

之所以可以省略方法的參數(shù)類型,是因?yàn)镴ava可以自動(dòng)推斷出來(lái),它知道listFiles接受的參數(shù)類型是FilenameFilter,這個(gè)接口只有一個(gè)方法accept,這個(gè)方法的兩個(gè)參數(shù)類型分別是File和String。

這樣簡(jiǎn)化下來(lái),代碼是不是簡(jiǎn)潔清楚多了?

排序的代碼用Lambda表達(dá)式可以寫(xiě)為:

Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName()));

提交任務(wù)的代碼用Lambda表達(dá)式可以寫(xiě)為:

executor.submit(()->System.out.println("hello"));

參數(shù)部分為空,寫(xiě)為()。

當(dāng)參數(shù)只有一個(gè)的時(shí)候,參數(shù)部分的括號(hào)可以省略,比如,F(xiàn)ile還有如下方法:

public File[] listFiles(FileFilter filter)

FileFilter的定義為:

public interface FileFilter {    boolean accept(File pathname);
}

使用FileFilter重寫(xiě)上面的列舉文件的例子,代碼可以為:

File[] files = f.listFiles(path -> path.getName().endsWith(".txt"));

變量引用

與匿名內(nèi)部類類似,Lambda表達(dá)式也可以訪問(wèn)定義在主體代碼外部的變量,但對(duì)于局部變量,它也只能訪問(wèn)final類型的變量,與匿名內(nèi)部類的區(qū)別是,它不要求變量聲明為final,但變量事實(shí)上不能被重新賦值。比如:

String msg = "hello world";
executor.submit(()->System.out.println(msg));

可以訪問(wèn)局部變量msg,但msg不能被重新賦值,如果這樣寫(xiě):

String msg = "hello world";
msg = "good morning";
executor.submit(()->System.out.println(msg));

Java編譯器會(huì)提示錯(cuò)誤。

這個(gè)原因與匿名內(nèi)部類是一樣的,Java會(huì)將msg的值作為參數(shù)傳遞給Lambda表達(dá)式,為L(zhǎng)ambda表達(dá)式建立一個(gè)副本,它的代碼訪問(wèn)的是這個(gè)副本,而不是外部聲明的msg變量。如果允許msg被修改,則程序員可能會(huì)誤以為L(zhǎng)ambda表達(dá)式會(huì)讀到修改后的值,引起更多的混淆。

為什么非要建副本,直接訪問(wèn)外部的msg變量不行嗎?不行,因?yàn)閙sg定義在棧中,當(dāng)Lambda表達(dá)式被執(zhí)行的時(shí)候,msg可能早已被釋放了。如果希望能夠修改值,可以將變量定義為實(shí)例變量,或者,將變量定義為數(shù)組,比如:

String[] msg = new String[]{"hello world"};
msg[0] = "good morning";
executor.submit(()->System.out.println(msg[0]));

與匿名內(nèi)部類比較

從以上內(nèi)容可以看出,Lambda表達(dá)式與匿名內(nèi)部類很像,主要就是簡(jiǎn)化了語(yǔ)法,那它是不是語(yǔ)法糖,內(nèi)部實(shí)現(xiàn)其實(shí)就是內(nèi)部類呢?答案是否定的,Java會(huì)為每個(gè)匿名內(nèi)部類生成一個(gè)類,但Lambda表達(dá)式不會(huì),Lambda表達(dá)式通常比較短,為每個(gè)表達(dá)式生成一個(gè)類會(huì)生成大量的類,性能會(huì)受到影響。

Java利用了Java 7引入的為支持動(dòng)態(tài)類型語(yǔ)言引入的invokedynamic指令、方法句柄(method handle)等,具體實(shí)現(xiàn)比較復(fù)雜,我們就不探討了,感興趣可以參看http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html,我們需要知道的是,Java的實(shí)現(xiàn)是非常高效的,不用擔(dān)心生成太多類的問(wèn)題。

Lambda表達(dá)式不是匿名內(nèi)部類,那它的類型到底是什么呢?是函數(shù)式接口。

函數(shù)式接口

Java 8引入了函數(shù)式接口的概念,函數(shù)式接口也是接口,但只能有一個(gè)抽象方法,前面提及的接口都只有一個(gè)抽象方法,都是函數(shù)式接口。之所以強(qiáng)調(diào)是"抽象"方法,是因?yàn)镴ava 8中還允許定義其他方法,我們待會(huì)會(huì)談到。Lambda表達(dá)式可以賦值給函數(shù)式接口,比如:

FileFilter filter = path -> path.getName().endsWith(".txt");
FilenameFilter fileNameFilter = (dir, name) -> name.endsWith(".txt");
Comparator<File> comparator = (f1, f2) -> f1.getName().compareTo(f2.getName());
Runnable task = () -> System.out.println("hello world");

如果看這些接口的定義,會(huì)發(fā)現(xiàn)它們都有一個(gè)注解@FunctionalInterface,比如:

@FunctionalInterfacepublic interface Runnable {    public abstract void run();
}

@FunctionalInterface用于清晰地告知使用者,這是一個(gè)函數(shù)式接口,不過(guò),這個(gè)注解不是必需的,不加,只要只有一個(gè)抽象方法,也是函數(shù)式接口。但如果加了,而又定義了超過(guò)一個(gè)抽象方法,Java編譯器會(huì)報(bào)錯(cuò),這類似于我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">85節(jié)介紹的Override注解。

預(yù)定義的函數(shù)式接口

接口列表

Java 8定義了大量的預(yù)定義函數(shù)式接口,用于常見(jiàn)類型的代碼傳遞,這些函數(shù)定義在包java.util.function下,主要的有:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

對(duì)于基本類型boolean, int, long和double,為避免裝箱/拆箱,Java 8提供了一些專門(mén)的函數(shù),比如,int相關(guān)的主要函數(shù)有:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

這些函數(shù)有什么用呢?它們被大量使用于Java 8的函數(shù)式數(shù)據(jù)處理Stream相關(guān)的類中,關(guān)于Stream,我們下節(jié)介紹。

即使不使用Stream,也可以在自己的代碼中直接使用這些預(yù)定義的函數(shù),我們看一些簡(jiǎn)單的示例。

Predicate示例

為便于舉例,我們先定義一個(gè)簡(jiǎn)單的學(xué)生類Student,有name和score兩個(gè)屬性,如下所示,我們省略了getter/setter方法。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

static class Student {
    String name;    double score;    
    public Student(String name, double score) {        this.name = name;        this.score = score;
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

有一個(gè)學(xué)生列表:

List<Student> students = Arrays.asList(new Student[] {        new Student("zhangsan", 89d),        new Student("lisi", 89d),        new Student("wangwu", 98d) });

在日常開(kāi)發(fā)中,列表處理的一個(gè)常見(jiàn)需求是過(guò)濾,列表的類型經(jīng)常不一樣,過(guò)濾的條件也經(jīng)常變化,但主體邏輯都是類似的,可以借助Predicate寫(xiě)一個(gè)通用的方法,如下所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static <E> List<E> filter(List<E> list, Predicate<E> pred) {
    List<E> retList = new ArrayList<>();    for (E e : list) {        if (pred.test(e)) {
            retList.add(e);
        }
    }    return retList;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

這個(gè)方法可以這么用:

// 過(guò)濾90分以上的students = filter(students, t -> t.getScore() > 90);

Function示例

列表處理的另一個(gè)常見(jiàn)需求是轉(zhuǎn)換,比如,給定一個(gè)學(xué)生列表,需要返回名稱列表,或者將名稱轉(zhuǎn)換為大寫(xiě)返回,可以借助Function寫(xiě)一個(gè)通用的方法,如下所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
    List<R> retList = new ArrayList<>(list.size());    for (T e : list) {
        retList.add(mapper.apply(e));
    }    return retList;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

根據(jù)學(xué)生列表返回名稱列表的代碼可以為:

List<String> names = map(students, t -> t.getName());

將學(xué)生名稱轉(zhuǎn)換為大寫(xiě)的代碼可以為:

students = map(students, t -> new Student(t.getName().toUpperCase(), t.getScore()));

Consumer示例

在上面轉(zhuǎn)換學(xué)生名稱為大寫(xiě)的例子中,我們?yōu)槊總€(gè)學(xué)生創(chuàng)建了一個(gè)新的對(duì)象,另一種常見(jiàn)的情況是直接修改原對(duì)象,具體怎么修改通過(guò)代碼傳遞,這時(shí),可以用Consumer寫(xiě)一個(gè)通用的方法,比如:

public static <E> void foreach(List<E> list, Consumer<E> consumer) {    for (E e : list) {
        consumer.accept(e);
    }
}

上面轉(zhuǎn)換為大寫(xiě)的例子可以改為:

foreach(students, t -> t.setName(t.getName().toUpperCase()));

以上這些示例主要用于演示函數(shù)式接口的基本概念,實(shí)際中應(yīng)該使用下節(jié)介紹的流API。

方法引用

基本用法
Lambda表達(dá)式經(jīng)常就是調(diào)用對(duì)象的某個(gè)方法,比如:

List<String> names = map(students, t -> t.getName());

這時(shí),它可以進(jìn)一步簡(jiǎn)化,如下所示:

List<String> names = map(students, Student::getName);

Student::getName這種寫(xiě)法,是Java 8引入的一種新語(yǔ)法,稱之為方法引用,它是Lambda表達(dá)式的一種簡(jiǎn)寫(xiě)方法,由::分隔為兩部分,前面是類名或變量名,后面是方法名。方法可以是實(shí)例方法,也可以是靜態(tài)方法,但含義不同。

我們看一些例子,還是以Student為例,先增加一個(gè)靜態(tài)方法:

public static String getCollegeName(){    return "Laoma School";
}

靜態(tài)方法

對(duì)于靜態(tài)方法,如下語(yǔ)句:

Supplier<String> s = Student::getCollegeName;

等價(jià)于:

Supplier<String> s = () -> Student.getCollegeName();

它們的參數(shù)都是空,返回類型為String。

實(shí)例方法

而對(duì)于實(shí)例方法,它第一個(gè)參數(shù)就是該類型的實(shí)例,比如,如下語(yǔ)句:

Function<Student, String> f = Student::getName;

等價(jià)于:

Function<Student, String> f = (Student t) -> t.getName();

對(duì)于Student::setName,它是一個(gè)BiConsumer,即:

BiConsumer<Student, String> c = Student::setName;

等價(jià)于:

BiConsumer<Student, String> c = (t, name) -> t.setName(name);

通過(guò)變量引用方法

如果方法引用的第一部分是變量名,則相當(dāng)于調(diào)用那個(gè)對(duì)象的方法,比如:

Student t = new Student("張三", 89d);
Supplier<String> s = t::getName;

等價(jià)于:

Supplier<String> s = () -> t.getName();

而:

Consumer<String> consumer = t::setName;

等價(jià)于:

Consumer<String> consumer = (name) -> t.setName(name);

構(gòu)造方法

對(duì)于構(gòu)造方法,方法引用的語(yǔ)法是<類名>::new,如Student::new,如下語(yǔ)句:

BiFunction<String, Double, Student> s = (name, score) -> new Student(name, score);

等價(jià)于:

BiFunction<String, Double, Student> s = Student::new;

函數(shù)的復(fù)合

在前面的例子中,函數(shù)式接口都用作方法的參數(shù),其他部分通過(guò)Lambda表達(dá)式傳遞具體代碼給它,函數(shù)式接口和Lambda表達(dá)式還可用作方法的返回值,傳遞代碼回調(diào)用者,將這兩種用法結(jié)合起來(lái),可以構(gòu)造復(fù)合的函數(shù),使程序簡(jiǎn)潔易讀。

下面我們會(huì)看一些例子,在介紹例子之前,我們先需要介紹Java 8對(duì)接口的增強(qiáng)。

接口的靜態(tài)方法和默認(rèn)方法

在Java 8之前,接口中的方法都是抽象方法,都沒(méi)有實(shí)現(xiàn)體,Java 8允許在接口中定義兩類新方法:靜態(tài)方法和默認(rèn)方法,它們有實(shí)現(xiàn)體,比如:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public interface IDemo {    void hello();    public static void test() {
        System.out.println("hello");
    }    default void hi() {
        System.out.println("hi");
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

test()就是一個(gè)靜態(tài)方法,可以通過(guò)IDemo.test()調(diào)用。在接口不能定義靜態(tài)方法之前,相關(guān)的靜態(tài)方法往往定義在單獨(dú)的類中,比如,Collection接口有一個(gè)對(duì)應(yīng)的單獨(dú)的類Collections,在Java 8中,就可以直接寫(xiě)在接口中了,比如Comparator接口就定義了多個(gè)靜態(tài)方法。

hi()是一個(gè)默認(rèn)方法,由關(guān)鍵字default標(biāo)識(shí),默認(rèn)方法與抽象方法都是接口的方法,不同在于,它有默認(rèn)的實(shí)現(xiàn),實(shí)現(xiàn)類可以改變它的實(shí)現(xiàn),也可以不改變。引入默認(rèn)方法主要是函數(shù)式數(shù)據(jù)處理的需求,是為了便于給接口增加功能。

在沒(méi)有默認(rèn)方法之前,Java是很難給接口增加功能的,比如List接口,因?yàn)橛刑喾荍ava JDK控制的代碼實(shí)現(xiàn)了該接口,如果給接口增加一個(gè)方法,則那些接口的實(shí)現(xiàn)就無(wú)法在新版Java 上運(yùn)行,必須改寫(xiě)代碼,實(shí)現(xiàn)新的方法,這顯然是無(wú)法接受的。函數(shù)式數(shù)據(jù)處理需要給一些接口增加一些新的方法,所以就有了默認(rèn)方法的概念,接口增加了新方法,而接口現(xiàn)有的實(shí)現(xiàn)類也不需要必須實(shí)現(xiàn)它。

看一些例子,List接口增加了sort方法,其定義為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}http://www.cnblogs.com/swiftma/p/7123356.html