Skip to main content

如何优雅的使用枚举[2]以及函数式编程

javaenum函数式编程About 4 min

这篇文章通过一个例子探讨如何减少条件判断的出现. 并介绍一下函数式编程.

不好的代码

注意下面打印日志的地方我们可以做很多的业务处理.

private static void dealWorkByAge(Integer age) {
    if (Objects.isNull(age) || age <= 0) {
        throw new IllegalArgumentException(String.format("[dealWorkByAge]年龄不能为空或小于等于0, age: [%s]", age));
    }

    if (age <= 20) {
        log.debug("人生得意马蹄急");
    } else if (age <= 50) {
        log.debug("人到中年不得已");
    } else if(age<=100) {
        log.debug("一寸光阴一寸金");
    } else {
        log.debug("阅尽尘世经风雨");
    }

}

转换为枚举方式处理

定义枚举类

@Slf4j
public enum AgeEnum {

    TEEN(20, t -> {
        log.debug("人生得意马蹄急");
    }),
    ADULT(50, t -> {
        log.debug("人到中年不得已");
    }),
    OLD(100, t -> {
        log.debug("一寸光阴一寸金");
    }),
    GOD(Integer.MAX_VALUE, t -> {
        log.debug("阅尽尘世经风雨");
    });


    public Integer age;
    public Consumer consumer;

    AgeEnum(Integer age, Consumer consumer) {
        this.age = age;
        this.consumer = consumer;
    }
}

使用枚举转换if逻辑处理:

private static void dealWorkByAgeEnum(Integer age) {
    if (Objects.isNull(age) || age <= 0) {
        throw new IllegalArgumentException(String.format("[dealWorkByAge]年龄不能为空或小于等于0, age: [%s]", age));
    }

    for (AgeEnum level : AgeEnum.values()) {
        if (age <= level.age) {
            level.consumer.accept(age);
            break;
        }
    }
}

这个例子不是很好, 需要注意的是: for循环中不会有if的那种优先级次序. 因此可能会导致业务处理出现问题. 比如输入age为10, 该参数在任何一个枚举类型中都是满足处理条件的. 因此最好将范围限制到为闭区间内.

另外再说明一点. 前端下拉框中的值如果传对应枚举类型的名字. 是可以自动转化成相应枚举类型的. 因此后端直接调用枚举相应的函数方法即可完成业务处理. 😃 是不是有点恍然大悟

函数式编程

2020-01-30-14-06-24
打开java.util.function包我们可以看到jdk提供的一些函数式接口. 主要有以下几种类型:

  1. Consumer 消费型. 输入一个参数, 没有返回值

    Consumer<Integer> consumer1 = t -> log.debug("consumer1====> " + t);
    Consumer<Integer> consumer2 = consumer1.andThen(t -> log.debug("consumer2====> " + t));
    
    consumer1.accept(1);
    consumer2.accept(2);
    
    

    2020-01-30-13-11-47

  2. Function 给定一个输入返回一个输出

    Function<Integer, String> function1 = t -> "======> " + t;
    Function<Integer, String> andThen = function1.andThen(t -> " =====> andThen: " + t);
    
    
    Function<Integer, String> compose = function1.compose(t -> t * 2);
    
    log.debug("compose 返回结果: [{}]", compose.apply(4));
    log.debug("andThen 返回结果: [{}]", andThen.apply(4));
    
    

    2020-01-30-13-26-36

    这里提及一下compose, 可以看一下下面的源码:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    

    很明显, 首先执行输入的函数并将其返回值作为输入. 执行过程是先执行输入, 后执行调用者.
    这和andThen正好是相反的. 如下是andThen的源码:

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    

    该方法是将当前的结果作为输入函数的输入参数. 执行过程是先执行调用者, 后执行输入.

  3. UnaryOperator 输入并返回该类型, 继承Function<T, T>

    UnaryOperator<Integer> unary1 = t -> t * 2;
    log.debug("UnaryOperator 返回结果: [{}]", unary1.apply(2));
    
  4. Predicate 输入一个参数, 返回判断结果

    Predicate<Integer> predicate = t -> t > 10;
    log.debug("是否大于10: [{}]", predicate.test(20));
    Predicate<Integer> and = predicate.and(t -> t < 30);
    log.debug("是否大于10且小于30: [{}]", and.test(20));
    Predicate<Integer> or = predicate.or(t -> t > 50);
    log.debug("是否大于10或50: [{}]", or.test(20));
    Predicate<Integer> negate = predicate.negate();
    log.debug("negate: [{}]" , negate.test(20));
    

    2020-01-30-13-56-22

  5. Supplier 没有输入参数, 获取程序运行返回结果

    Supplier<Integer> supplier = () -> 10;
    log.debug("Supplier: [{}]", supplier.get());
    

自定义函数接口

如果我们的业务依赖的不止这些参数要如何处理呢?(多个参数输入见Bi****看名字也能猜到这种接口支持两个参数输入)

一种方法是使用compose, andThen进行组合处理, 上面我们在Function接口的测试代码中已经分析了, 不过这种适合于下一步与上一步的相依赖的情况. 另外一种方法就是定义自己的函数接口支持多参数输入.

定义函数

@FunctionalInterface
public interface MineFunction<A, B, C, D, R> {
    R exe(A a, B b, C c, D d);
}

@FunctionalInterface表示这是一个函数接口

测试

MineFunction<Integer, Integer, Integer, String, String> mine =
        (a, b, c, d) -> String.format("%d,%d,%d,%s", a,b,c,d);
log.debug("mine: [{}]" , mine.exe(1,2,3,"哈哈哈"));

结果

2020-01-30-14-24-46

是不是很简单. 😃
注意如果你的多个参数之间可以封装成一个对象的话为什么不进行封装呢?

end

2020-01-30-14-28-43
就到这吧, 谢谢.

What do you think?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v3.0.0-alpha.10