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

文章目录

  • 匿名内部类
    • “匿名”在哪里
  • 函数式编程
    • 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.rhkb.cn/news/481465.html

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

相关文章

ChatGPT 网络安全秘籍(二)

第三章&#xff1a;代码分析和安全开发 这一章深入探讨软件开发的复杂过程&#xff0c;关注当今数字世界中的一个关键问题&#xff1a;确保软件系统的安全。随着技术的不断复杂和威胁的不断演变&#xff0c;采用融合了安全考虑的安全软件开发生命周期&#xff08;SSDLC&#x…

学习笔记044——HashMap源码学习2

文章目录 1、HasMap 底层实现2、HashMap 加载顺序 1、HasMap 底层实现 JDK 1.8 HashMap 底层设计涉及到三种不同的数据结构&#xff0c;分别是数组、链表、红黑树。 1、基本的存储是数组&#xff0c;根据 key 值求出一个数组下标&#xff0c;将元素&#xff08;key-value&am…

计算机网络常见面试题总结(上)

计算机网络基础 网络分层模型 OSI 七层模型是什么&#xff1f;每一层的作用是什么&#xff1f; OSI 七层模型 是国际标准化组织提出的一个网络分层模型&#xff0c;其大体结构以及每一层提供的功能如下图所示&#xff1a; 每一层都专注做一件事情&#xff0c;并且每一层都需…

用micropython 操作stm32f4单片机的定时器实现蜂鸣器驱动

import pyb import time # 初始化引脚和定时器通道作为PWM输出 # 注意&#xff1a;这里我们假设您使用的是支持PWM的引脚和定时器 # 在不同的MicroPython板上&#xff0c;支持的引脚和定时器可能不同 # 请查阅您的板的文档以确认正确的引脚和定时器 buzzer_pin pyb.Pin(PD15,…

前端框架Vue3项目实战(基于Vue3实现一个小相册)

下面是是对Vue3操作的一个项目实战 下面代码是html的基本骨架&#xff08;没有任何的功能&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>相册</title> <style&…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-39

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

群控系统服务端开发模式-应用开发-前端短信配置开发

一、添加视图 在根目录下src文件夹下views文件夹下param文件夹下sms文件夹下&#xff0c;新建index.vue&#xff0c;代码如下 <template><div class"app-container"><div class"filter-container" style"float:left;"><el…

极致性能:19个Vue 项目的优化手段

前言 在前端开发领域&#xff0c;Vue.js 广泛应用于各种类型的项目中。然而&#xff0c;随着项目规模的扩大和用户需求的增加&#xff0c;性能优化的重要性愈发凸显。优化不仅可以提升用户体验&#xff0c;还能显著减少资源消耗&#xff0c;提高应用的响应速度和稳定性。 本文…

基于Java Springboot个人记账之财来财往微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

【maven-5】Maven 项目构建的生命周期:深入理解与应用

1. 生命周期是什么 ​在Maven出现之前&#xff0c;项目构建的生命周期就已经存在&#xff0c;软件开发人员每天都在对项目进行清理&#xff0c;编译&#xff0c;测试及部署。虽然大家都在不停地做构建工作&#xff0c;但公司和公司间&#xff0c;项目和项目间&#xff0c;往往…

LLamafactory API部署与使用异步方式 API 调用优化大模型推理效率

文章目录 背景介绍第三方大模型API 介绍LLamafactory 部署API大模型 API 调用工具类项目开源 背景介绍 第三方大模型API 目前&#xff0c;市面上有许多第三方大模型 API 服务提供商&#xff0c;通过 API 接口向用户提供多样化的服务。这些平台不仅能提供更多类别和类型的模型…

【Python网络爬虫笔记】6- 网络爬虫中的Requests库

一、概述 Requests 是一个用 Python 语言编写的、简洁且功能强大的 HTTP 库。它允许开发者方便地发送各种 HTTP 请求&#xff0c;如 GET、POST、PUT、DELETE 等&#xff0c;并且可以轻松地处理请求的响应。这个库在 Python 生态系统中被广泛使用&#xff0c;无论是简单的网页数…

【AI技术赋能有限元分析应用实践】Abaqus有限元分析到深度学习方法应用全过程——汽车刹车片热力耦合分析

目录 一、项目实现介绍**项目背景****项目目标****项目流程概述****技术融合****项目价值** 二、实现流程**Step 1: 分析问题构建方法&#xff0c;寻找主要分析目标&#xff0c;确定初步目标****Step 2: 使用 Abaqus 完成有限元仿真&#xff0c;后处理并保存数据为 odb 格式***…

【人工智能-科普】深度森林:传统机器学习与深度学习的创新结合

文章目录 深度森林:传统机器学习与深度学习的创新结合一、什么是深度森林?二、深度森林的工作原理1. **特征提取和转换**2. **多层级训练**3. **最终分类**三、深度森林的关键组成部分1. **森林层(Forest Layer)**2. **级联结构(Cascade Structure)**3. **特征增强(Feat…

Netty的内存池机制怎样设计的?

大家好&#xff0c;我是锋哥。今天分享关于【Netty的内存池机制怎样设计的&#xff1f;】面试题。希望对大家有帮助&#xff1b; Netty的内存池机制怎样设计的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Netty 的内存池机制设计是为了提高性能&…

Postman设置接口关联,实现参数化

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结果&#xff0c; 从而让后一个接口能正常执行&#xff0c;这…

七牛云成功保存但无法显示和访问{“error“:“download token not specified“}

在使用七牛云存储图片时&#xff0c;前端通过链接访问图片时遇到错误&#xff1a; {"error":"download token not specified"} 具体表现为&#xff1a; 后端通过 access_key 和 secret_key 生成了上传和下载的 Token。前端将域名与 res.key 拼接后生成图…

《实战OpenCV系列》专栏介绍

简介 本专栏由浅入深&#xff0c;详细介绍了使用OpenCV进行图像/视频处理的各方面知识&#xff0c;包括&#xff1a;图像显示、图像的数学运算、图像的裁剪与拼接、图像的像素操作、几何变换、直方图、图像滤波、色彩空间转换、边缘检测、形态学操作、模板匹配、视频处理、图像…

常用函数的使用错题汇总

#include <iostream> #include <fstream> #include <string>int main() {std::ifstream fin("example.txt"); // 创建 ifstream 对象并打开文件// 检查文件是否成功打开if (!fin) {std::cerr << "Error opening file!" << s…

曲面单值化定理

曲面单值化定理&#xff08;Uniformization Theorem&#xff09;是复分析、几何和拓扑学中的一个重要结果。它为紧致黎曼曲面提供了标准化的几何结构&#xff0c;是研究复几何和代数几何的基础。以下是对曲面单值化定理的详细介绍以及其应用场景。 曲面单值化定理的陈述 基本版…