若是我要新建一個java的項目,那麼有兩個類庫是必備的,一個是junit,另外一個是Guava。選擇junit,由於我喜歡TDD,喜歡自動化測試。而是用Guava,是由於我喜歡簡潔的API。Guava提供了不少的實用工具函數來彌補java標準庫的不足,另外Guava還引入了函數式編程的概念,在必定程度上緩解了java在JDK1.8以前沒有lambda的缺陷,使使用java書寫簡潔易讀的函數式風格的代碼成爲可能。java
下面就簡單的介紹下Guava中的一些體現了函數式編程的API。git
Filter
咱們先建立一個簡單的Person類。程序員
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Person { public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } } |
若是要產生一個Person類的List,一般的寫法多是這樣子。github
1 2 3 4 5 |
List<Person> people = new ArrayList<Person>(); people.add(new Person("bowen",27)); people.add(new Person("bob", 20)); people.add(new Person("Katy", 18)); people.add(new Person("Logon", 24)); |
而Guava提供了一個newArrayList的方法,其自帶類型推演,並能夠方便的生成一個List,而且經過參數傳遞初始化值。編程
1 2 3 4 |
List<Person> people = newArrayList(new Person("bowen", 27), new Person("bob", 20), new Person("Katy", 18), new Person("Logon", 24)); |
固然,這不算函數式編程的範疇,這是Guava給咱們提供的一個實用的函數。數組
若是咱們選取其中年齡大於20的人,一般的寫法多是這樣子。app
1 2 3 4 5 6 |
List<Person> oldPeople = new ArrayList<Person>(); for (Person person : people) { if (person.getAge() >= 20) { oldPeople.add(person); } } |
這就是典型的filter模式。filter即從一個集合中根據一個條件篩選元素。其中person.getAge() >=20就是這個條件。Guava爲這種模式提供了一個filter的方法。因此咱們能夠這樣寫。ide
1 2 3 4 5 |
List<Person> oldPeople = newArrayList(filter(people, new Predicate<Person>() { public boolean apply(Person person) { return person.getAge() >= 20; } })); |
這裏的Predicate是Guava中的一個接口,咱們來看看它的定義。函數式編程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@GwtCompatible public interface Predicate<T> { /** * Returns the result of applying this predicate to {@code input}. This method is <i>generally * expected</i>, but not absolutely required, to have the following properties: * * <ul> * <li>Its execution does not cause any observable side effects. * <li>The computation is <i>consistent with equals</i>; that is, {@link Objects#equal * Objects.equal}{@code (a, b)} implies that {@code predicate.apply(a) == * predicate.apply(b))}. * </ul> * * @throws NullPointerException if {@code input} is null and this predicate does not accept null * arguments */ boolean apply(@Nullable T input); /** * Indicates whether another object is equal to this predicate. * * <p>Most implementations will have no reason to override the behavior of {@link Object#equals}. * However, an implementation may also choose to return {@code true} whenever {@code object} is a * {@link Predicate} that it considers <i>interchangeable</i> with this one. "Interchangeable" * <i>typically</i> means that {@code this.apply(t) == that.apply(t)} for all {@code t} of type * {@code T}). Note that a {@code false} result from this method does not imply that the * predicates are known <i>not</i> to be interchangeable. */ @Override boolean equals(@Nullable Object object); } |
裏面只有一個apply方法,接收一個泛型的實參,返回一個boolean值。因爲java世界中函數並非一等公民,因此咱們沒法直接傳遞一個條件函數,只能經過Predicate這個類包裝一下。函數
And Predicate
若是要再實現一個方法來查找People列表中全部名字中包含b字母的列表,咱們能夠用Guava簡單的實現。
1 2 3 4 5 |
List<Person> namedPeople = newArrayList(filter(people, new Predicate<Person>() { public boolean apply(Person person) { return person.getName().contains("b"); } })); |
一切是這麼的簡單。 那麼新需求來了,若是如今須要找年齡>=20而且名稱包含b的人,該如何實現那? 可能你會這樣寫。
1 2 3 4 5 |
List<Person> filteredPeople = newArrayList(filter(people, new Predicate<Person>() { public boolean apply(Person person) { return person.getName().contains("b") && person.getAge() >= 20; } })); |
這樣寫的話就有必定的代碼重複,由於以前咱們已經寫了兩個Predicate來分別實現這兩個條件判斷,能不能重用以前的Predicate那?答案是能。 咱們首先將以前生成年齡判斷和名稱判斷的兩個Predicate抽成方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private Predicate<Person> ageBiggerThan(final int age) { return new Predicate<Person>() { public boolean apply(Person person) { return person.getAge() >= age; } }; } private Predicate<Person> nameContains(final String str) { return new Predicate<Person>() { public boolean apply(Person person) { return person.getName().contains(str); } }; } |
而咱們的結果其實就是這兩個Predicate相與。Guava給咱們提供了and方法,用於對一組Predicate求與。
1
|
List<Person> filteredPeople = newArrayList(filter(people, and(ageBiggerThan(20), nameContains("b")))); |
因爲and接收一組Predicate,返回也是一個Predicate,因此能夠直接做爲filter的第二個參數。若是不熟悉函數式編程的人可能感受有點怪異,可是習慣了就會以爲它的強大與簡潔。 固然除了and,Guava還爲咱們提供了or,用於對一組Predicate求或。這裏就很少講了,你們能夠本身練習下。
Map(transform)
列表操做還有另外一個常見的模式,就是將數組中的全部元素映射爲另外一種元素的列表,這就是map pattern。舉個例子,求People列表中的全部人名。程序員十有八九都會這樣寫。
1 2 3 4 |
List<String> names = new ArrayList<String>(); for (Person person : people) { names.add(person.getName()); } |
Guava已經給咱們提供了這種Pattern的結果辦法,那就是使用transform方法。
1 2 3 4 5 |
List<String> names = newArrayList(transform(people, new Function<Person, String>() { public String apply( Person person) { return person.getName(); } })); |
Function是另一種用於封裝函數的接口對象。它的定義以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@GwtCompatible public interface Function<F, T> { /** * Returns the result of applying this function to {@code input}. This method is <i>generally * expected</i>, but not absolutely required, to have the following properties: * * <ul> * <li>Its execution does not cause any observable side effects. * <li>The computation is <i>consistent with equals</i>; that is, {@link Objects#equal * Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a), * function.apply(b))}. * </ul> * * @throws NullPointerException if {@code input} is null and this function does not accept null * arguments */ @Nullable T apply(@Nullable F input); /** * Indicates whether another object is equal to this function. * * <p>Most implementations will have no reason to override the behavior of {@link Object#equals}. * However, an implementation may also choose to return {@code true} whenever {@code object} is a * {@link Function} that it considers <i>interchangeable</i> with this one. "Interchangeable" * <i>typically</i> means that {@code Objects.equal(this.apply(f), that.apply(f))} is true for all * {@code f} of type {@code F}. Note that a {@code false} result from this method does not imply * that the functions are known <i>not</i> to be interchangeable. */ @Override boolean equals(@Nullable Object object); } |
它與Predicate很是類似,但不一樣的是它接收兩個泛型,apply方法接收一種泛型實參,返回值是另外一種泛型值。正是這個apply方法定義了數組間元素一對一的map規則。
reduce
除了filter與map模式外,列表操做還有一種reduce操做。好比求people列表中全部人年齡的和。Guava並未提供reduce方法。具體緣由咱們並不清楚。可是咱們能夠本身簡單的實現一個reduce pattern。 先定義一個Func的接口。
1 2 3 4 5 |
public interface Func<F,T> { T apply(F currentElement, T origin); } |
apply方法的第一個參數爲列表中的當前元素,第二個參數爲默認值,返回值類型爲默認值類型。 而後咱們定義個reduce的靜態方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Reduce { private Reduce() { } public static <F,T> T reduce(final Iterable<F> iterable, final Func<F, T> func, T origin) { for (Iterator iterator = iterable.iterator(); iterator.hasNext(); ) { origin = func.apply((F)(iterator.next()), origin); } return origin; } } |
reduce方法接收三個參數,第一個是須要進行reduce操做的列表,第二個是封裝reduce操做的Func,第三個參數是初始值。
咱們可使用這個reduce來實現求people列表中全部人的年齡之和。
1 2 3 4 5 6 |
Integer ages = Reduce.reduce(people, new Func<Person, Integer>() { public Integer apply(Person person, Integer origin) { return person.getAge() + origin; } }, 0); |
咱們也能夠輕鬆的寫一個方法來獲得年齡的最大值。
1 2 3 4 5 6 |
Integer maxAge = Reduce.reduce(people, new Func<Person, Integer>() { public Integer apply(Person person, Integer origin) { return person.getAge() > origin ? person.getAge() : origin; } }, 0); |
Fluent pattern
如今新需求來了,須要找出年齡>=20歲的人的全部名稱。該如何操做那?咱們可使用filter過濾出年齡>=20的人,而後使用transform獲得剩下的全部人的人名。
1 2 3 4 5 6 7 8 9 10 11 12 |
private Function<Person, String> getName() { return new Function<Person, String>() { public String apply( Person person) { return person.getName(); } }; } public void getPeopleNamesByAge() { List<String> names = newArrayList(transform(filter(people, ageBiggerThan(20)), getName())); } |
這樣括號套括號的着實很差看。能不能改進一下那?Guava爲咱們提供了fluent模式的API,咱們能夠這樣來寫。
1
|
List<String> names = from(people).filter(ageBiggerThan(20)).transform(getName()).toList(); |
Guava中還有不少好玩的東西,你們時間能夠多發掘發掘。這篇文章的源碼已經被我放置到github中,感興趣的能夠自行查看。