教你如何封装自定义函数式接口

前言

Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包,和传统数学上的意义有区别。【摘自百度百科】

Java在JDK8的时候引入了Lambda表达式的用法,它简化了我们的代码并且可以让我们的代码更简洁,也大大的提高了我们代码的可读性。   其实我们在日常的一些工作中或多或少都有用到过Lambda表达式,例如Stream流、MybatisPlus中的一些使用等等。那么我们如何去封装自定义的Lambda表达式呢?又为何要封装自定义的Lambda表达式呢?别急,且听我娓娓道来。

必要知识

在我们进行自定义Lambda表达式之前,应该先了解一下它的原理以及一些相关的类,这样有助于我们根据现有的业务去编写相应的代码。

Function

使用过MP的小伙伴们应该都知道,MybatisPlus中的条件构造器中有一项叫做LambdaQueryWrapper的条件构造器,用来构造我们需要的函数,在这里我举个例子:

java 复制代码
		LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(User::getUsername, User::getPassword);

从上面的代码我们可以看到,在 select 这个方法中,我们使用的不是传统的getter的写法,而是中间带了两个冒号,这就是lambda表达式语法糖的一种表现形式,我们点进去看看他里面长什么样。

里面的内容很简单,我们可以看到这个方法的入参是多个SFunction,那我们再点进去这个SFunction看看里面有什么。

它里面的逻辑很简单,加了一个 @FunctionalInterface 的注解,并继承了 Function ,点进去看一下

我们可以看到这个接口也加了@FunctionalInterface注解,并且接口上有两个泛型:TR T表示了我们的入参类型 R表示了我们的出参类型

我们再来看接口上面的这个注解 @FunctionalInterface,不知道是啥意思,那我们就继续往里面点!

点进去之后我们发现没什么东西,但是上面有注释,借助翻译看一下

大概的意思就是说我使用了这个注解后,就可以标明该接口为一个函数式的接口,然后就可以支持Lambda表达式了 那么除了Function还有没有其他的函数式接口呢?答案是有的!我们只要进入@FunctionalInterface注解内,按住Ctrl往里面点就可以看到所有引用该注解的接口有哪些了,这里就不一一赘述了,就只捡几个比较典型的例子给大家说明一下。

Consumer

Java中引入的函数式接口可不止Function一个,另外还有ConsumerSupplierRunnable等,别急,咱们一个一个看。

首先是Consumer这个接口,打开之后可以看到它也有一个@FunctionalInterface注解, 刚才我们已经知道了只要引用了这个注解,那么下面的接口就被声明为一个函数式接口。 但是它只有一个泛型T,并且它的方法里可以看到返回类型为void。 那么意思就很明显了,它表明了T入参类型,没有返回参数。

Supplier

另外一个函数式接口为Supplier,我们看一下这个接口内部都写了什么。

这个函数式接口也引用了@FunctionalInterface注解,这个就不多说了,它也有一个泛型T,但是要注意的是这个泛型与上面Consumer的泛型不一样 ,它代表的不是入参,而是出参类型,从下面的get方法也可以看出来。

Runnable

除了上面说的同时有入参和出参的Function只有入参的Consumer只有出参的Supplier之外,还有一种入参和出参都没有的接口,Runnable就是其中一个典型的例子,我们找到这个类进去看一下。

表格

接下来我们做一个表格,以供我们清晰的看到我们在定义函数式接口时可选的几种类型

接口名 入参 出参
Function
Consumer
Supplier
Runnable

通过上述表格,我们可以根据我们项目中具体的业务来选择到底要用哪一种。

自定义函数式接口

我们在实际的开发工作中,经常可以看到类似的业务,比如:如果结果为false则抛出异常。我们可能会以这种形式来完成这样的业务:

java 复制代码
		boolean res = xxxService.add();
        if (Boolean.FALSE.equals(res)){
            throw new xxxException();
        }

但是我们可以通过Lambda表达式的方式来完成这样的业务,首先我们去编写一个工具类。

FunctionUtils

isFalse

新建一个FunctionUtils,并在里面写入下面这个方法。

java 复制代码
public static CustomException isFalse(boolean res) {
    return (errorMessage) -> {
        if (Boolean.FALSE.equals(res)) {
            throw new RuntimeException(errorMessage);
        }
    };
}

其中CustomException是我们自定义的一个接口,如下:

java 复制代码
@FunctionalInterface
public interface CustomException {
	/**
	* @param message 错误信息
	* @return 
	*/
    void throwCustomException(String message);
}

然后我们在调用的时候就可以这样调用来完成我们需要的业务了。

java 复制代码
	boolean res = xxxService.doSomething();
    FunctionUtils.isFalse(res).throwCustomException("error");

isTrueOrFalse

当然,除了这种调用之外,还可以有一种类似于if-else的调用方法。我们在FunctionUtils中添加下面这个方法。

java 复制代码
public static LogicHandler isTrueOrFalse(boolean res) {
    return (trueHandler, falseHandler) -> {
        if (Boolean.TRUE.equals(res)) {
            trueHandler.run();
        } else {
            falseHandler.run();
        }
    };
}

其中LogicHandler也是我们自定义的一个函数式接口,并在其内部定义了两个无入参和出参的处理器:

java 复制代码
@FunctionalInterface
public interface LogicHandler {
	/**
	* @param trueHandler  成功处理器
	* @param falseHandler 失败处理器
	* @return
	*/
    void logicHandler(Runnable trueHandler, Runnable falseHandler);
}

传入两个处理器,让他来帮我们处理不同的业务,我们在调用的时候就可以这样去调用:

java 复制代码
boolean res = xxxService.doSomething();
FunctionUtils.isTrueOrFalse(res).logicHandler(() -> {
        //To do something 
    },
    () -> {
        //To do something 
});

这样我们就可以以lambda的形式来处理我们不同的业务了。

isPresent

除此之外我们可能还会遇到一些判断对象是否为空的场景,这时候我们也可以去封装函数式接口来实现。在FunctionUtils中加入如下代码:

java 复制代码
public static PresentOrOtherHandler<?> isPresent(Object obj) {
    return (action, otherHandler) -> {
        if (Objects.nonNull(obj)) {
            action.accept(obj);
        } else {
            otherHandler.run();
        }
    };
}

我们在调用的时候通过如下的方式进行调用:

java 复制代码
FunctionUtils.isPresent(null).presentOrOtherHandler(System.out::println,
            () -> {
                //to do something
            });

如果对象不为空的话则执行传入的Lambda表达式,否则就进入到其他的逻辑中。 在这里我们传入的为System.out::println,它等同于System.out.println(obj) ,相当于Consumer的处理方式,我们知道Consumer是需要一个入参的,而System.out::println就相当于我们的入参。

那么上面这段代码可以解释为:如果对象非空的话就执行传入的方法,否则执行定义好的逻辑。

总结

我们在封装函数式接口时总共需要用到FunctionConsumerSupplierRunnable四种方式进行调用,而这四种方式各有各的特点,我们也可以根据自己的业务场景来选择不同的接口去封装。

封装函数式接口可以使我们的代码更加优雅和简洁,提高了我们代码的可读性,但是我们也不能无脑的去使用该方法,遇到什么样的接口都使用函数式的接口进行封装,还是要 根据实际的业务场景来选择 是否要封装自定义的函数式接口。

相关推荐
坐吃山猪2 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫3 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao3 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区4 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT5 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy5 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss7 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续7 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0447 小时前
ReAct模式解读
java·ai