Java经典类库-Guava中的函数式编程讲解

若是我要新建一个java的项目,那么有两个类库是必备的,一个是junit,另外一个是Guava。选择junit,由于我喜欢TDD,喜欢自动化测试。而是用Guava,是由于我喜欢简洁的API。Guava提供了不少的实用工具函数来弥补java标准库的不足,另外Guava还引入了函数式编程的概念,在必定程度上缓解了java在JDK1.8以前没有lambda的缺陷,使使用java书写简洁易读的函数式风格的代码成为可能。java

下面就简单的介绍下Guava中的一些体现了函数式编程的API。git

Filter

咱们先建立一个简单的Person类。程序员

Person.java
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中的一个接口,咱们来看看它的定义。函数式编程

Predicate.java
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是另一种用于封装函数的接口对象。它的定义以下:

Function.java
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的接口。

Func.java
1
2
3
4
5
 public interface Func<F,T> {   T apply(F currentElement, T origin);   } 

apply方法的第一个参数为列表中的当前元素,第二个参数为默认值,返回值类型为默认值类型。 而后咱们定义个reduce的静态方法。

Reduce.java
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中,感兴趣的能够自行查看。

相关文章
相关标签/搜索