为什么需要函数式编程?
从一个例子说起,我们要过滤用户中id为偶数的用户,或是过滤姓“张“的用户。我们定义filter函数,作为公共过滤函数(复用代码)。该函数接受一个User和Predicate<T>作为参数。其中Predicate<T>是一个@FunctionalInterface接口,Predicate<T>中有个test方法,接受一个T类型对象,并返回一个布尔值。在 Java 8 以前,我们只能使用如下的匿名内部类进行代码的编写,可以看出虽然代码已经进行了一定程度复用,但还是挺麻烦和啰嗦的。
public class User {
private final Integer id;
private final String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
// 过滤ID为偶数的用户
public static List<User> filterUsersWithEvenId(List<User> users) {
return filter(users, new Predicate<User>() {
@Override
public boolean test(User user) {
return user.id % 2 == 0;
}
});
}
// 过滤姓张的用户
public static List<User> filterZhangUsers(List<User> users) {
return filter(users, new Predicate<User>() {
@Override
public boolean test(User user) {
return user.name.startsWith("张");
}
});
}
public static List<User> filter(List<User> users, Predicate<User> predicate) {
List<User> results = new ArrayList<>();
for (User user : users) {
if (predicate.test(user)) {
results.add(user);
}
}
return results;
}
}
所谓函数就是完成x到y的映射x->y。同样的,我们有一个用户映射到一个布尔值User->boolean这样的映射关系,任何满足这个函数定义的东西Java都可以将它转化为一个函数接口@FunctionalInterface(在这个上下文中我们指的是Predicate<User>)。于是在 Java 8 以后,我们有了几种更简单的写法。
对于Predicate<User>,我们需要满足这样的映射关系:User->boolean
lamda表达式: User -> boolean
静态方法引用:static boolean xxx(User user) { }
实例方法引用:boolean xxx() { }
lamba表达式:
public static List<User> filterZhangUsers(List<User> users) {
return filter(users, user -> user.name.startsWith("张"));
}
静态方法引用(相较于lamda表达式,方法引用有名字,能更好的描述自己在干什么。所以在lamda表达式超过两行时,一般建议使用方法引用):
static boolean userWithEvenId(User user) {
return user.id % 2 == 0;
}
public static List<User> filterUsersWithEvenId(List<User> users) {
return filter(users, User::userWithEvenId);
}
实例方法引用(这是因为实例方法的参数中有一个隐含的User this):
public class User {
private final Integer id;
private final String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
//……
public boolean userWithEvenId() {
return id % 2 == 0;
}
// 过滤ID为偶数的用户
public static List<User> filterUsersWithEvenId(List<User> users) {
return filter(users, User::userWithEvenId);
}
//……
}
- 函数式编程给我们带了了什么好处?
- 减少工作量,大大简化代码
- 提升效率
- 减少bug
Java 函数接口
记住一句话,任何只含有一个抽象方法的接口都可以被自动转化成函数接口@FunctionalInterface。java 8 以后接口可以包含defalt方法。那什么是抽象方法呢?答案是没有方法体的方法。甚至我们可以自己定义Predicate,替换掉上文中的Predicate,也可以正常工作。
interface 阿猫阿狗 {
boolean 吃骨头(User user)
}
在java.util.function包中,有如下一些比较常用的函数接口(其中每一个都还有平行的三套,这里就不列出了):
-
Consumer<T>将T类型的对象映射为虚空T->void,比如List中的forEach方法。 -
Function<T,R>将T类型对象映射为R类型对象T->R -
Supplier<T>可以看作是Consumer<T>的逆操作虚空->T
Comparator
Collections.sort(Collection, Comparator)的第二参数就收一个Comparator函数接口。现在有这么一个需求:将用户按年龄从大大小,然后按他们的钱按从小到大排序。如果用匿名内部类,我们有这样的写法:
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User(1, new BigDecimal(5000));
User user2 = new User(1, new BigDecimal(2000));
User user3 = new User(5, new BigDecimal(1));
users.add(user1);
users.add(user2);
users.add(user3);
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
if (o1.age > o2.age) {
return -1;
} else if (o1.age < o2.age) {
return 1;
}
if (o1.money.compareTo(o2.money) > 0) {
return 1;
} else {
return -1;
}
}
});
}
}
或是使用lamda表达式:
Collections.sort(users, (o1, o2) -> {
if (o1.age > o2.age) {
return -1;
} else if (o1.age < o2.age) {
return 1;
}
if (o1.money.compareTo(o2.money) > 0) {
return 1;
} else {
return -1;
}
});
Comparator.comparing方法接受一个Function<T,R>函数接口。返回一个Comparator。由此可以看出函数式是多么简洁且优雅。
Collections.sort(users, Comparator.comparing(User::getAge).reversed().thenComparing(User::getMoney));












网友评论