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

 

posted @ 2022-06-29 18:06  seeAll  阅读(1011)  评论(0编辑  收藏  举报