目录
从什么是好代码讲起
重申函数式接口定义
重要的函数接口
Java中重要的函数接口
Predicate
Consumer
Function
Supplier
自定义函数
最近又在看《clean code》,回顾了一下里面提到的整洁代码的标准。
然后审视了一下现在的项目代码,里面还有很多if,for循环。比如:
// 查询用户列表
List userList = userService.list();
// 打印用户信息
for (int i = 0; i < userList.size(); i++) {
System.out.println(userList.get(i));
}
并不是这种写法不好(用《clean code》的话说就是,它不并是一个坏味道),非常多优秀的框架,类库也是这样写的。但现在项目用是jdk1.8啊,已经出来多久了!隔壁有的已经到17了,要求使用1.8的新特性不过分吧?
不仅是现在(也不仅是现在的公司),再把时间也往前推到17年(往前推服务过的2个公司),都存在一样的现象:jdk版本是1.8,而写代码的风格却还停留在1.4!而其中的原因也很简单甚至不值得一提:并不是lambda表达式不好,或者抗拒这种使用方法,就是不熟悉而已。
没有看错,就是有的程序员不熟悉而已,当他愿意花时间去熟悉lambda表达式,就很快应用并爱不释手。所以,我认为整洁代码还有一个评判因素就是,使用jdk新的代码风格编写代码!
3年前在JAVA8-lambda表达式1:什么是lambda表达式,一文里面认识了什么是lambda,这里再简单回顾一下:只有一个抽象方法的接口,就是函数式接口!
这里一再强调lambda表达式的定义,多数时候可以直接使用内置的函数来解决问题,比如JAVA8-lambda表达式2:常用的集合类api,甚至自定义函数JAVA8-lambda表达式6:重构和定制收集器
上面例子里面的代码,如果要改写成lambda表达式,可以如下:
userList.stream().forEach(user -> System.out.println(user));
而forEach方法里面的参数其实就是一个函数:Consumer函数式接口:
Consumer是一个比较重要的函数,表示接受一个参数用于执行代码且没有返回值。
其中Java中重要的函数接口,有以下几个:
接口
|
参数
|
返回类型
|
示例
|
Predicate |
T
|
boolean
|
条件判断:接受一个参数并判断给定条件是否成立,比如过滤出age>60的用户 |
Consumer |
T |
void
|
立即执行:接受一个参数并立即执行给定代码,比如例子中的打印用户信息 |
Function |
T |
R
|
立即执行:接受一个参数立即执行给定代码并转换成指定的类型返回,比如将Predicate过滤出age>60的用户设置为老年客户 |
Supplier |
None
|
T | 获取结果:立即执行给定代码并获取指定结果,比如收集上面的过滤结果 |
UnaryOperator |
T | T | 立即执行:对单个操作数的操作,产生与其操作数相同类型的结果,比如要将userList转成Map集合就可以用到 |
BinaryOperator |
(T, T)
|
T | 二元操作:接受2个相同类型参数立即执行给定代码,并返回相同类型的结果,比如统计所有用户的年龄之和 |
Predict
Predicate
// 添加2个用户:张3跟李四
User user1 = new User();
user1.setId(1L);
user1.setName("张3");
user1.setAge(61);
user1.setEmail("[email protected]");
userList.add(user1);
User user2 = new User();
user2.setId(2L);
user2.setName("李四");
user2.setAge(31);
user2.setEmail("[email protected]");
userList.add(user2);
// 打印age>60的用户
for (int i = 0; i < userList.size(); i++) {
User user = userList.get(i);
if (user.getAge() > 60) {
System.out.println(userList.get(i));
}
}
如果要用lambda函数来改写,可能就是下面这种,方式2:
// lambda表达式改写:打印age>60的用户
userList.stream().filter(user -> user.getAge() > 60).forEach(user -> System.out.println(user));
方式1与方式2其实是等价的,效果相同,但是方式2明显代码更简洁,意图也理清晰!重点在于filter这个高阶函数上面,它要求参数是一个Predicate函数。据此,上面的方式2其实可以写成如下此种方式,方便理解:
// 条件函数
Predicate predicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
userList.stream().filter(predicate).forEach(user -> System.out.println(user));
因为Predicate
// 年龄条件函数
Predicate agePredicate = user -> user.getAge() > 60;
// 姓名条件函数
Predicate namePredicate = user -> user.getName().startsWith("张");
// lambda表达式改写:打印age>60的用户
userList.stream().filter(agePredicate).filter(namePredicate).forEach(user -> System.out.println(user));
Consumer
// 年龄条件函数
Predicate agePredicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
Consumer printConsumer = user -> System.out.println(user);
userList.stream().filter(agePredicate).forEach(printConsumer);
这里要重点注意一点的是,上面的例子并不是指Consumer只能用在集合类处理,而是在强调代码即函数,lambda函数甚至可以做为参数传递,比如可以将打印的提出一个方法,由上面的集合处理过程中来调用:
// 年龄条件函数
Predicate agePredicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
Consumer printConsumer = user -> System.out.println(user);
userList.stream().filter(agePredicate).forEach(user -> print(printConsumer, user));
/**
* 打印用户信息
*
* @param printConsumer
* @param u
*/
private void print(Consumer printConsumer, User u) {
printConsumer.accept(u);
}
继续以上面的例子来看,如果要获取用户的名称列表,用lambda表达式可以这么写:
// 获取用户名称列表
List userNames = userList.stream().map(user -> {
return user.getName();
}).collect(Collectors.toList());
按照上面的函数式写法,也可以写成如下形式:
// 用户姓名函数
Function userNameFunction = user -> user.getName();
List userNames = userList.stream().map(userNameFunction).collect(Collectors.toList());
注意Function
看到这里,会不会有人疑问,Predicate
Function isOldUserFunction = user -> user.getAge() > 60;
List userNames = userList.stream().map(isOldUserFunction).collect(Collectors.toList());
System.out.println(userNames);
其实从函数的作用来说,这是不对的,原因在于对于Function
Predicate
Supplier
Supplier nameSupplier = User::new;
User user = nameSupplier.get();
所以它通常用在类似工厂的方法调用中,获取一个特定的对象。比如以前提到Optional用法里面的一个例子:
CardPromotionPublishResponse response = new CardPromotionPublishResponse();
CardPromotionPrize backUp = new CardPromotionPrize();
CardPromotionPrize cardPromotionPrize = Optional.ofNullable(response.getCardPromotionPrize()).orElseGet(CardPromotionPrize::new);
cardPromotionPrize = Optional.ofNullable(response.getCardPromotionPrize()).orElse(backUp);
如果查询的优惠为空,则默认生成一个CardPromotionPrize::new,这个也是Supplier函数!
这里可以看下Optional的源码,这个方法orElseGet的参数就是一个Supplier函数:
/**
* Return the value if present, otherwise invoke {@code other} and return
* the result of that invocation.
*
* @param other a {@code Supplier} whose result is returned if no value
* is present
* @return the value if present otherwise the result of {@code other.get()}
* @throws NullPointerException if value is not present and {@code other} is
* null
*/
public T orElseGet(Supplier extends T> other) {
return value != null ? value : other.get();
}
要强调一点的是,Supplier可以返回任务值,不一定是new一个对象,甚至可以是一个固定的字符串,比如:
public class SampleTest {
public static void main(String[] args) {
Supplier strSupplier = SampleTest::getDefault;
System.out.println(strSupplier.get());
}
private static String getDefault() {
return "aaaa";
}
}
总之,这里始终强调的一点就是lambda代码即函数的思想
上面表格中的六个函数,你会发现并没有一一讲完,因为它们表达的都是一个思想,代码即函数。除了这些之外,还有更多细分专业场景下的比函数,比如针对包装类优化的
等函数。这些jdk提供的默认函数,其它就是把一般场景中做了抽象,用于判断的Predicate,用于获得值的Supplier,用过转换的Function。如果有特殊需求,完全可以自定义lambda函数,只要符合lambda的定义即可!比如,也来定义一个Supplier实现一个相同的功能:
@FunctionalInterface
public interface SupplierV2 {
P getV2();
}
public class SampleTest {
public static void main(String[] args) {
Supplier strSupplier = SampleTest::getDefault;
System.out.println(strSupplier.get());
// 自定义lambda函数
SupplierV2 strSupplierV2 = SampleTest::getDefault;
System.out.println(strSupplierV2.getV2());
}
private static String getDefault() {
return "aaaa";
}
}
后面会给出一个,更加复杂的模板方法中使用lambda的例子!