java函数式编程
一、概述
java是面向对象的,对象往往映射现实生活中的某个具体的东西,绝大多数保存在java内存的堆中;
java的函数式编程,则可以将对象指向一个函数,而非具体的东西;
函数式接口可以表现为lambda表达式,把lambda表达式认为是一种匿名内部类有助于理解(注,以下都是这么认为的,但不是一回事),但两者之间有区别,区别我也不是很清楚;
函数式接口和普通接口的区别是函数式接口只能有一个抽象方法,可使用@FunctionalInterface注解进行约束和提醒;
jdk提供了很多可以直接使用的函数式接口,比较常用的有以下4种:
java.util.function |
|
Predicate |
断言型:有参数,输出固定为boolean |
Function |
公式型:有入参,有输出 |
Supplier |
产出型:没有入参,有输出 |
Consumer |
消费型:有入参,没有输出 |
二、示例
提前约定的信息:
买家要买3个苹果,重量不能低于3两;
苹果是5元一斤;
老板会将挑选的苹果放到结算台1上面;
1,Predicate
断言型:有参数,输出固定为boolean
// 1,老板从货架上挑选了3个苹果放到结算台1 for (int i = 0; i < 3; i++) { seller.pickOneApple(needApple -> { // 挑选的标准:苹果的重量不低于3两 return needApple.getWeight() >= 3; }); }
public void pickOneApple(Predicate<Apple> predicate) { Apple pickApple = null; for (Apple apple : appleList) { // 挑选1个符合买家标准的苹果 if (predicate.test(apple)) { pickApple = apple; break; } } if (null != pickApple) { // 把挑选的苹果从货架上拿出来 appleList.remove(pickApple); // 放入到约定好的结算台1上面 cashOneAppleList.add(pickApple); return; } throw new RuntimeException("没有符合要求的苹果了!"); }
总结:
不同的应用场景,可以传入不同的标准,如下代码:
seller.pickOneApple(needApple -> { // 挑选的标准:苹果的重量不低于3两 //return needApple.getWeight() >= 3; // 挑选的标准:苹果的颜色为红色 //return "红色".equals(needApple.getColor()); // 挑选的标准:苹果的颜色为红色且重量不低于3两 return "红色".equals(needApple.getColor()) && needApple.getWeight() >= 3; });
如果不使用函数式编程,则对应每种标准,我们都需要编写一个方法;
2,Function
公式型:有入参,有输出
// 2,老板开始称重,计算价格 // 结算台1上苹果的总重(单位:两) int liang = seller.getCashOneAppleList().stream() .mapToInt(Apple::getWeight) .sum(); // 转换:两 -> 斤 double jin = seller.calculateJin(liang, inputLiang -> { // 转换公式:16两为一斤 double result = inputLiang / 16; return new BigDecimal(result).setScale(1, RoundingMode.HALF_UP).doubleValue(); }); System.out.println("老板说:16两为一斤,一共" + jin + "斤,你需要支付" + 5 * jin + "元"); // 2.1,买家说老板太实在了,自己那边都是10两一斤;于是乎,老板重新设置了电子秤的公式,并计算价格 jin = seller.calculateJin(liang, inputLiang -> { // 转换公式:10两为一斤 double result = inputLiang / 10; return new BigDecimal(result).setScale(1, RoundingMode.HALF_UP).doubleValue(); }); System.out.println("老板说:按你说的,10两为一斤,一共" + jin + "斤,你需要支付" + 5 * jin + "元");
/** * 两 -> 斤 * * @param liang 两数 * @param function 两 -> 斤的转换公式 * @return 两转换成斤后的数值 */ public double calculateJin(double liang, Function<Double, Double> function) { double jin = function.apply(liang); return jin; }
总结:
同理,不同的应用场景,传入不同的公式;
如果不使用函数式编程,则也只需要编写一个方法,传入10或16,但是如果不同的应用场景有不同的公式模板,则同样需要编写不同的方法;
3,Supplier
产出型:没有入参,有输出
// 3,买家付钱,省略 // 4,卖家把结算台2上的苹果打包给买家 buyer.setAppleList(seller.supplyApples(() -> { return seller.getCashTwoAppleList(); })); // 5,买家开始吃苹果 if (CollectionUtils.isEmpty(buyer.getAppleList())) { // 买家说,老板你给我的袋子里没有苹果 // 老板说,搞错了,应该把结算台1的苹果打包给你 buyer.setAppleList(seller.supplyApples(() -> { return seller.getCashOneAppleList(); })); }
/** * 打包苹果 * * @param supplier 打包的过程 * @return 打包好的苹果 */ public List<Apple> supplyApples(Supplier<List<Apple>> supplier) { return supplier.get(); }
总结:
不同的应用场景,产出不同的结果;
如果不使用函数式编程,可以将结算台抽象成对象,这样就有两个结算台对象,分别产出不同的结果;
4,Consumer
消费型:有入参,没有输出
for (int i = 0; i < buyer.getAppleList().size(); i++) { // 吃第2个苹果的时候 if (i % 2 == 1) { buyer.eatApple(buyer.getAppleList().get(i), apple -> { System.out.println("吃不下了!心想(这个苹果是我的,我想怎么着就怎么着,扔了这个苹果!)" + apple); }); } else { // 吃第1个和第3个苹果的时候 buyer.eatApple(buyer.getAppleList().get(i), apple -> { System.out.println("这个苹果不错,我吃!" + apple); }); } }
/** * 处理苹果 * * @param apple 苹果 * @param consumer 处理方式 */ public void eatApple(Apple apple, Consumer<Apple> consumer) { consumer.accept(apple); }
总结:
不同的应用场景,可以采取不同的处理方式;
如果不使用函数式编程,可以针对不同的场景编写对应的方法,如吃掉苹果、扔掉苹果、送人、雕花等等;
三、全部代码
import lombok.Data; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; /** * 卖家 * * @author xxx * @date 2022-06-29 14:35 */ @Data public class Seller { // 货架上的苹果 private List<Apple> appleList; // 结算台上1的苹果 private List<Apple> cashOneAppleList; // 结算台上2的苹果 private List<Apple> cashTwoAppleList; /** * 挑选一个苹果 * * @param predicate 挑选的标准 */ public void pickOneApple(Predicate<Apple> predicate) { Apple pickApple = null; for (Apple apple : appleList) { // 挑选1个符合买家标准的苹果 if (predicate.test(apple)) { pickApple = apple; break; } } if (null != pickApple) { // 把挑选的苹果从货架上拿出来 appleList.remove(pickApple); // 放入到约定好的结算台1上面 cashOneAppleList.add(pickApple); return; } throw new RuntimeException("没有符合要求的苹果了!"); } /** * 两 -> 斤 * * @param liang 两数 * @param function 两 -> 斤的转换公式 * @return 两转换成斤后的数值 */ public double calculateJin(double liang, Function<Double, Double> function) { double jin = function.apply(liang); return jin; } /** * 打包苹果 * * @param supplier 打包的过程 * @return 打包好的苹果 */ public List<Apple> supplyApples(Supplier<List<Apple>> supplier) { return supplier.get(); } }
import lombok.Data; import java.util.List; import java.util.function.Consumer; /** * 买家 * * @author xxx * @date 2022-06-29 14:36 */ @Data public class Buyer { // 拥有的苹果 private List<Apple> appleList; /** * 处理苹果 * * @param apple 苹果 * @param consumer 处理方式 */ public void eatApple(Apple apple, Consumer<Apple> consumer) { consumer.accept(apple); } }
import org.apache.commons.collections.CollectionUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; /** * 生活的一部分 * * @author xxx * @date 2022-06-29 14:38 */ public class Life { /** * 提前约定的信息: * 买家对老板说要3个苹果,重量不能低于3两 * 苹果是5元1斤 * 老板会将挑选的苹果放到结算台1上面 */ public static void main(String[] args) { Seller seller = initSeller(); Buyer buyer = initBuyer(); // 1,老板从货架上挑选了3个苹果放到结算台1 for (int i = 0; i < 3; i++) { seller.pickOneApple(needApple -> { // 挑选的标准:苹果的重量不低于3两 return needApple.getWeight() >= 3; }); } // 2,老板开始称重,计算价格 // 结算台1上苹果的总重(单位:两) int liang = seller.getCashOneAppleList().stream() .mapToInt(Apple::getWeight) .sum(); // 转换:两 -> 斤 double jin = seller.calculateJin(liang, inputLiang -> { // 转换公式:16两为一斤 double result = inputLiang / 16; return new BigDecimal(result).setScale(1, RoundingMode.HALF_UP).doubleValue(); }); System.out.println("老板说:16两为一斤,一共" + jin + "斤,你需要支付" + 5 * jin + "元"); // 2.1,买家说老板太实在了,自己那边都是10两一斤;于是乎,老板重新设置了电子秤的公式,并计算价格 jin = seller.calculateJin(liang, inputLiang -> { // 转换公式:10两为一斤 double result = inputLiang / 10; return new BigDecimal(result).setScale(1, RoundingMode.HALF_UP).doubleValue(); }); System.out.println("老板说:按你说的,10两为一斤,一共" + jin + "斤,你需要支付" + 5 * jin + "元"); // 3,买家付钱,省略 // 4,卖家把结算台2上的苹果打包给买家 buyer.setAppleList(seller.supplyApples(() -> { return seller.getCashTwoAppleList(); })); // 5,买家开始吃苹果 if (CollectionUtils.isEmpty(buyer.getAppleList())) { // 买家说,老板你给我的袋子里没有苹果 // 老板说,搞错了,应该把结算台1的苹果打包给你 buyer.setAppleList(seller.supplyApples(() -> { return seller.getCashOneAppleList(); })); } for (int i = 0; i < buyer.getAppleList().size(); i++) { // 吃第2个苹果的时候 if (i % 2 == 1) { buyer.eatApple(buyer.getAppleList().get(i), apple -> { System.out.println("吃不下了!心想(这个苹果是我的,我想怎么着就怎么着,扔了这个苹果!)" + apple); }); } else { // 吃第1个和第3个苹果的时候 buyer.eatApple(buyer.getAppleList().get(i), apple -> { System.out.println("这个苹果不错,我吃!" + apple); }); } } } /** * 初始化卖家 * * @return 卖家 */ private static Seller initSeller() { Seller seller = new Seller(); // 初始化货架上的苹果 List<Apple> appleList = new ArrayList<>(); appleList.add(new Apple(1, "红色")); appleList.add(new Apple(2, "红色")); appleList.add(new Apple(3, "红色")); appleList.add(new Apple(4, "红色")); appleList.add(new Apple(5, "红色")); appleList.add(new Apple(6, "红色")); seller.setAppleList(appleList); // 结算台1为空 seller.setCashOneAppleList(new ArrayList<>()); // 结算台2为空 seller.setCashTwoAppleList(new ArrayList<>()); return seller; } /** * 初始化买家 * * @return 买家 */ private static Buyer initBuyer() { Buyer buyer = new Buyer(); buyer.setAppleList(new ArrayList<>()); return buyer; } }
四、应用
我为什么要研究这个;
因为我们系统的定时任务很简单,用@EnableScheduling,@Scheduled来实现,需要自己编写分布式锁,如下:
try { // 使用redis-setNx作为分布式锁,只允许一台服务器执行此定时任务 if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(redisLockName, REDIS_KEY_LOCKED))) { return; } // 设置锁的失效时间 redisTemplate.expire(redisLockName, expireTime, TimeUnit.MINUTES); // 执行具体的定时任务逻辑 scheduleExecutor.executeSchedule(); } finally { // 定时任务执行结束后,删除锁 redisTemplate.delete(redisLockName); }
就每个定时任务,除了//执行具体的定时任务逻辑那儿不一样,其他地方都一样,就想着能不能抽象出来一个公共方法,不是传入参数,需要传入方法才行;
然后使用了函数式编程实现,代码如下:
import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.TimeUnit; /** * redis锁工具类 * * @author xxx * @date 2022-06-27 16:02 */ public class RedisLockUtil { /** * redis针对某个key的锁的value */ public static final String REDIS_KEY_LOCKED = "locked"; // 也可以使用上面提到的jdk自带的函数式接口 @FunctionalInterface public interface ScheduleExecutor { /** * 执行具体的定时任务逻辑 */ void executeSchedule(); } /** * 获得redis锁的服务器执行定时任务 * * @param redisLockName 锁的名称 * @param expireTime 锁失效时间,单位分钟 * @param redisTemplate redis接口 * @param scheduleExecutor 定时任务的具体逻辑 */ public static void startScheduleWithRedisLock(String redisLockName, int expireTime, StringRedisTemplate redisTemplate, ScheduleExecutor scheduleExecutor) { try { // 使用redis-setNx作为分布式锁,只允许一台服务器执行此定时任务 if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(redisLockName, REDIS_KEY_LOCKED))) { return; } // 设置锁的失效时间 redisTemplate.expire(redisLockName, expireTime, TimeUnit.MINUTES); // 执行具体的定时任务逻辑 scheduleExecutor.executeSchedule(); } finally { // 定时任务执行结束后,删除锁 redisTemplate.delete(redisLockName); } } }
五、总结
jdk8的λ表达式,底层大量使用的也是函数式编程,所以他们都是jdk8一起出来的;
参考:https://www.cnblogs.com/seeall/p/16427621.html