目录
从什么是好代码讲起
最近又在看《clean code》,回顾了一下里面提到的整洁代码的标准。
然后审视了一下现在的项目代码,里面还有很多if,for循环。比如:
// 查询用户列表
List<User> 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中重要的函数接口
其中Java中重要的函数接口,有以下几个:
接口
|
参数
|
返回类型
|
示例
|
Predicate<T>
|
T
|
boolean
| 条件判断:接受一个参数并判断给定条件是否成立,比如过滤出age>60的用户 |
Consumer<T>
| T |
void
| 立即执行:接受一个参数并立即执行给定代码,比如例子中的打印用户信息 |
Function<T,R>
| T |
R
| 立即执行:接受一个参数立即执行给定代码并转换成指定的类型返回,比如将Predicate过滤出age>60的用户设置为老年客户 |
Supplier<T>
|
None
| T | 获取结果:立即执行给定代码并获取指定结果,比如收集上面的过滤结果 |
UnaryOperator<T>
| T | T | 立即执行:对单个操作数的操作,产生与其操作数相同类型的结果,比如要将userList转成Map集合就可以用到 |
BinaryOperator<T>
|
(T, T)
| T | 二元操作:接受2个相同类型参数立即执行给定代码,并返回相同类型的结果,比如统计所有用户的年龄之和 |
Predict
Predicate
Predicate<T>接口是比较好理解的,目的就是执行一个判断条件,返回boolean值。比如过滤出age>60的用户,用传统代码如下,方式1:
// 添加2个用户:张3跟李四
User user1 = new User();
user1.setId(1L);
user1.setName("张3");
user1.setAge(61);
user1.setEmail("zhangsan@163.com");
userList.add(user1);
User user2 = new User();
user2.setId(2L);
user2.setName("李四");
user2.setAge(31);
user2.setEmail("lisi@163.com");
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<User> predicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
userList.stream().filter(predicate).forEach(user -> System.out.println(user));
- 先声明一个函数predicate,目的是过滤age>60的用户
- 传递给集合处理函数filter,执行函数过滤出用户列表
因为Predicate<T>是泛型,所以这里是需要声明为Predicate<User>,由lambda自动做类型推导。可以再举一个例子,比如过滤过所以姓"张"且age>60的用户,如果还是按照刚才的方式,代码如下:
// 年龄条件函数
Predicate<User> agePredicate = user -> user.getAge() > 60;
// 姓名条件函数
Predicate<User> namePredicate = user -> user.getName().startsWith("张");
// lambda表达式改写:打印age>60的用户
userList.stream().filter(agePredicate).filter(namePredicate).forEach(user -> System.out.println(user));
Consumer
Consumer<T>接口在开篇的时候已经提到,比如集合的forEach参数。从Predicate的分析来看,上面的例子也可以改写成如下:
// 年龄条件函数
Predicate<User> agePredicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
Consumer<User> printConsumer = user -> System.out.println(user);
userList.stream().filter(agePredicate).forEach(printConsumer);
- 先声明一个函数printConsumer,目的是打印age>60的用户
- 传递给集合处理函数forEach,执行函数打印出户列表
这里要重点注意一点的是,上面的例子并不是指Consumer只能用在集合类处理,而是在强调代码即函数,lambda函数甚至可以做为参数传递,比如可以将打印的提出一个方法,由上面的集合处理过程中来调用:
// 年龄条件函数
Predicate<User> agePredicate = user -> user.getAge() > 60;
// lambda表达式改写:打印age>60的用户
Consumer<User> printConsumer = user -> System.out.println(user);
userList.stream().filter(agePredicate).forEach(user -> print(printConsumer, user));
/**
* 打印用户信息
*
* @param printConsumer
* @param u
*/
private void print(Consumer<User> printConsumer, User u) {
printConsumer.accept(u);
}
Function
继续以上面的例子来看,如果要获取用户的名称列表,用lambda表达式可以这么写:
// 获取用户名称列表
List<String> userNames = userList.stream().map(user -> {
return user.getName();
}).collect(Collectors.toList());
按照上面的函数式写法,也可以写成如下形式:
// 用户姓名函数
Function<User, String> userNameFunction = user -> user.getName();
List<String> userNames = userList.stream().map(userNameFunction).collect(Collectors.toList());
注意Function<T,R>函数,接受一个参数立即执行给定代码并转换成指定的类型返回。这里要重点强调的是,里面的返回类型,这个在很多场景是非常有用的,比如上面的类型转换,从User对象转换成String类型。如果是有多个参数,比如2个参数的情况,java8也提供了BiFunction<T, U, R>的函数来支持!
看到这里,会不会有人疑问,Predicate<T>固定返回boolean值,所以是Function<T,R>函数的特例呢?毫无疑问,Function<T,R>是可以返回boolean的,比如:
Function<User, Boolean> isOldUserFunction = user -> user.getAge() > 60;
List<Boolean> userNames = userList.stream().map(isOldUserFunction).collect(Collectors.toList());
System.out.println(userNames);
其实从函数的作用来说,这是不对的,原因在于对于Function<T,R>函数来说,它作用的元素是类型会变化,个数是不变的,即T->R是1:1的
Predicate<T>是过滤函数,它作用的元素会代入条件去执行,得到是否满足的结果。所以它们在集合流中的表现才各不相同
Supplier
Supplier<T>这个接口比较特殊一点,它没有参数并且可以返回指定的泛型对象,比如下面的new一个User的实例:
Supplier<User> 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<String> strSupplier = SampleTest::getDefault;
System.out.println(strSupplier.get());
}
private static String getDefault() {
return "aaaa";
}
}
总之,这里始终强调的一点就是lambda代码即函数的思想
自定义lambda函数
上面表格中的六个函数,你会发现并没有一一讲完,因为它们表达的都是一个思想,代码即函数。除了这些之外,还有更多细分专业场景下的比函数,比如针对包装类优化的
- DoubleConsumer
- BooleanSupplier
- IntFunction
- LongPredicate
等函数。这些jdk提供的默认函数,其它就是把一般场景中做了抽象,用于判断的Predicate,用于获得值的Supplier,用过转换的Function。如果有特殊需求,完全可以自定义lambda函数,只要符合lambda的定义即可!比如,也来定义一个Supplier实现一个相同的功能:
@FunctionalInterface
public interface SupplierV2<P> {
P getV2();
}
public class SampleTest {
public static void main(String[] args) {
Supplier<String> strSupplier = SampleTest::getDefault;
System.out.println(strSupplier.get());
// 自定义lambda函数
SupplierV2<String> strSupplierV2 = SampleTest::getDefault;
System.out.println(strSupplierV2.getV2());
}
private static String getDefault() {
return "aaaa";
}
}
后面会给出一个,更加复杂的模板方法中使用lambda的例子!