函数式编程 优点: 减少工作量,减少 Bug,提高效率
减少工作量:做同样一件事,普通的方法实现,可能需要几十行代码,而函数式编程只需要几行代码
减少 Bug:代码写的越少,Bug 自然也变的越少
提高效率:别人还在吭哧坑次的写几十上百行代码时,你已经写完,顺便刷了个知乎
函数式编程深入浅出 你可能对我的话不信,那咱们就来看看实际的例子
假如给定一个用户列表信息,现在要你分别获取id 为偶数的用户
、姓周的用户
、姓王的用户
,你会如何实现这个需求。
大多数人会写出如下代码
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 32 public static List<User> filterUsersWithEvenId (List<User> users) { List<User> result = new ArrayList <>(); for (User user : users) { if (user.id % 2 == 0 ) { result.add(user); } } return result; } public static List<User> filterZhouUsers (List<User> users) { List<User> result = new ArrayList <>(); for (User user : users) { if (user.name.startsWith("周" )) { result.add(user); } } return result; } public static List<User> filterWangUsers (List<User> users) { List<User> result = new ArrayList <>(); for (User user : users) { if (user.name.startsWith("王" )) { result.add(user); } } return result; }
代码达到了我们需要的效果,但是你仔细观察,会发现重复的代码太多了,不同的地方仅仅是 if 中的条件判断,这个时候就需要思考一下如何简化这些代码。稍微细心的人就会发现,过滤姓张的用户,和过滤姓王的用户可以抽取成一个方法,只需要把姓氏当做参数传给函数就行了,如下
1 2 3 4 5 6 7 8 9 10 public static List<User> filterUsersWithLastName (List<User> users,String lastName) { List<User> result = new ArrayList <>(); for (User user : users) { if (user.name.startsWith(lastName)) { result.add(user); } } return result; }
这样的确简化了部分代码,但是仅此而已吗?难道过滤id为偶数的用户
方法,就不能跟其它方法合并了吗?
这个时候你可能会想,要是可以传条件就好了,这样方法就能根据条件,过滤我们需要的用户信息。的确,这种方法确实可行,那要怎么去实现呢?既然传递的都是自己「实现」的条件,那么很容易想到「接口」这个东西。所以我们可以根据要求写一个接口,并提供一个方法,让其他实现该接口的类,自己定义方法的实现,如下
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 32 33 34 35 public static List<User> filterUsersWithCondition (List<User> users,Condition condition) { List<User> result = new ArrayList <>(); for (User user : users) { if (condition.isSatisfied(user)) { result.add(user); } } return result; }interface Condition { boolean isSatisfied (User user) ; }static class ZhouUser implements Condition { @Override public boolean isSatisfied (User user) { return user.name.startsWith("周" ); } }static class WangUser implements Condition { @Override public boolean isSatisfied (User user) { return user.name.startsWith("王" ); } }static class UsersWithEvenId implements Condition { @Override public boolean isSatisfied (User user) { return user.id % 2 == 0 ; } }
我们使用了一个接口,以及三个类,每个类对应不同的实现。编写一个根据条件过滤的方法 filterUsersWithCondition
,我们传入接口类,然后进行条件判断。
但是实际上 Java 已经帮我们定义好了这些通用接口,不用我们再去定义,在 java.util.function
包中,例如:
1 2 3 @FunctionalInterface public interface Predicate <T> { boolean test (T t) ;
他与我们自己定义的 Condition 接口很像,方法都是接收一个参数,返回一个布尔值。因此,我们完全可以把自己定义的接口,替换成官方提供的。这样又减少了一点代码量,以 ZhouUser
类举例
1 2 3 4 5 6 static class ZhouUser implements Predicate { @Override public boolean test (User user) { return user.name.startsWith("周" ); } }
这个时候我们得 filterUsersWithCondition
方法,更改如下
1 2 3 4 5 6 7 8 9 10 public static List<User> filterUsersWithCondition (List<User> users,Predicate<User> predicate) { List<User> result = new ArrayList <>(); for (User user : users) { if (predicate.test(user)) { result.add(user); } } return result; }
实际上,做到这样已经很不错了,但是我们还能精简。每次调用 filterUsersWithCondition
方法的时候,都要传入一个实现类,并且每次有新需求,都要生成一个新类实现 Predicate 接口,这样代码量还是很多。
我们可以这样,直接使用匿名类完成操作,这样我们就不用频繁的实现接口了。如下
1 2 3 4 5 List<User> users = new ArrayList <>(); filterUsersWithCondition(users, new Predicate <User>() { @Override public boolean test (User user) { return user.name.startsWith("周" );} })
这个时候 IDEA 就会提示你了,按下快捷键 alt + enter 建,将匿名类转化为 Lambda 表达式。于是我们上面的代码就变成这样,代码瞬间简洁了很多。
1 2 List<User> users = new ArrayList <>(); filterUsersWithCondition(users, user -> user.name.startsWith("周" ));
这边简单的介绍下 Lambda 表达式:user -> user.name.startsWith("周")
它对应的就是过滤姓周用户的匿名内部类 test 方法。->
左边对应参数,他完整的参数是 (User user)
,由于 Lambda 可以通过上下文推断出参数类型,因此我们得式子仅仅指定了参数名user
。->
右边的就是要执行的语句了,遇到多行执行语句需要用 {}
括起来,用分号分隔语句。
Predicate 中的 test 方法,是将一个 User 对象到 boolean 的映射。实际上只要我们写的方法满足object->boolean
,都可以自动转化成一个函数接口。比如我们刚写的 Lambda 表达式 user -> user.name.startsWith("周")
,以及我们将要展示的方法引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Main { public static void main (String[] args) { List<User> users = new ArrayList <>(); filterUsersWithCondition(users, Main::zhouUser); } public static boolean zhouUser (User user) { return user.name.startsWith("周" ); } public static List<User> filterUsersWithCondition (List<User> users, Predicate<User> predicate) { List<User> result = new ArrayList <>(); for (User user : users) { if (predicate.test(user)) { result.add(user); } } return result; } }
我们自己编写了一个方法 zhouUser
,这个方法也是满足 Object -> boolean 的映射关系,因此他会被转化成相应的函数接口,这时使用方法引用调用这个方法,即可达到相同的目的。方法引用相比 Labmda 表达式的实现,更为清晰易懂,因为有方法名,我们很容易根据方法的名称来了解代码在干什么,对于日常维护开发更为友好。
Java 中的函数接口 先说一个结论:任何只包含一个抽象方法的接口,都可以转化成函数接口 ,例如我们刚开始使用的 Condition
接口。
在 Java 中不只有 Predicate,还有很多默认的实现。像,Consumer
是 Object->void 的映射;Supplier
是 void->Object 的映射;ToIntFunction
是 Object->int 的映射,等等,如图:
我们可以根据我们得需求使用以上接口,或者自己定义我们所需的接口。
使用 Comparator 实战 当我们需要对数据进行比较的时候,就需要 Comparator
还是上面说到的 User 对象,现在有了新需求,需要对 user 的 id 进行从小到大排序,然后按照年龄从大到小排列。我们可以使用 Collections.sort() 方法,对数据进行排序。可以发现需要传入的参数是一个 Comparator 接口,因此我们可以直接 New 一个 Comparator,并实现他的方法。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 List<User> users = Arrays.asList( new User (1 , 18 ), new User (2 , 5 ), new User (3 , 7 ), new User (4 , 20 )); Collections.sort(users, new Comparator <User>() { @Override public int compare (User o1, User o2) { if (o1.id < o2.id) { return 1 ; } else if (o1.id > o2.id) { return -1 ; } return 0 ; } });
Java 8 之后,JDK 为我们提供了更好的方法。在 Comparator 接口中有一个静态方法 comparing(),我们可以查看代码,发现他需要我们传入一个 Function 接口
1 2 3 4 5 6 7 public static <T, U extends Comparable <? super U>> Comparator<T> comparing ( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); }
因此我们传入的参数遵循 Function 接口对应的映射关系就可以了,打开发现 Function 是 T->R 的映射关系,即我们传入的参数转化成另一种数据类型。因此我们可以写成这样,User 类转化成整形,符合映射关系
1 Collections.sort(users,Comparator.comparing(user -> user.id);
我们可以使用方法引用替换 Lambda 表达式,getId 方法表面上没有参数,实际上是有一个隐藏的 this,因次该方法引用也符合映射关系
1 Collections.sort(users,Comparator.comparing(User::getId);
比较了 id 之后我们还需要比较年龄的倒序排列,使用 thenComparing 继续比较,再使用 reversed() 方法将结果倒序排列。
1 Collections.sort(users, Comparator.comparing(User::getId).thenComparing(User::getAge).reversed());
这样就完成了我们得需求,一行代码完成了我们得工作,还减少了 bug,头发又可以少掉几根了,真是幸福。