Java8函数式编程之Lambda入门及案例解析
这是一个新的系列,主要讲Java8的lambda编程以及Stream流式编程相关的用法和案例。
这个系列脱胎于一个内部的分享,由于篇幅较长,内容较多,因此拆分成多篇文章进行发布,方便自己后续参考,也希望能够帮到读者朋友。
在这里重点感谢慕课网的 《告别996,开启Java高效编程之门》 课程。
本文是Java8函数式编程系列的第一篇,我们一起学习一下Java8函数式编程的基本概念及操作。
1.概述Lambda表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
1.1 lambda表达式语法
(parameters) -> expression
或
(parameters) -> {statement};
我们可以将lambda表达式理解为一种代替原先匿名函数的新的编程方式
通过使用lambda表达式替换匿名函数的形式,将lambda表达式作为方法参数,实现判断逻辑参数化传递的目的。
1.2 lambda表达式形式
无参数
() -> System.out.println("code");
只有一个参数
name -> System.out.println("hello:" + name + "!");
没有参数,且逻辑复杂,需要通过大括号将多个语句括起来
() -> {
System.out.println("hello");
System.out.println("lambda");
}
包含两个参数的方法
BinaryOperator<Long> functionAdd = (x, y) -> x + y;
Long result = functionAdd.apply(1L, 2L);
包含两个参数且对参数显式声明
BinaryOperator<Long> functionAdd = (Long x, Long y) -> x + y;
Long result = functionAdd.apply(1L, 2L);
1.3 函数式接口
定义:
一个接口有且只有一个抽象方法;
函数式接口的实例可以通过 lambda 表达式、方法引用或者构造方法引用来创建;
注意:
如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口
@FunctionInterface是Java8函数式接口注解,属于声明式注解,帮助编译器校验被标注的接口是否符合函数式接口定义
1.4 方法引用
我们通过Lambda表达式来实现匿名方法。
有些情况下,使用Lambda表达式仅仅是调用一些已经存在的方法;除了调用动作外,没有其他任何多余的动作,在这种情况下我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求。它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。
方法引用可以理解为Lambda表达式的另外一种表现形式。
方法引用是调用特定方法的lambda表达式的一种快捷写法,可以让你重复使用现有的方法定义,并像lambda表达式一样传递他们。
注意:
使用方法引用时,只写方法名,不写括号
1.4.1 方法引用格式:
格式: 目标引用 双冒号分隔符 方法名
eg: String :: valueOf
1.4.2 方法引用分类:
1.指向静态方法的方法引用:当Lambda表达式的结构体是一个对象,并且调用其静态方法时,使用如下方式
表达式:
(args) -> ClassName::staticMethod(args);
格式: ClassName :: staticMethodName
eg: Integer :: valueOf
2.指向任意类型实例方法的方法引用:当直接调用对象的实例方法,则使用如下方式进行调用
表达式:
(args) -> args.instanceMethod();
格式: ClassName::instanceMethod;
eg: String::indexOf
String::toString
3.指向现有对象的实例方法的方法引用:通过对象实例,方法引用实例方法
表达式:
(args) -> object.instanceMethod(args);
改写为
(args) -> object::instanceMethod;
eg:
StringBuilder sb = new StringBuilder();
Consumer<String> consumer = (String str) -> stringBuilder.append(str);
就可以改写为
Consumer<String> consumer = (String str) -> stringBuilder::append;
2.从一个案例入手
我们先看一个例子,宏观感受一下Java8 Lambda编程带来的便利(后续讲解Stream同样使用该案例)
2.1 案例:直观体验Java8Stream操作
Sku实体类: 标识一个电商下单商品信息对象
public class Sku {
// 商品编号
private Integer skuId;
// 商品名称
private String skuName;
// 单价
private Double skuPrice;
// 购买个数
private Integer totalNum;
// 总价
private Double totalPrice;
// 商品类型
private Enum skuCategory;
public Sku() {
}
public Sku(Integer skuId, String skuName, Double skuPrice, Integer totalNum, Double totalPrice, Enum skuCategory) {
this.skuId = skuId;
this.skuName = skuName;
this.skuPrice = skuPrice;
this.totalNum = totalNum;
this.totalPrice = totalPrice;
this.skuCategory = skuCategory;
}
...省略getter setter...
}
SkuCategoryEnum枚举类: 商品类型枚举
public enum SkuCategoryEnum {
CLOTHING(10, "服务类"),
ELECTRONICS(20, "数码产品类"),
SPORTS(30, "运动类"),
BOOKS(40, "图书类")
;
// 商品类型编号
private Integer code;
// 商品名称
private String name;
SkuCategoryEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
...省略getter...
}
CartService类: 初始化一批数据,模拟购物车
public class CartService {
// 初始化购物车
private static List<Sku> cartSkuList = new ArrayList<>();
static {
cartSkuList.add(new Sku(2, "无人机", 1000.00, 10, 1000.00, SkuCategoryEnum.ELECTRONICS));
cartSkuList.add(new Sku(1, "VR一体机", 2100.00, 10, 2100.00, SkuCategoryEnum.ELECTRONICS));
cartSkuList.add(new Sku(4, "牛仔裤", 60.00, 10, 60.00, SkuCategoryEnum.CLOTHING));
cartSkuList.add(new Sku(13, "衬衫", 120.00, 10, 120.00, SkuCategoryEnum.CLOTHING));
cartSkuList.add(new Sku(121, "Java编程思想", 100.00, 10, 100.00, SkuCategoryEnum.BOOKS));
cartSkuList.add(new Sku(3, "程序化广告", 80.00, 10, 80.00, SkuCategoryEnum.BOOKS));
}
public static List<Sku> getCartSkuList() {
return cartSkuList;
}
}
我们直接看这个案例
private static List<Sku> cartSkuList = CartService.getCartSkuList();
@Test
public void show() {
List<Integer> collect = cartSkuList.stream()
// 方法引用
.map(Sku::getSkuId)
.distinct()
.sorted()
.collect(Collectors.toList());
collect.stream().forEach(skuId -> {
System.out.println(skuId.toString());
});
}
简单解释下这段代码的意图:
首先获取购物车中商品列表,将该列表转换为流;收集商品列表中的所有商品编号(skuId),对商品编号进行去重,并进行自然排序(升序排列),最后收集为一个商品编号集合,并对该集合进行遍历打印。
我并没有加注释,但是相信聪明的你也一定能读懂上面这段代码,这正是Stream编程的特点:方法名见名知意,流式编程方式符合人类思考逻辑
运行该用例,打印如下:
1
2
3
4
13
121
打印结果符合我们的预期意图。
想象一下,如果不使用lambda+Stream方式,而是使用java7及之前的传统集合操作,实现上述操作我们的代码量有多少?保守估计至少是上述代码段的1.5倍。
这个案例可能还不具备说服力,接下来的文章中,我将通过一个对比案例来比较lambda编程与传统方式对集合操作的效率提升。
我们下文继续。
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。