Java之函数式编程
Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。
先了解Java新引入的函数式编程,首先了解在没有函数式编程之前,函数编程的演变史。
实战案例:函数编程演变史
根据实际场景演示,为了应对不断变化的业务需求,代码的多个改进版本。
购物车案例
准备前置需求,购物车相关的实体类、枚举类、服务类等
下单商品实体类
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.function.shopping.cart;
public class Sku {
private Integer skuId;
private String skuName;
private Double skuPrice;
private Integer totalNum;
private Double totalPrice;
private Enum skuCategory;
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... }
|
商品类型枚举
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.function.shopping.enums;
public enum SkuCategoryEnum {
CLOTHING(10, "服装类"),
ELECTRONICS(30, "数码类"),
SPORTS(30, "运动类"),
BOOKS(40, "图书类");
private final Integer code;
private final String name;
SkuCategoryEnum(Integer code, String name) { this.code = code; this.name = name; }
public Integer getCode() { return code; }
public String getName() { return name; } }
|
购物车服务类
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 36 37 38 39 40
| package com.function.shopping.service;
import com.function.shopping.cart.Sku; import com.function.shopping.enums.SkuCategoryEnum;
import java.util.ArrayList; import java.util.List;
public class CartService {
private static List<Sku> cartSkuList = new ArrayList<Sku>() { { add(new Sku(654032, "无人机", 4999.00, 1, 4999.00, SkuCategoryEnum.ELECTRONICS)); add(new Sku(654033, "VR一体机", 2299.00, 1, 2299.00, SkuCategoryEnum.ELECTRONICS)); add(new Sku(654221, "跑步机", 3999.00, 1, 3999.00, SkuCategoryEnum.SPORTS)); add(new Sku(654121, "Java编程思想", 79.80, 1, 79.80, SkuCategoryEnum.BOOKS)); add(new Sku(654122, "Java核心技术", 149.80, 1, 149.80, SkuCategoryEnum.BOOKS)); add(new Sku(654021, "睡衣套装", 299.00, 1, 299.00, SkuCategoryEnum.CLOTHING)); add(new Sku(654022, "牛仔裤", 1999.00, 1, 1999.00, SkuCategoryEnum.CLOTHING)); } };
public static List<Sku> getCartSKuList() { return cartSkuList; } }
|
需求实现
version1.0.0
需求:找出购物车中所有电子产品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public static List<Sku> filterElectronicsSkus(List<Sku> cartSkuList) { List<Sku> result = new ArrayList<>(); for (Sku sku : cartSkuList) { if (SkuCategoryEnum.ELECTRONICS.equals(sku.getSkuCategory())) { result.add(sku); } } return result; }
|
测试代码
1 2 3 4 5 6 7 8
| @Test void filterElectronicsSkus() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterElectronicsSkus(skus); System.out.println(JSONUtil.parse(result)); }
|
version2.0.0
需求:根据传入商品类型参数,找出购物车中同种商品类型的商品列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public static List<Sku> filterSkusByCategory(List<Sku> cartSkuList, SkuCategoryEnum skuCategoryEnum) { List<Sku> result = new ArrayList<>(); for (Sku sku : cartSkuList) { if (skuCategoryEnum.equals(sku.getSkuCategory())) { result.add(sku); } } return result; }
|
测试代码
1 2 3 4 5 6 7 8
| @Test void filterSkusByCategory() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterSkusByCategory(skus, SkuCategoryEnum.BOOKS); System.out.println(JSONUtil.parse(result)); }
|
version3.0.0
支持通过商品类型或总价过滤商品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public static List<Sku> filterSkusByPrice(List<Sku> cartSkuList, SkuCategoryEnum skuCategoryEnum, Double totalPrice, Boolean categoryOrPrice) { List<Sku> result = new ArrayList<>(); for (Sku sku : cartSkuList) { if ((categoryOrPrice && skuCategoryEnum.equals(sku.getSkuCategory())) || (!categoryOrPrice && sku.getTotalPrice() > totalPrice)) { result.add(sku); } } return result; }
|
测试代码
1 2 3 4 5 6 7 8
| @Test void filterSkusByPrice() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterSkusByPrice(skus, null, 2000.00, false); System.out.println(JSONUtil.parse(result)); }
|
小结
已经完成了三个需求的比较,发现根据条件过滤,只要条件(单条件或多条件)不同就会写相应的方法,因此会在代码中硬编码大量的判断条件,带来了代码不易阅读及难以维护,下面的版本展示优化硬编码带来的问题
version4.0.0
根据不同的Sku判断标准,对Sku列表进行过滤
传入一个判断标准,判断标准会动态的判断条件是否满足条件,从而是否达到条件进行返回,无形的将判断条件进行参数化,通过参数传入一个接口,接口的实现类定义了条件判断的具体实现,行为参数化传入到方法中,这其实是设计模式中的策略模式将数据的循环与处理分开了
1.定义Sku的选择谓词接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.function.shopping.service;
import com.function.shopping.cart.Sku;
public interface SkuPredicate {
boolean test(Sku sku); }
|
2.实现接口条件判断的具体实现
条件一:对Sku的总价是否超出2000作为的判断标准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.function.shopping.predicate;
import com.function.shopping.cart.Sku; import com.function.shopping.service.SkuPredicate;
public class SkuTotalPricePredicate implements SkuPredicate { @Override public boolean test(Sku sku) { return sku.getTotalPrice() > 2000; } }
|
条件二:对Sku的商品类型为图书类的判断标准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.function.shopping.predicate;
import com.function.shopping.cart.Sku; import com.function.shopping.enums.SkuCategoryEnum; import com.function.shopping.service.SkuPredicate;
public class SkuBooksCategoryPredicate implements SkuPredicate { @Override public boolean test(Sku sku) { return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()); } }
|
3.具体使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuPredicate skuPredicate) { List<Sku> result = new ArrayList<>(); for (Sku sku : cartSkuList) { if (skuPredicate.test(sku)) { result.add(sku); } } return result; }
|
测试代码
1 2 3 4 5 6 7 8
| @Test void filterSkus() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterSkus(skus, new SkuTotalPricePredicate()); System.out.println(JSONUtil.parse(result)); }
|
version5.0.0
使用策略模式将数据的循环与处理区分开,但是有带来一个问题,编写的判断条件有且只是用一次,若条件过多,我们会编写很多的判断条件类实现Predicate接口,而每次只是用一次,因此可以引用内部匿名类进行优化
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test void filterSkusAnonymous() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterSkus( skus, new SkuPredicate() { @Override public boolean test(Sku sku) { return sku.getSkuPrice() > 1000; } } ); System.out.println(JSONUtil.parse(result)); }
|
version6.0.0
在第五版的迭代后,使用了匿名类进行代码优化,好处是不用对接口进行实现,不用产生多余的类,只是用一次也没必要些那么多的类,但是在代码中的匿名类在写起来还是有些多,本次版本优化使用lambda进行优化
测试代码
1 2 3 4 5 6 7 8 9
| @Test void filterSkusLambda() { List<Sku> skus = CartService.getCartSKuList(); List<Sku> result = CartService.filterSkus( skus, (Sku sku) -> sku.getSkuPrice() > 1000); System.out.println(JSONUtil.parse(result)); }
|
这段代码与version6.0.0执行效果完全一样,但代码编写上出现了很大的不同,使用Lambda表达式替代匿名类,将行为参数化传递到代码中,由代码动态调用。
函数编程演化历程
- 将业务逻辑直接写死在代码里
- 将单一维度的条件作为参数传入方法中。方法内根据参数进行业务逻辑实现。
- 将多个纬度的条件作为参数传入方法中。业务实现需要根据不同的参数处理不同逻辑。
- 将业务逻辑封装为一个实体类,方法接收实体类为参数,方法内部调用实体类的处理逻辑。
- 调用方法时不在创建实体类,而是使用匿名函数的形式替代。
- 使用Lambda表达式替代匿名函数的形式,作为方法的参数。真正实现判断逻辑参数化传递。