Java 8新特性(一):Lambda表達式

本文首發於一書生VOID的博客。 原文連接:Java 8新特性(一):Lambda表達式html


2014年3月發佈的Java 8,有多是Java版本更新中變化最大的一次。新的Java 8爲開發者帶來了許多重量級的新特性,包括Lambda表達式,流式數據處理,新的Optional類,新的日期和時間API等。這些新特性給Java開發者帶來了福音,特別是Lambda表達式的支持,使程序設計更加簡化。本篇文章將討論行爲參數化,Lambda表達式,函數式接口等特性。java

行爲參數化

在軟件開發的過程當中,開發人員可能會遇到頻繁的需求變動,使他們不斷地修改程序以應對這些變化的需求,致使項目進度緩慢甚至項目延期。行爲參數化就是一種能夠幫助你應對頻繁需求變動的開發模式,簡單的說,就是預先定義一個代碼塊而不去執行它,把它當作參數傳遞給另外一個方法,這樣,這個方法的行爲就被這段代碼塊參數化了。git

爲了方便理解,咱們經過一個例子來說解行爲參數化的使用。假設咱們正在開發一個圖書管理系統,需求是要對圖書的做者進行過濾,篩選出指定做者的書籍。比較常見的作法就是編寫一個方法,把做者當成方法的參數:github

public List<Book> filterByAuthor(List<Book> books, String author) {
	List<Book> result = new ArrayList<>();
	for (Book book : books) {
		if (author.equals(book.getAuthor())) {
			result.add(book);
		}
	}
	return result;
}
複製代碼

如今客戶須要變動需求,添加過濾條件,按照出版社過濾,因而咱們不得再也不次編寫一個方法:express

public List<Book> filterByPublisher(List<Book> books, String publisher) {
	List<Book> result = new ArrayList<>();
	for (Book book : books) {
		if (publisher.equals(book.getPublisher())) {
			result.add(book);
		}
	}
	return result;
}
複製代碼

兩個方法除了名稱以外,內部的實現邏輯幾乎如出一轍,惟一的區別就是if判斷條件,前者判斷的是做者,後者判斷的是出版社。若是如今客戶又要增長需求,須要按照圖書的售價過濾,是否是須要再次將上面的方法複製一遍,將if判斷條件改成售價? No! 這種作法違背了DRY(Don’t Repeat Yourself,不要重複本身)原則,並且不利於後期維護,若是須要改變方法內部遍歷方式來提升性能,意味着每一個filterByXxx()方法都須要修改,工做量太大。app

一種可行的辦法是對過濾的條件作更高層的抽象,過濾的條件無非就是圖書的某些屬性(好比價格、出版社、出版日期、做者等),能夠聲明一個接口用於對過濾條件建模:ide

public interface BookPredicate {
    public boolean test(Book book);
}
複製代碼

BookPredicate接口只有一個抽象方法test(),該方法接受一個Book類型參數,返回一個boolean值,能夠用它來表示圖書的不一樣過濾條件。函數

接下來咱們對以前的過濾方法進行重構,將filterByXxx()方法的第二個參數換成上面定義的接口:性能

public List<Book> filter(List<Book> books, BookPredicate bookPredicate) {
    List<Book> result = new ArrayList<>();
	for (Book book : books) {
		if (bookPredicate.test(book)) {
			result.add(book);
		}
	}
	return result;
}
複製代碼

將過濾的條件換成BookPredicate的實現類,這裏採用了內部類:spa

// 根據做者過濾
final String author = "張三";
List<Book> result = filter(books, new BookPredicate() {
    @Override
    public boolean test(Book book) {
        return author.equals(book.getAuthor());
    }
});

// 根據圖書價格過濾
final double price = 100.00D;
List<Book> result = filter(books, new BookPredicate() {
    @Override
    public boolean test(Book book) {
        return price > book.getPrice();
    }
});
複製代碼

重構先後有什麼區別?咱們將方法中的if判斷條件換成了BookPredicate接口定義的test()方法,用於判斷是否知足過濾條件,將圖書過濾的邏輯交給了BookPredicate接口的實現類,而不是在filter()方法內部實現過濾,而BookPredicate接口又是filter()方法的參數。以上的步驟,就是將行爲參數化,也就是將圖書過濾的行爲(BookPredicate接口的實現類)當作filter()方法的參數。如今,能夠刪掉全部filterByXxx()的方法,只保留filter()方法,就算後期數據規模很龐大,須要改變集合的遍歷方式來提升性能,只須要在filter()方法內部作出相應的修改,而不用去修改其餘業務代碼。

不過,BookPredicate接口只是針對圖書的過濾,若是須要對其餘對象集合排序(如:用戶),又得從新申明一個接口。有一個辦法就是能夠用Java的泛型對它作進一步的抽象:

public interface Predicate<T> {
    public boolean test(T t);
}
複製代碼

如今你能夠把filter()方法用在任何對象的過濾中。

Lambda表達式

雖然咱們對filter()方法進行重構,並抽象了Predicate接口做爲過濾的條件,但實際上還須要編寫不少內部類來實現Predicate接口。使用內部類的方式實現Predicate接口有不少缺點:首先是代碼顯得臃腫不堪,可讀性差;其次,若是某個局部變量被內部類使用,這個變量必須使用final關鍵字修飾。在Java 8中,使用Lambda表達式能夠對內部類進一步簡化:

// 根據做者過濾
List<Book> result = filter(books, book -> "張三".equals(book.getAuthor()));

// 根據圖書價格過濾
List<Book> result = filter(books, book -> 100 > book.getPrice());
複製代碼

使用Lambda僅僅用一行代碼就對內部類進行了轉化,並且代碼變得更加清晰可讀。其中book -> "張三".equals(book.getAuthor())book -> 100 > book.getPrice()就是咱們接下來要研究的Lambda表達式。

Lambda表達式是什麼

Lambda表達式(lambda expression)是一個匿名函數,由數學中的λ演算而得名。在Java 8中能夠把Lambda表達式理解爲匿名函數,它沒有名稱,可是有參數列表、函數主體、返回類型等。

Lambda表達式的語法以下:

(parameters) -> { statements; }
複製代碼

爲何要使用Lambda表達式?前面你也看到了,在Java中使用內部類顯得十分冗長,要編寫不少樣板代碼,Lambda表達式正是爲了簡化這些步驟出現的,它使代碼變得清晰易懂。

如何使用Lambda表達式

Lambda表達式是爲了簡化內部類的,你能夠把它當成是內部類的一種簡寫方式,只要是有內部類的代碼塊,均可以轉化成Lambda表達式:

// Comparator排序
List<Integer> list = Arrays.asList(3, 1, 4, 5, 2);
list.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

// 使用Lambda表達式簡化
list.sort((o1, o2) -> o1.compareTo(o2));
複製代碼
// Runnable代碼塊
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello Man!");
    }
});

// 使用Lambda表達式簡化
Thread thread = new Thread(() -> System.out.println("Hello Man!"));
複製代碼

能夠看出,只要是內部類的代碼塊,就可使用Lambda表達式簡化,而且簡化後的代碼清晰易懂。甚至,Comparator排序的Lambda表達式還能夠進一步簡化:

list.sort(Integer::compareTo);
複製代碼

這種寫法被稱爲 方法引用,方法引用是Lambda表達式的簡便寫法。若是你的Lambda表達式只是調用這個方法,最好使用名稱調用,而不是描述如何調用,這樣能夠提升代碼的可讀性。

方法引用使用::分隔符,分隔符的前半部分表示引用類型,後面半部分表示引用的方法名稱。例如:Integer::compareTo表示引用類型爲Integer,引用名稱爲compareTo的方法。

相似使用方法引用的例子還有打印集合中的元素到控制檯中:

list.forEach(System.out::println);
複製代碼

函數式接口

若是你的好奇心使你翻看Runnable接口源代碼,你會發現該接口被一個@FunctionalInterface的註解修飾,這是Java 8中添加的新註解,用於表示 函數式接口

函數式接口又是什麼鬼?在Java 8中,把那些僅有一個抽象方法的接口稱爲函數式接口。若是一個接口被@FunctionalInterface註解標註,表示這個接口被設計成函數式接口,只能有一個抽象方法,若是你添加多個抽象方法,編譯時會提示「Multiple non-overriding abstract methods found in interface XXX」之類的錯誤。

函數式方法又能作什麼?Java8容許你以Lambda表達式的方式爲函數式接口提供實現,通俗的說,你能夠將整個Lambda表達式做爲接口的實現類。

除了Runnable以外,Java 8中內置了許多函數式接口供開發者使用,這些接口位於java.util.function包中,咱們以前使用的Predicate接口,已經被包含在這個包內,他們分別爲PredicateConsumerFunction,因爲咱們已經在以前的圖書過濾的例子中介紹了Predicate的用法,因此接下來主要介紹ConsumerFunction的用法。

Consumer

java.util.function.Consumer<T>定義了一個名叫accept()的抽象方法,它接受泛型T的對象,沒有返回(void)。若是你須要訪問類型T的對象,並對其執行某些操做,就可使用這個接口。好比,你能夠用它來建立一個forEach()方法,接受一個集合,並對集合中每一個元素執行操做:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

public static <T> void forEach(List<T> list, Consumer<T> consumer) {
    for(T t: list){
        consumer.accept(t);
    }
}

public static void main(String[] args) {
    List<String> list = Arrays.asList("A", "B", "C", "D");
    forEach(list, str -> System.out.println(str));
    // 也能夠寫成
    forEach(list, System.out::println);
}
複製代碼

Function

java.util.function.Function<T, R>接口定義了一個叫做apply()的方法,它接受一個泛型T的對象,並返回一個泛型R的對象。若是你須要定義一個Lambda,將輸入對象的信息映射到輸出,就可使用這個接口。好比,咱們須要計算一個圖書集合中每本書的做者名稱有幾個漢字(假設這些書的做者都是中國人):

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for(T s: list){
        result.add(f.apply(s));
    }
    return result;
}

public static void main(String[] args) {
    List<Book> books = Arrays.asList(
        new Book("張三", 99.00D),
        new Book("李四", 59.00D),
        new Book("王老五", 59.00D)
    );
    List<Integer> results = map(books, book -> book.getAuthor().length());
}
複製代碼

如今,你應該對Lambda表達式有一個初步的瞭解了,而且,你可使用Lambda表達式來重構你的代碼,提升代碼可讀性;使用行爲參數化來設計你的程序,讓程序更靈活。在下一篇文章將會介紹Java 8的另外一個特性——流式數據處理。

相關文章
相關標籤/搜索