关于函数式接口和编程的解析和案例实战

文章目录

  • 匿名内部类
    • “匿名”在哪里
  • 函数式编程
    • lambda表达式的条件
    • Supplier
      • 使用示例
    • Consumer
      • accept
      • andThen
      • 使用场景
    • Functional
      • BiFunctional
      • TriFunctional

匿名内部类

匿名内部类的学习和使用是实现lambda表达式和函数式编程的基础。是想一下,我们在使用接口中的方法的时候,正常流程都是定义这个接口的实现类,然后使用实现类的对象调用接口中的方法。下面展示一个接口的方法使用的常规方法

public interface Test {void forTest();
}

如果想要使用这个接口,我们需要定义Test的实现类

// 定义一个学生类实现 Swim 游泳接口
public class TestImpl implements Test{// 实现方法@Overridepublic void test() {//for testSystem.out.println("测试test方法");}//测试public static void main(String[] args) {// 创建 Student 类的对象 s1TestImpl t = new TestImpl();//打印调用实现的方法t.test();}
}

“匿名”在哪里

1. 匿名了实现的接口所在的父类
java中可以根据传入的对象类型,区分这个对象的类信息,所以匿名的第一层就是省略了这个类,省去了 implements ClassName中的ClassName。之所以被称为 “匿名”,主要是因为它没有显式地定义类名,并且在创建对象的同时就直接实现了某个接口或者继承了某个类,以下是关于它为何可以实现匿名以及具体匿名了哪些信息的详细解释:

2. 匿名了外部独立的类定义结构
匿名内部类将类的定义和使用紧密结合在了一起,它直接嵌套在创建对象的代码语句中,没有像常规类那样在外部单独的代码块里呈现完整的类结构,比如类的修饰符(public、private 等)、类的继承关系(除了在匿名内部类中体现的继承自某个类或者实现某个接口)等这些在普通类定义中可能出现的结构信息都被隐去了,整个类的定义仿佛是 “匿名” 地融入到了使用它的那一处代码当中,使代码结构更加简洁,不过也相对牺牲了一些代码的清晰性和可维护性。换而言之,匿名内部类中我们的TestImpl也不再需要显示给出,这里我们以实现自定义比较器作为示例

List<Integer> nums = Arrays.asList(1,5,3,7,11,6,2);
nums.sort(new Comparator<Integer>(){@Overridepublic int compare(Integer i1, Integer i2){return i2- i1;}
});

3. 其他信息补充说明
对于内部的方法入参和重写的注解,是实现一个接口方法中必须的信息,有无返回值需要根据接口方法定义是void还是其他区分,以上就是匿名内部类的使用。在日常开发中,基于参数是一个接口方法返回值的这种写法较为常见,例如:Runnable或者Comarator等

函数式编程

当前java 8中提供了很多基于函数式的新特性。其中函数式接口有代表性的非 lambda表达式莫属。相较于匿名内部类,lambda表达式更加精简,仅保留传入的实参和返回值以及计算逻辑。上文中的自定义比较器在lambda表达式下可以优化为:“

List<Integer> nums = Arrays.asList(1,5,3,7,11,6,2);
nums.sort((i1, i2) -> i2- i1);

lambda表达式的条件

1. 必须实现的是函数式接口

	函数式接口是指只包含一个抽象方法的接口(除了从 Object 类继承的公共方法,如 equals、hashCode 等,这些不算额外的抽象方法)。这是最关键的前提条件,因为 Lambda 表达式本质上就是为了简洁地实现函数式接口而设计的语法糖。

代码示例:“下面的Runnable仅包含一个接口,因此可以改写为lambda表达式

new Runnable() {@Overridepublic void run() {System.out.println("执行任务");}
};

如果是含有多个抽象方法的接口,无法使用lambda表达式改写,因为编译器无法区分你需要覆盖的具体方法,所以下面的接口无法适配lambda表达式的改造

MyTestInterface myObj = new MyTestInterface() {@Overridepublic void method1() {// 具体实现逻辑}@Overridepublic int method2(int num) {return num * 2;}
};

2. 接口抽象方法的参数和返回值类型需明确可推断
虽然 Java 有类型推断机制,但在使用 Lambda 表达式改写匿名内部类时,接口抽象方法的参数类型和返回值类型要能够相对清晰地确定,以便编译器能正确解析 Lambda 表达式所代表的逻辑。例如比较器的重写,编译器可以根据传入的实参判断类型是String

interface StringJoinerFunction {String join(String s1, String s2);
}
public class StringJoinerExampleWithError {public static void main(String[] args) {StringJoinerFunction joiner = (s1, s2) -> {System.out.println(s1 + s2);return s1+s2; // 返回值类型与接口定义的String不符,无法正确改写};System.out.println(joiner.join("Hello", "World"));}
}

3. 实现逻辑相对简单,无复杂的语句块或逻辑分支
这个要求仅仅基于代码本身的可读写,例如一些if else逻辑也可以用于lamvbda表达式,但是却失去了代码简洁原本的意义。不如直接使用匿名内部类或者实现接口的形式完成抽象方法的调用。

Supplier

Supplier(供给者) Supplier是一个不接受任何输入参数但返回一个结果的操作。它主要用于生成数据或对象。Supplier接口定义了一个get方法,该方法不接受任何输入参数并返回一个结果。表示从函数式接口返回的对象中获取Supplier内的数据

 Supplier<String> supplier =  () -> {return "Hello World";};System.out.println(supplier.get());

Supplier在企业级开发中的应用场景大约有以下几个方面

使用示例

1. 动态加载配置
一些数据库配置信息和连接池的加载如果消耗较大的资源,并且希望使用时候动态加载的情况下,可以使用Supplier预先定义这个连接配置项。避免在应用启动阶段就占用大量内存和初始化时间。
例如,创建一个数据库连接池对象,代码示例如下:

    public static Supplier<DataSource> supplier = () -> {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("xxxx:3306/pdb_19c");dataSource.setUsername("Hikari");dataSource.setPassword("123456");return dataSource;};public static DataSource getDataSource () {return (DataSource) Optional.ofNullable(supplier).get();}public static Connection getConnectionDyn() throws Exception {return getDataSource().getConnection();}

2. 生成默认数据

继续上面的思路,可以使用static final修饰默认值,使用Supplier处理指向默认值的配置,用作某属性为空的兜底配置

   private static final Supplier<Integer> portSupplier = () -> {Optional<Integer> configPort = readPortFromConfig();return configPort.orElse(DEFAULT_PORT);};private static Optional<Integer> readPortFromConfig() {// 模拟从配置文件读取端口号,这里假设返回Optional.empty()表示读取失败Integer i1 = null;return Optional.ofNullable(i1);}public static void main(String[] args) {System.out.println(portSupplier.get());}

3. 实现灵活的策略模式
策略模式在企业级开发中常用于根据不同情况选择不同的业务逻辑执行方式。Supplier 结合 Lambda 表达式可以让策略模式的实现更加简洁和灵活,下面是用Supplier实现的策略模式

    public static Supplier<BigDecimal> normalMemberDiscount = () -> {return BigDecimal.valueOf(0.9); // 9折};// 高级会员折扣策略public static Supplier<BigDecimal> premiumMemberDiscount = () -> {return BigDecimal.valueOf(0.8); // 8折};public static BigDecimal calculateDiscount(Order order, Supplier<BigDecimal> discountSupplier) {return order.getAmount().multiply(discountSupplier.get());}

4. 生成运行时的测试数据
Supplier 结合 Lambda 表达式可以方便地实现这一点,让测试数据的生成更加灵活和动态。
例如,在测试一个用户注册模块时,需要生成不同的用户信息作为测试数据

class UserTestDataGenerator {private static Supplier<String> usernameSupplier = () -> {Random random = new Random();return "user_" + random.nextInt(1000);};private static Supplier<String> passwordSupplier = () -> {Random random = new Random();return "pass_" + random.nextInt(1000);};private static Supplier<String> emailSupplier = () -> {Random random = new Random();return "user_" + random.nextInt(1000) + "@example.com";};public static Supplier<User> userSupplier = () -> new User(usernameSupplier.get(), passwordSupplier.get(), emailSupplier.get());
}

Consumer

序言:顾名思义,是消费者的意思。这个函数本身不接收返回值类型,一般实现打印、输出、入参的转换处理等操作
1. 代码示例
下面给出Consumer定义的示例,可以看到这个对象的定义类似于一个Comparator比较器,其作用是接收一个字符串,然后执行accept方法中对于字符串的操作。

 Consumer<String> con = new Consumer<String>() {@Overridepublic void accept(String string) {System.out.println("string values :" + string);}};con.accept("Hello World"); 

如果使用新版idea编译器的亲们,可以发现编译器提示这个方法的优化写法为使用lambda表达式的形式,即下面的格式:

  Consumer<String> con = string -> System.out.println("string values :" + string);con.accept("Hello World");

accept

accept在笔者看来可以视作一个开关,当主线程调用这个方法的时候,执行开关内部的逻辑。可以类比线程的submit方法,执行内部的代码块,:

 public static void main(String[] args) {// 定义一个Consumer<String>类型的变量,使用Lambda表达式实现其accept方法Consumer<String> stringConsumer = (str) -> System.out.println(str);// 调用accept方法,传入一个字符串参数stringConsumer.accept("Hello, World!");}
}

由上面的代码可以了解到,我们设置Consumer的定义,并且在想要让其执行的地方应用accept()触发Consumer的函数部分,输出了Hello World

andThen

如果一个操作之后还有其他操作,可以将其Consumer对象放到andThen的参数位置上,这个是因为andThen相当于执行accept的accept,从源码分析上可以得到这样的结论

default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}

consumer之间可以使用andThen进行串联式的编排,例如对姓名进行输出之后转换字符串为小写,通过consumer的定义和组装可以轻松实现。同时这两consumer对象也可以放在函数方法的形参位置作为回调方法使用

        List<String> arr = Arrays.asList("Wang", "Zi", "Meng");Consumer<String> con2 = out -> System.out.print("会员姓名:"+ out +";");Consumer<String> con3 = str2 -> System.out.println("小写版本为: " +str2.toLowerCase());con2.andThen(con3);arr.stream().forEach(con2.andThen(con3)::accept);

输出结果:
在这里插入图片描述

使用场景

使用函数式的consumer和普通的for循环有什么区别,将通过下面的示例进行展示

case 1 代码简洁性与可读性

如果在for循环中需要多重处理,并且这段代码整体写在循环体内容易造成多层嵌套或者本身具有一定程度的复用性,应该考虑将其抽象出来作为一个Consumer对象,例如循环处理某一个属性,需要将属性进行字符的转换或者精度的保留
  Consumer<String> con = string -> System.out.println(string.toUppercase());con.accept("Hello World");

2. 更好地支持函数式编程范式

Java 8 引入了函数式编程的一些特性,Consumer 作为函数式接口(只包含一个抽象方法 accept 的接口),符合函数式编程中对行为(操作)的抽象概念。它可以方便地与其他函数式接口(如 Predicate、Function 等)以及 Stream API 等配合使用,实现更高级、更灵活的编程模式,比如对集合进行过滤(使用 Predicate)后再对满足条件的元素进行消费(使用 Consumer)等操作,能够在代码中更好地体现数据的转换、处理流程,让代码更具逻辑性和条理性,同时也便于进行代码的单元测试等维护工作。

3. 增强代码的可复用性和灵活性

将操作抽象为 Consumer 接口,可以方便地在不同的地方复用这些操作逻辑。例如,前面提到的将字符串转换为大写的 Consumer 操作,可以在多个需要对字符串进行此处理的地方重复使用,只需要传递这个 Consumer 实例即可。而且通过将 Consumer 作为方法参数,能让方法的功能更加灵活多样,根据传入的不同 Consumer 实现不同的业务逻辑,提高了代码应对不同需求变化的能力,降低了代码的耦合度,使得整个代码库更加易于扩展和维护。虽然在很多情况下使用普通逻辑确实也能实现相同的功能,但 Consumer 函数式接口凭借其在代码简洁性、函数式编程支持以及复用性和灵活性等方面的优势,在 Java 编程中有着广泛且合适的应用场景,能够帮助开发人员更高效、优雅地编写代码,应对各种复杂的业务需求

Functional

通过lambda表达式可以看出匿名内部类的优化写法,func定义如下:

		Function<Integer,String> func = new Function<Integer, String>() {@Overridepublic String apply(Integer integer) {return String.valueOf(integer);}};list.stream().forEach(li -> {System.out.println(func.apply(li));});Function<Integer,String> funcLLambda = integer -> String.valueOf(integer);

BiFunctional

这个接口有四个类型参数,T、U、V 分别对应三个输入参数的类型,而 R 对应返回结果的类型,其唯一的抽象方法 apply 接受三个参数(分别为 T、U、V 类型),并返回一个 R 类型的结果,符合接受三个元素作为入参并返回结果的需求,并且由于标注了 @FunctionalInterface,可以很好地使用 Lambda 表达式来实现它。

代码示例

   private BiFunction<Double,Double,Double> biFunction = (Double a, Double b) -> Math.sqrt(a*a + b*b);

TriFunctional

这个接口有四个类型参数,T、U、V 分别对应三个输入参数的类型,而 R 对应返回结果的类型,其唯一的抽象方法 apply 接受三个参数(分别为 T、U、V 类型),并返回一个 R 类型的结果,符合接受三个元素作为入参并返回结果的需求,并且由于标注了 @FunctionalInterface,可以很好地使用 Lambda 表达式来实现它

代码示例

 public static void main(String[] args) {TriFunction<String, String, String, String> formatFunction = (str1, str2, str3) -> {return String.format("姓名: %s, 年龄: %s, 城市: %s", str1, str2, str3);};String result = formatFunction.apply("张三", "25", "北京");System.out.println(result);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/62374.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记:黑马程序员JavaWeb开发教程(2024.11.29)

10.5 案例-部门管理-新增 如何接收来自前端的数据: 接收到json数据之后&#xff0c;利用RequestBody注解&#xff0c;将前端响应回来的json格式的数据封装到实体类中 对代码中Controller层的优化 发现路径中都有/depts&#xff0c;可以将每个方法对应请求路径中的…

数据库管理-第268期 srvctl在ADG备库添加PDB的service报错,看如何解决(20241129)

数据库管理268期 2024-11-29 数据库管理-第268期 srvctl在ADG备库添加PDB的service报错&#xff0c;看如何解决&#xff08;20241129&#xff09;1 背景2 处理过程3 原因总结 数据库管理-第268期 srvctl在ADG备库添加PDB的service报错&#xff0c;看如何解决&#xff08;202411…

brew安装mongodb和php-mongodb扩展新手教程

1、首先保证macos下成功安装了Homebrew&#xff0c; 在终端输入如下命令&#xff1a; brew search mongodb 搜索是不是有mongodb资源&#xff0c; 演示效果如下&#xff1a; 2、下面来介绍Brew 安装 MongoDB&#xff0c;代码如下&#xff1a; brew tap mongodb/brew brew in…

国产FPGA+DSP 双FMC 6U VPX处理板

高性能国产化信号处理平台采用6U VPX架构&#xff0c;双FMC接口国产V7 FPGA 国产多核 DSP 的硬件架构&#xff0c;可以完成一体化电子系统、有源相控阵雷达、电子侦察、MIMO 通信、声呐等领域的高速实时信号处理。 信号处理平台的组成框图如图 1 所示&#xff0c; DSP处理器采…

AI数据分析工具(二)

豆包-免费 优点 强大的数据处理能力&#xff1a; 豆包能够与Excel无缝集成&#xff0c;支持多种数据类型的导入&#xff0c;包括文本、数字、日期等&#xff0c;使得数据整理和分析变得更加便捷。豆包提供了丰富的数据处理功能&#xff0c;如数据去重、填充缺失值、转换格式等…

STM32G4系列MCU的Direct memory access controller (DMA)功能介绍之二

目录 概述 1 DMA通道 1.1 可编程数据大小 1.2 指针增量 2 通道配置 2.1 配置步骤 2.2 通道状态和禁用通道 3 模式应用 3.1 循环模式&#xff08;内存到外设/外设到内存的传输&#xff09; 3.2 内存到内存模式 3.3 Peripheral-to-peripheral模式 3.4 编程转移方向&a…

【一文读懂】大语言模型

学习参考 项目教程&#xff1a;中文教程 代码仓库&#xff1a;代码地址 仓库代码目录说明&#xff1a; requirements.txt&#xff1a;官方环境下的安装依赖 notebook&#xff1a;Notebook 源代码文件 docs&#xff1a;Markdown 文档文件 figures&#xff1a;图片 data_base&…

大数据-234 离线数仓 - 异构数据源 DataX 将数据 从 HDFS 到 MySQL

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…

鸿蒙进阶篇-Stage模型、UIAbility

“在科技的浪潮中&#xff0c;鸿蒙操作系统宛如一颗璀璨的新星&#xff0c;引领着创新的方向。作为鸿蒙开天组&#xff0c;今天我们将一同踏上鸿蒙基础的探索之旅&#xff0c;为您揭开这一神奇系统的神秘面纱。” 各位小伙伴们我们又见面了,我就是鸿蒙开天组,下面让我们进入今…

学习threejs,使用specularMap设置高光贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.MeshPhongMaterial高…

一个简洁的ajax注册登录找回密码切换的前端页面

成功和失败不同颜色显示&#xff0c;纯原生代码不需要jq等第三方插件 <% Language"VBScript" CodePage"65001"%> <% Response.Charset "UTF-8" Session.CodePage "65001" Response.Addheader "Content-Type",&q…

uniapp首页样式,实现菜单导航结构

实现菜单导航结构 1.导入字体图标库需要的文件 2.修改引用路径iconfont.css 3.导入到App.vue中 <style>import url(./static/font/iconfont.css); </style>导航区域代码 VUE代码 <template><view class"home"><!-- 导航区域 --><…

解析客服知识库搭建的五个必要性

在当今竞争激烈的商业环境中&#xff0c;客服知识库的搭建已成为企业提升服务质量、优化客户体验的重要手段。一个完善的客服知识库不仅能帮助企业高效管理客户服务流程&#xff0c;还能显著提升客户满意度和忠诚度。以下是搭建客服知识库的五个必要性&#xff1a; 1. 提升服务…

淘宝Vision Pro:革新购物体验的沉浸式未来

引言 简要介绍淘宝Vision Pro版的背景,包括它在美区AppStore的发布及WWDC上的展示。阐述本文的目的:为读者提供一个全面的功能概览与设计背后的思考。设计原则 列出并简要解释5条设计原则(熟悉、直观、真实、实用、易用)。说明这些原则如何指导整个产品设计过程。核心功能详…

【CSS in Depth 2 精译_062】第 10 章 CSS 中的容器查询(@container)概述 + 10.1 容器查询的一个简单示例

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 【第十章 CSS 容器查询】 ✔️ 10.1 容器查询的一个简单示例 ✔️ 10.1.1 容器尺寸查询的用法 ✔️ 10.2 深入理解容器10.3 与容器相关的单位10.4 容器样式查询的用法10.5 本章小结 文章目录 第 10…

HCIE:详解OSPF,从基础到高级特性再到深入研究

目录 前言 一、OSPF协议基本原理 简介 基本原理 OSPF路由器类型 OSPF网络类型 OSPF报文类型和封装 OSPF邻居的建立的维护 DR和BDR的选举 伪节点 LSDB的更新 OSPF的配置 二、OSPF的高级特性 虚连接&#xff08;Virtual-Link&#xff09; OSPF的LSA和路由选择 OSPF…

think php处理 异步 url 请求 记录

1、需求 某网站 需要 AI生成音乐&#xff0c;生成mp3文件的时候需要等待&#xff0c;需要程序中实时监听mp3文件是否生成 2、用的开发框架 为php 3、文件结构 配置路由设置 Route::group(/music, function () {Route::post(/musicLyrics, AiMusic/musicLyrics);//Ai生成歌词流式…

【VRChat 改模】开发环境搭建:VCC、VRChat SDK、Unity 等环境配置

一、配置 Unity 相关 1.下载 UnityHub 下载地址&#xff1a;https://unity.com/download 安装打开后如图所示&#xff1a; 2.下载 VRChat 官方推荐版本的 Unity 跳转界面&#xff08;VRChat 官方推荐页面&#xff09;&#xff1a;https://creators.vrchat.com/sdk/upgrade/…

ollama部署bge-m3,并实现与dify平台对接

概述 这几天为了写技术博客,各种组件可谓是装了卸,卸了装,只想复现一些东西,确保你们看到的东西都是可以复现的。 (看在我这么认真的份上,求个关注啊,拜托各位观众老爷了。) 这不,为了实验在windows上docker里运行pytorch,把docker重装了。 dify也得重装: Dify基…

详细介绍HTTP与RPC:为什么有了HTTP,还需要RPC?

目录 一、HTTP 二、RPC 介绍 工作原理 核心功能 如何服务寻址 如何进行序列化和反序列化 如何网络传输 基于 TCP 协议的 RPC 调用 基于 HTTP 协议的 RPC 调用 实现方式 优点和缺点 使用场景 常见框架 示例 三、问题 问题一&#xff1a;是先有HTTP还是先有RPC&…