Java 8:1行為引數化
2 次檢視
行為引數化本質上是一塊程式碼並使其可用而不執行它。例如,它可以傳遞給方法。 由於Java 8引入了lambdas (最後),現在可以使用匿名函式來引數化方法的行為。如果您熟悉Javascript,Scala,Groovy或內建lambdas的任何其他語言,那麼您可能一直在使用行為引數化。
在軟體開發中,使用者需求可能會發生變化,這可能不會讓您驚訝。將行為作為引數傳遞可以幫助減輕變化的痛苦。
不幸的是,有些應用程式無法升級以與最新版本的Java一起執行。因此,我將介紹可用於Java 8之前的執行時的替代解決方案。在本文中,我將從如何使用以前的Java版本實現行為引數化的示例開始,然後將這些解決方案與lambdas進行比較。在這個過程中,我試圖展示函數語言程式設計的成語如何使您的生活更容易作為軟體開發人員。
示例域
我們來看一個過濾Java物件的例子。更具體地說,我將使用Java 7過濾 book
物件列表而不使用任何外部庫。該書類有3個領域: name
, pageCount
和 author
。想象一下,我們有一個圖書館應用程式,根據要求,應該可以找到超過200頁的書籍。
public class Book { private String name; private int pageCount; private String author; //getters and setters omitted... }
找到長篇小說的簡單方法(在我們的案例中是一本長篇小說是一本超過200頁的書)是迴圈遍歷書籍列表,使用if子句來檢查它是否超過指定數量的頁面,新增書到結果列表,最後還給它。
public static List<Book> findLongNovels(List<Book> books) { List<Book> result = new ArrayList<>(); for (Book book : books) { if (book.getPageCount() > 200) { result.add(book); } } return result; }
改變要求
一切都很好,花花公子,對吧?按照慣例,需求會發生變化,並且會增加新的要求。現在,庫應用程式應該能夠按作者過濾書籍。完成非常簡單。只需使用與以前相同的總體佈局。
public static List<Book> filterBooksByAuthor(List<Book> books, String author) { List<Book> result = new ArrayList<>(); for (Book book : books) { if (author.equals(book.getAuthor())) { result.add(book); } } return result; }
程式碼重用
如果 filterBooksByAuthor
與之前的 findLongNovels
方法進行比較,您可以清楚地看到它們非常相似。這是一個WET解決方案。讓我們乾涸吧。整體結構是一樣的。程式碼遍歷書籍列表並應用過濾子句。目標是保持迭代和過濾分離。使用Java 7時,我們可以建立一個 BookPredicate
可以定義過濾邏輯的介面。一個謂語本質上是一個布林值函式。由於Java 7沒有lambdas,我們將把謂詞包裝在一個類中。
public interface BookPredicate { boolean test(Book book); }
可以將過濾邏輯移動到實現該 BookPredicate
介面的單獨類。
public class LengthPredicate implements BookPredicate { private int length; public LengthPredicate(int length) { this.length = length; } @Override public boolean test(Book book) { return book.getPageCount() > length; } } public class AuthorPredicate implements BookPredicate { private String author; public AuthorPredicate(String author) { this.author = author; } @Override public boolean test(Book book) { return author.equals(book.getAuthor()); } }
在進行一些重構之後,可以重用迭代書籍列表的方法。
public static List<Book> filterBooks(List<Book> books, BookPredicate p) { List<Book> result = new ArrayList<>(); for (Book book : books) { if (p.test(book)) { result.add(book); } } return result; } //somewhere in main BookPredicate lengthPredicate = new LengthPredicate(200); BookPredicate authorPredicate = new AuthorPredicate("Lewis Carrol"); filterBooks(books, lengthPredicate); filterBooks(books, authorPredicate);
太冗長了?
現在我們不是在重複自己,但是嘿,這就是要編寫的程式碼。正如他們所說, Java是冗長的 。最初有2種方法可以過濾書籍。那是大約15行程式碼。刪除重複的程式碼並將過濾邏輯移到單獨的類後,有超過30行。雖然這對於一個小專案來說並不多,但是對於一個大型專案來說,這些線條會加起來。有什麼辦法可以寫出更簡潔的程式碼嗎?
匿名內部類
我們不是定義a的具體實現,而是動態 BookPredicate
建立一個。
filterBooks(books, new BookPredicate() { @Override public boolean test(Book book) { return "Lewis Carrol".equals(book.getAuthor()); } });
這很簡潔。它看起來幾乎像一個lambda。事實上,在使用Java 8時,IDE會建議您用lambda替換它。匿名內部類的缺點是它帶有樣板程式碼。需要例項化一個新物件,需要覆蓋一個方法,並在這裡和那裡使用一些花括號。該樣板使得更難以專注於實際重要的部分 – test
方法內部的比較。
使用第三方庫
正如所料,建立庫是為了克服語言的缺點。鮑勃叔叔在他的 部落格文章 中寫道,我們編寫框架來彌補我們希望用我們的語言缺少的功能。你見過的每個框架都只是這句話的回聲:
我的語言很糟糕!
有什麼替代品呢?Google Guava庫具有允許您進行更多功能樣式程式設計的謂詞。
Iterables.filter(books, new Predicate<Book>() { @Override public boolean apply(Book input) { return "Lewis Carrol".equals(input.getAuthor()); } });
它與我們用 filterBooks
方法實現的非常相似。在函數語言程式設計中,通過將謂詞應用於列表的每個元素來完成對項列表的過濾。 Filter
是函式式語言的常用功能。稍後我們將看到Java 8也包含它。使用Guava的好處是您不必編寫列表迭代程式碼和謂詞介面。
另一種可能的解決方案是將lambdaj與Hamcrest匹配器一起使用。lambdaj是一個庫,允許您以偽功能和靜態型別的方式操作集合。
filter(having(on(Book.class).getAuthor(), equalTo("Lewis Carrol")), books);
哇,這一切都在一條線上。如果有一個更復雜的過濾條款,這將變得有點麻煩。
Java 8 lambdas
最新版本帶來了一些新功能,可以提高程式碼的可讀性,並幫助語言在未來保持競爭力。讓我們看看書籍過濾示例,看看行為引數化如何與語言中內建的lambdas一起使用。
首先,我們需要重寫 filterBooks
要使用的方法, java.util.function.Predicate
這是Java 8中的新介面。
public static List<Book> filterBooks(List<Book> books, Predicate<Book> p) { List<Book> result = new ArrayList<>(); for (Book book : books) { if (p.test(book)) { result.add(book); } } return result; }
在呼叫時 filterBooks
,我們可以傳遞一個lambda表示式,告訴它如何進行過濾。
filterBooks(books, book -> "Lewis Carrol".equals(book.getAuthor()));
雖然我們使用了一個lambda表示式並使 filterBooks
方法的行為可引數化,但仍然有這個樣板程式碼迭代一系列書籍。以前我提到Java 8包含了函式式語言中常用的過濾器習語。Streams是一種新的API,有助於表達複雜的資料處理查詢。其中,它包括過濾方法。
books.stream().filter(b -> "Lewis Carrol".equals(b.getAuthor())).collect(toList());
可以看出,書籍列表沒有傳遞給方法,但我們可以 filter
通過首先從中建立流來呼叫方法。迭代由Streams API處理,由於lambda,行為是可引數化的。因此,Java 8不是編寫大量的樣板程式碼,而是處理常見的任務,只需一行程式碼即可解決手頭的問題。
還記得那些不斷變化
在這篇文章的開頭,我給出了一個改變需求的例子。現在可以使用lambda了,讓我們看看庫應用程式如何處理新的功能請求。應該可以找到超過200頁的書籍。
books.stream().filter(b -> b.getPageCount() > 200).collect(toList());
在不修改任何現有程式碼的情況下,使用新行為過濾書籍列表非常容易。
Retrolambda
如果您使用的是以前版本的Java,那麼您仍然可以通過使用Retrolambda來利用lambdas 。它允許您在Java 7,6 或5上執行帶有 lambda表示式 , 方法引用 和 try-with-resources語句的 Java 8程式碼。它通過轉換Java 8編譯的位元組碼來實現,以便它可以在較舊的Java執行時上執行。我不是其內部工作的專家,但從我所讀到的,它取代了lambdas與匿名的內部類。
儘管如此,Retrolambda並沒有向後傳輸Streams API。為此,您可以使用streamsupport。
摘要
使用函數語言程式設計中常用的習語可以極大地提高程式碼的可讀性。行為引數化很好,因為它使您能夠將迭代集合的程式碼與應用於集合的每個元素的行為分開。這樣可以更好地重用程式碼,並幫助您編寫更靈活的API。
微信讚賞
支付寶讚賞
