当前位置: 首页 > news >正文

Java8,函数式编程应用:

持续更新中:

函数式(Functional)接口 什么是函数式(Functional)接口 只包含一个抽象方法的接口,称为函数式接口。

你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式
抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽 象方法上进行声明)。

我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检 查它是否是一个函数式接口。同时 javadoc
也会包含一条声明,说明这个 接口是一个函数式接口。

在java.util.function包下定义了Java 8 的丰富的函数式接口:

package com.ly.frauddataplatform.luoyan;//TODO: 需要在接口上定义@FunctionalInterface注解,表示这是一个函数式的接口
//TODO: MyFunc<R,T>这里是标明泛型
@FunctionalInterface
public interface MyFunc<R,T> {/*** @Author luoyan* @Description: 函数式编程* @Date 2024/3/4 20:42* @param t: 入参,泛型可以传任何类型* @return R: 返回参数,泛型可以传任何类型**/R getValue(T t);
}

这里是自定义的函数式编程的自定义的实现接口方式:

    //TODO: 这里自定义一个方法,这个方法可以实现前置通知,后置通知,可以在调用方法的前后做一些公用的操作.//TODO: 在调用到自己的实现方法中的时候,去设计实现自己所需要的代码逻辑流程./*** @Author luoyan* @Description: 封装的方法,泛型中<R,T>:这个是声明泛型,R:返回的类型,也可以修改R为String,T为String.那么入参和出现就必须是String的类型.* @Date 2024/3/4 20:46 * @param myFunc: 这个是函数方法* @param name: 入参* @return R**/public <R,T> R toUpDate(MyFunc<R, T> myFunc, T name){System.out.println("前置通知");//TODO: 调用自己设定订代码逻辑流程.R value = myFunc.getValue(name);System.out.println(value);System.out.println("后置通知");return myFunc.getValue(name);}//TODO: 使用一个方法进行测试.@Testpublic void testDemoOne(){//TODO: 这里实现自己的代码逻辑.//TODO: 这里name是T,也就是入参,"1231"是R,也就是出参,可以自己随意定义,因为是泛型.String s = toUpDate(str -> {System.out.println(str);return "1231";}, true);System.out.println(s);}

函数式接口:有且仅有一个抽象方法的接口

Java 中的函数式编程体现就是 Lambda 表达式,所以函数式接口就是可以使用于 Lambda 使用的接口只有确保接口中有且仅有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。

如何检测一个接口是不是函数式接口呢?

@FunctionalInterface 注解,放在接口定义的上方,如果接口是函数接口,编译通过;如果不是,编译失败。

注意:

我们自己定义函数式接口的时候,@FunctionalInterface 是可选的,就算我们不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上注解。

Java 8 在 java.util.function 包下预定了大量的函数式接口供我们使用,常用如下:

  • Supplier 接口
  • Consumer 接口
  • Predicate 接口
  • Function 接口

Supplier接口

方法说明
T get()生产数据的接口

源码展示:
在这里插入图片描述

Supplier 接口被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的 get 方法就会生产什么类型的数据供我们使用。

@FunctionalInterface
public interface Supplier<T> {/*** Gets a result.** @return a result*/T get();
}

该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据,如下:
可以看到输出得值是String和1,这里也就是调用到了方法自己顶的代码逻辑流程中。

@Test
void supplier_test() {String string = getString(() -> "String");log.info(string);Integer integer = getInteger(() -> 1);log.info(integer + "");
}String getString(Supplier<String> supplier) {return supplier.get();
}Integer getInteger(Supplier<Integer> supplier) {return supplier.get();
}

Consumer

方法说明
void accept(T t)消费一个指定泛型的数据
default Consumer andThen(Consumer<? super T> after)消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合

源码展示:
在这里插入图片描述

① accept

@FunctionalInterface
public interface Consumer<T> {/*** 消费一个指定泛型的数据** @param t the input argument*/void accept(T t);/*** 默认方法*/default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}

对于 accept() 方法,它是消费,如下:

@Test
void consumer_test() {consumer_accept(100, money -> log.info("本次消费: " + money));
}/*** 定义一个方法** @param money    传递一个int类型的 money* @param consumer*/
void consumer_accept(Integer money, Consumer<Integer> consumer) {consumer.accept(money);
}

② andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是一步接一步操作。例如两个步骤组 合的情况:

@Test
void consumer_andThen_test() {consumer_andThen("abcdefg",// 先转为大写str -> log.info(str.toUpperCase()),// 在转为小写str -> log.info(str.toLowerCase()));
}void consumer_andThen(String str, Consumer<String> consumer1, Consumer<String> consumer2) {consumer1.andThen(consumer2).accept(str);
}

例子:

String[] array = { "张三,女", "李四,女", "王五,男" };

让它们按照以下格式打印:

姓名: XX 性别: XX

@Test
void printInfo() {String[] array = {"张三,女", "李四,女", "王五,男"};printInfo(array,str -> System.out.print("姓名: " + str.split(",")[0]),str -> System.out.println(" 性别: " + str.split(",")[1]));
}
void printInfo(String[] arr, Consumer<String> consumer1, Consumer<String> consumer2) {for (String s : arr) {consumer1.andThen(consumer2).accept(s);}
}

结果如下:

很明显consumer1调用了自己的System.out.print(“姓名: " +str.split(”,“)[0])这个流程,然后在调用了System.out.println(” 性别: " +str.split(“,”)[1])这个流程,把参数array作为s传入到了这个流程中,也就是印证了源码中的(T t) -> {accept(t); after.accept(t); }流程。

在这里插入图片描述

Predicate:函数接口

方法说明
boolean test(T t)对给定的参数进行判断(判断逻辑由 Lambda 表达式实现),返回一个布尔值
default Predicate negate()返回一个逻辑的否定,对应逻辑非( ! )
default Predicate and (Predicate other)返回一个组合判断,对应短路与(&&)
default Predicate or (Predicate other)返回一个组合判断,对应短路或(

源码展示:
在这里插入图片描述

① test()
可以用来判断某个值是否满足条件,如下:

/*** 判断字符串的长度是否满足条件*/
@Test
void predicate_test() {boolean predicate = predicate_test("predicate", str -> str.length() > 10);System.out.println(predicate);
}boolean predicate_test(String str, Predicate<String> predicate) {boolean b = predicate.test(str);return b;
}

② and() && or()
这两个是相对的,的关系,会短路,如下:

/*** 判断字符串是否以 A 开头,并且 equals B*/
@Test
void predicate_and_test() {boolean b = predicate_and("A", x -> x.startsWith("A"), y -> y.equals("B"));System.out.println(b);
}
boolean predicate_and(String str, Predicate<String> predicate1, Predicate<String> predicate2) {boolean b = predicate1.and(predicate2).test(str);return b;
}输出: false/*** 判断字符串是否以 A 开头,或者 equals B*/
@Test
void predicate_or_test() {boolean b = predicate_or("A", x -> x.startsWith("A"), y -> y.equals("B"));System.out.println(b);
}
boolean predicate_or(String str, Predicate<String> predicate1, Predicate<String> predicate2) {boolean b = predicate1.or(predicate2).test(str);return b;
}输出: true

③ negate()
该方法就是对 test() 取反,该方法的源码如下:

default Predicate<T> negate() {return (t) -> !test(t);
}

例子:

/*** 判断字符串的长度是否大于 5*/
@Test
void predicate_negate_test() {Predicate<String> predicate = s -> s.length() > 5;boolean b1 = predicate.test("predicate");System.out.println(b1); // trueboolean b2 = predicate.negate().test("predicate");System.out.println(b2); // false
}

第二个例子:

Predicate<Integer> predicate = n ->{if (n > 4){return true;}return false;
};Predicate<Integer> and = predicate.and(k -> {if (k < 6) {return true;}return false;
}).or(t -> {if (t == 10) {return true;}return false;
});
boolean test1 = and.test(5);
System.out.println(test1);

Function
该接口通常用于对参数进行处理,转换然后返回一个新的值。

方法说明
R apply(T t)根据类型 T 的参数获取类型 R 的结果
default Function<T,V> andThen(Function after)返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果

源码展示:
在这里插入图片描述

这里使用:Function接口来实现。此接口的特点:有入参,有出参,可以直接很好的使用。还有其他的函数。
在这里插入图片描述
比如:只需要入参,不需要出参,可以根据自己的情况而定。
在这里插入图片描述

① apply()
该方法根据类型 T 的参数获取类型 R 的结果。 例如:将 String 类型转换为 int 类型。

@Test
void function_test() {int i = function_apply("1234", x -> Integer.valueOf(x));System.out.println(i);
}/*** 吧字符串类型转为 int 类型* @param str* @param fun* @return*/
int function_apply(String str, Function<String, Integer> fun) {Integer apply = fun.apply(str);return apply;
}

② andThen()

@Test
void function_andThen_test() {double d = function_andThen("10", x -> Integer.parseInt(x) + 10, y -> Double.valueOf(y));System.out.println(d);
}/*** 把 String 类型转为 Integer 加10,然后再转为 Double 类型*/
double function_andThen(String str, Function<String, Integer> fun1, Function<Integer, Double> fun2) {double apply = fun1.andThen(fun2).apply(str);//自动拆箱return apply;
}

实际开发中,举一个例子:
比如我们要使用前置要做的事情,然后调用方法自己的逻辑,最后在执行一个后置要做的事情,其中前置流程和后置流程都是共有的流程。那么就可以使用函数式的变成去封装方法,然后每个地方都可以直接调用。这样很方便。

如果没有函数式编程,我们可能会想到反射的方式,传入一个类,一个方法,然后通过反射的方式去调用这个类中的某个方法,从而可以达到这种实现,但是太麻烦了。

目前我们需要对于redis进行上锁,然后解锁,中间是走自己的流程,那么我们就可以使用函数式编程:

package com.elong.fraud.rcenginedataconvert.constants;import com.elong.fraud.rcenginedataconvert.model.dto.TryLockParamData;
import com.ly.tcbase.cacheclient.CacheClientHA;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;import javax.annotation.Resource;
import java.util.UUID;
import java.util.function.Function;/*** @Author luoyan* @Description: redis锁* @Date 2024年01月18日 10:38* @Version: V1.0*/
@Component
public class RedissionTemplate {@Resourceprivate CacheClientHA cacheClientHa;/*** @Author luoyan* @Description:* @Date 2024/1/19 15:15* @param tryLockParamData: 入参值.*                        LockKey:key,*                        param:请求参数* @param consumer: Function<T,R> *                T:入参,*                R:返回参数* @return R**/public <R> R tryLock(TryLockParamData tryLockParamData, Function<Object,R> consumer){//TODO: 前置通知,对于参数进行校验String lockKey = tryLockParamData.getLockKey();if (ObjectUtils.isEmpty(lockKey)){return null;}//TODO: 前置通知,进行上锁String lockVal = UUID.randomUUID().toString();Boolean lock = cacheClientHa.String().setnx(lockKey, 60L, lockVal);try {long millis = System.currentTimeMillis();while (!lock) {//等待锁释放try {if (System.currentTimeMillis() - millis > 3000) {throw new RuntimeException("程序异常,未获取锁!");}Thread.sleep(100);} catch (Exception ignore) {}lock = cacheClientHa.String().setnx(lockKey, 60L, lockVal);}//TODO: 这里去调用自己的代码流程return consumer.apply(tryLockParamData.getParam());}finally {//TODO: 后置通知,去进行解锁。if (lockVal.equals(cacheClientHa.String().setcas(lockKey, lockVal, lockVal))) {cacheClientHa.Key().del(lockKey);}}}
}

第一处调用:
这里是重复调用公用的redis上锁流程,然后自己实现自己的代码逻辑流程。

private JSONObject saveOrUpdateOriginData(JSONObject newValue, String uniqueId, String sourceName) {//分布式锁String lockKey = String.format(RedisCacheConstants.DATA_SOURCE_DETAIL_LOCK_FORMAT, sourceName, uniqueId);//TODO:new TryLockParamData(lockKey):入参。逗号后面就是函数方法,也就是自己的代码逻辑。JSONObject newValReturn = redissionTemplate.tryLock(new TryLockParamData(lockKey), consumer -> {//TODO: 自己的代码逻辑流程,先上锁,然后走这里的代码逻辑流程,最后进行解锁。return newVal;});return newValReturn == null ? new JSONObject() : newValReturn;
}

第二处调用:
这里就是再次调用redis进行上锁,实现自己的代码逻辑流程。

//分布式锁
String lockKey = String.format(RedisCacheConstants.FEATURE_LIST_UPDATE_LOCK_FORMAT, uniqueId);
//TODO:new TryLockParamData(lockKey):入参。逗号后面就是函数方法,也就是自己的代码逻辑。
redissionTemplate.tryLock(new TryLockParamData(lockKey), rLock -> {//TODO: 自己的代码逻辑流程,先上锁,然后走这里的代码逻辑流程,最后进行解锁。//先执行更新,更新失败执行插入int update = dcdbFraudMapper.update(origin.getTableName(), fieldList, StrUtil.toUnderlineCase(origin.getUpdateKey()), uniqueId);if (update == 0) {boolean insert = dcdbFraudMapper.insert(origin.getTableName(), fieldList);}return null;
});

可以看到上面两处的调用使用函数式编程非常方便且代码也很美观,不用其他的方式就能够快速的实现,对于同事的阅读和理解也是非常快速的。

如果对你有帮助,或是有一些启发的话,制作不易,还请点赞收藏!!!感谢感谢。

相关文章:

  • make命令失效-解决
  • 中文文本分类_1(pytorch 实现)
  • 【pyinstaller打包记录】Linux系统打包可执行文件后,onnxruntime报警告(Init provider bridge failed)
  • 怎么把视频做成二维码形式?视频扫码观看的制作方法
  • 前端面试 跨域理解
  • 技术应用:基于MyBatis Plus自动生成数据库主键
  • K线实战分析系列之十九:反击线——看涨看跌信号
  • 零基础如何快速入门伦敦金交易
  • Windows Media Player (Win10)
  • 杭州某国企 Java 面经
  • JavaScript 基础学习笔记(五):函数、作用域、匿名函数
  • Python实战(11):正则表达式
  • HTTP头部信息解释分析(详细整理)
  • java基础-mysql
  • tcpdump 命令
  • Android组件 - 收藏集 - 掘金
  • emacs初体验
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • Invalidate和postInvalidate的区别
  • iOS 系统授权开发
  • java中具有继承关系的类及其对象初始化顺序
  • js ES6 求数组的交集,并集,还有差集
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • php ci框架整合银盛支付
  • php的插入排序,通过双层for循环
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 分类模型——Logistics Regression
  • 分享一份非常强势的Android面试题
  • 解析带emoji和链接的聊天系统消息
  • 前端之React实战:创建跨平台的项目架构
  • 区块链技术特点之去中心化特性
  • 日剧·日综资源集合(建议收藏)
  • 手机端车牌号码键盘的vue组件
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 协程
  • 一天一个设计模式之JS实现——适配器模式
  • 移动端解决方案学习记录
  • 用jquery写贪吃蛇
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 通过调用文摘列表API获取文摘
  • ​secrets --- 生成管理密码的安全随机数​
  • !$boo在php中什么意思,php前戏
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (04)odoo视图操作
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (42)STM32——LCD显示屏实验笔记
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (二)hibernate配置管理
  • (分布式缓存)Redis分片集群
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (三)elasticsearch 源码之启动流程分析
  • (顺序)容器的好伴侣 --- 容器适配器