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

函数式编程要点

一、介绍和入门

1、函数式编程介绍

函数式编程思想在于简化冗余代码,将函数式接口(接口中只有一个未实现的方法)作为最高抽象。它关注的是入参、返回、语法结构,是一种极简的编程方式。相对面向过程和面向对象编程,函数编程是面向数据处理和计算过程的抽象与组合

//在java中可以使用@FunctionalInterface标识函数式接口,其作用是检验接口中是否只有一个未实现的方法,如
@FunctionalInterface
public interface Function<T, R> {R apply(T var1);
}

2、java中的函数式编程接口

在java.util.function包中,提供了大量规范的函数式接口,以避免过多使用自定义函数式接口

主要分为5类

  1. Consumer结尾:接收入参,没有返回
  2. Supplier结尾:没有入参,有返回
  3. Function结尾:接收入参,有返回
  4. Predicate结尾:断言,返回boolean类型参数
  5. Operator结尾:入参和返回参数类型相同
Consumer    接受一个参数,无返回值
BiConsumer    接受两个参数,无返回值
DoubleConsumer    接受一个double类型的参数,无返回值
IntConsumer    接受一个int类型的参数,无返回值
LongConsumer    接受一个long类型的参数,无返回值
ObjDoubleConsumer    接受一个自定义类型的参数和一个double类型的参数,无返回值
ObjIntConsumer    接受一个自定义类型的参数和一个int类型的参数,无返回值
ObjLongConsumer    接受一个自定义类型的参数和一个long类型的参数,无返回值
Function    接受一个参数,有返回值
BiFunction    接受两个参数,有返回值
DoubleFunction    接受一个double类型的参数,有返回值
IntFunction    接受一个int类型的参数,有返回值
LongFunction    接受一个long类型的参数,有返回值
IntToDoubleFunction    接受一个int类型的参数,返回一个double类型的值
IntToLongFunction    接受一个int类型的参数,返回一个long类型的值
LongToDoubleFunction    接受一个long类型的参数,返回一个double类型的值
LongToIntFunction    接受一个long类型的参数,返回一个int类型的值
DoubleToIntFunction    接受一个double类型的参数,返回一个int类型的值
DoubleToLongFunction    接受一个double类型的参数,返回一个long类型的值
ToDoubleBiFunction    接受两个参数,返回一个double类型的值
ToDoubleFunction    接受一个参数,返回一个double类型的值
ToIntBiFunction    接受两个参数,返回一个int类型的值
ToIntFunction    接受一个参数,返回一个int类型的值
ToLongBiFunction    接受两个参数,返回一个long类型的值
ToLongFunction    接受一个参数,返回一个long类型的值
BinaryOperator    接受两个相同类型的参数,返回一个相同类型的值
DoubleBinaryOperator    接受两个double类型的参数,返回一个double类型的值
DoubleUnaryOperator    接受一个double类型的参数,返回一个double类型的值
IntBinaryOperator    接受两个int类型的参数,返回一个int类型的值
IntUnaryOperator    接受一个int类型的参数,返回一个int类型的值
LongBinaryOperator    接受两个long类型的参数,返回一个long类型的值
LongUnaryOperator    接受一个long类型的参数,返回一个long类型的值
UnaryOperator    接受一个参数,返回一个相同类型的值
Predicate    接受一个参数,返回一个boolean类型的值
BiPredicate    接受两个参数,返回一个boolean类型的值
DoublePredicate    接受一个double类型的参数,返回一个boolean类型的值
IntPredicate    接受一个int类型的参数,返回一个boolean类型的值
LongPredicate    接受一个long类型的参数,返回一个boolean类型的值
Supplier    无参数,有返回值
BooleanSupplier    无参数,返回一个boolean类型的值
DoubleSupplier    无参数,返回一个double类型的值
IntSupplier    无参数,返回一个int类型的值
LongSupplier    无参数,返回一个long类型的值

 3、函数式编程配合Lambda表达式的简单实践

    public static void main(String[] args) {//匿名内部类Function<Integer, String> function1 = new Function<Integer, String>() {@Overridepublic String apply(Integer i) {return String.valueOf(i);}};System.out.println(function1.apply(1));//简化Function<Integer, String> function2 = i -> String.valueOf(i);System.out.println(function2.apply(1));//再简化Function<Integer, String> function3 = String::valueOf;System.out.println(function3.apply(1));//多个参数BiFunction<Integer, Integer, Integer> biFunction = (i, j) -> i + j;System.out.println(biFunction.apply(0, 1));//断言Predicate<Integer> predicate = i -> i == 1;//判断是否满足条件System.out.println(predicate.test(2));//判断是否不满足条件System.out.println(predicate.negate().test(2));}

4、函数式编程特点

  1. 不可变性:在函数式编程中,数据是不可变的。这意味着一旦一个值被定义,它就不能被改变。这种不可变性有助于减少程序中的错误和副作用,因为你不必担心在某个地方意外地修改了数据。
  2. 无副作用:函数式编程中的函数通常没有副作用,即它们不会修改全局状态或产生其他外部影响。这使得函数更加可预测和可测试,因为你可以确信给定相同的输入,函数将始终产生相同的输出。
  3. 函数的纯粹性:在函数式编程中,函数是纯粹的,即它们的结果只取决于其输入参数,而不受外部状态的影响。这有助于简化代码和推理过程,因为你不必考虑函数之外的任何状态或变量。
  4. 高阶函数和闭包:函数式编程鼓励使用高阶函数(接受其他函数作为参数或返回函数的函数)和闭包(捕获其外部环境的函数)。这些特性使得函数更加灵活和可组合,可以方便地构建复杂的计算过程和数据结构。
  5. 抽象和组合:函数式编程强调通过抽象和组合来构建复杂的程序。你可以将计算过程分解为一系列简单的函数,然后将它们组合在一起以实现更复杂的功能。这种方式有助于提高代码的可读性、可维护性和可重用性

 5、函数式编程缺点

  1. 学习成本较高:函数式编程的思维方式与传统的命令式编程有很大的不同。它强调不可变性、无副作用和函数的纯粹性,这可能需要开发者花费更多的时间和精力来学习和适应。此外,函数式编程通常使用高阶函数和闭包等高级特性,这也增加了学习的难度。
  2. 性能问题:虽然函数式编程可以提高代码的可读性和可维护性,但在某些情况下,它可能不如命令式编程性能好。这主要是因为函数式编程强调不可变性和无副作用,这可能导致大量的数据复制和函数调用,从而增加了内存消耗和运行时间。然而,现代的函数式编程语言和编译器已经采用了许多优化技术来缓解这个问题。
  3. 调试困难:由于函数式编程强调函数的纯粹性和无副作用,这使得在调试过程中很难跟踪和定位错误。在命令式编程中,你可以通过设置断点、打印日志等方式来跟踪程序的执行过程。但在函数式编程中,由于函数之间没有共享的状态,因此很难确定错误发生的位置和原因。
  4. 与现有系统的兼容性:函数式编程与传统的命令式编程在思维方式和编程风格上有很大的不同。这可能导致在将函数式编程与现有的命令式系统进行集成时出现兼容性问题。例如,函数式编程强调不可变性和无副作用,这可能与现有系统中的某些特性和库不兼容。
  5. 可能的代码可读性问题:虽然函数式编程可以提高代码的简洁性和可维护性,但过度使用某些函数式编程特性(如无参风格、大量方法的组合)可能会影响代码的可读性。这使得其他开发者在阅读和理解代码时可能会遇到困难。

 二、进阶

1、Java8 Stream Api(流操作)

代码示例

    public static void main(String[] args) {  // 创建一个整数列表  List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  // 使用Stream API进行过滤、映射和聚合操作  List<Integer> result = numbers.stream() // 创建流  .filter(n -> n % 2 == 0) // 过滤偶数  .map(n -> n * n) // 映射:将每个数平方  .collect(Collectors.toList()); // 聚合:将结果收集到一个新的列表中  // 输出结果[4, 16, 36, 64, 100]System.out.println(result);  } 

Stream Api特点

  1. 流水线操作:Stream API 的操作可以像流水线一样串联起来,形成一个大的操作链。每个操作都可以看作是对数据源的一个转换或处理步骤。
  2. 内部迭代:Stream API 采用了内部迭代的方式,具体的迭代过程则由流自行完成。这种方式简化了代码。                                               
  3. 不可变性:Stream API 中的流是不可变的。一旦一个流被创建,就不能再修改它的数据源或操作。每次对流进行修改或转换,都会返回一个新的流对象。这种不可变性保证了数据的安全性,避免了在并发环境下的数据竞争问题。
  4. 惰性求值:Stream API 采用了惰性求值的方式。这意味着在中间处理过程中,流只是对操作进行了记录,并不会立即执行。只有当执行到终止操作时,才会进行实际的计算。这种方式可以提高性能,避免不必要的计算。
  5. 丰富的操作:Stream Api简化了Java开发中数据运算、聚合、分组、转换、比较、过滤、遍历、排序等操作,通过使用Lambda表达式和函数式编程,可以简化冗余复杂的代码,这些操作可以方便地满足各种数据处理需求。

Stream Api使用注意点

  1. 流的操作默认多线程并行执行,对于少量数据操作其性能可能不如传统写法,此外在并发编程中流处理的中间操作过程中不应该介入有状态数据,或将流中间操作的结果输出。
  2. 流操作并不是每次对一个中间过程进行处理,而是每次走完整个处理链。

 Stream Api的中间操作和终端操作

  1. 中间操作
    中间操作会返回一个新的流,并允许链式操作。这些操作不会立即执行,而是惰性求值的,即只有当终端操作被触发时,中间操作才会被应用到数据源上。常见的中间操作有:

    • filter(Predicate<? super T> predicate)
      • 过滤流中的元素,只保留满足给定谓词的元素。
    • map(Function<? super T, ? extends R> mapper)
      • 将流中的每个元素映射成一个新的元素,通常用于转换数据类型或提取信息。
    • flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
      • 将流中的每个元素映射成一个新的流,然后将这些流合并成一个流。这通常用于将多个流合并成一个,或将嵌套的数据结构展平。
    • limit(long maxSize)
      • 截断流,使其只包含前 maxSize 个元素。这是一种短路操作,即一旦达到指定的大小,就会停止处理更多的元素。
    • skip(long n)
      • 跳过流中的前 n 个元素。如果流中的元素不足 n 个,则返回一个空流。
    • distinct()
      • 返回一个包含所有不同元素的新流。这是通过流的元素的 equals() 方法进行比较的。
    • sorted() 或 sorted(Comparator<? super T> comparator)
      • 返回一个新流,其中的元素按自然顺序排序,或者根据提供的 Comparator 进行排序。
    • peek(Consumer<? super T> action)
      • 对流中的每个元素执行给定的操作,并返回一个新的流包含原始元素。这主要用于调试目的,因为它允许你查看流中的元素而不改变它们。
    • mapToInt(ToIntFunction<? super T> mapper)mapToLong(ToLongFunction<? super T> mapper)mapToDouble(ToDoubleFunction<? super T> mapper)
      • 这些操作将流中的元素映射成基本数据类型(int, long, double)的流,如 IntStreamLongStreamDoubleStream。这对于需要进行数值计算的情况非常有用。
  2. 终端操作
    终端操作会触发实际的计算,并关闭流。流只能被遍历一次,因此终端操作之后,流就不能再被使用了。常见的终端操作有:

    • forEach(Consumer<? super T> action)
      • 对流中的每个元素执行给定的操作。这通常用于遍历流并执行某种副作用。
    • collect(Collector<? super T, A, R> collector)
      • 使用提供的Collector对流中的元素进行归约操作,并将结果收集到一个目标对象中,通常是集合或其他数据结构。
    • toArray()
      • 将流中的元素收集到一个数组中。这个方法有两种形式:无参的toArray(),它返回一个Object[],以及toArray(IntFunction<A[]> generator),它允许你指定数组的类型和大小。
    • reduce(T identity, BinaryOperator<T> accumulator)
      • 使用给定的累积函数对流中的元素进行归约操作,并返回归约的结果。identity是归约的初始值。
    • min(Comparator<? super T> comparator) 和 max(Comparator<? super T> comparator)
      • 根据提供的比较器返回流中的最小或最大元素。返回的是一个Optional<T>,表示最小或最大元素可能存在也可能不存在。
    • count()
      • 返回流中的元素数量。这是一个长整型值,可以处理比Integer.MAX_VALUE更大的流。
    • anyMatch(Predicate<? super T> predicate)
      • 判断流中是否有任何元素匹配给定的谓词。如果有一个元素满足条件,就返回true
    • allMatch(Predicate<? super T> predicate)
      • 判断流中的所有元素是否都匹配给定的谓词。只有当所有元素都满足条件时,才返回true
    • noneMatch(Predicate<? super T> predicate)
      • 判断流中是否没有任何元素匹配给定的谓词。如果所有元素都不满足条件,就返回true
    • findFirst()
      • 返回流中的第一个元素。返回的是一个Optional<T>,表示第一个元素可能存在也可能不存在。
    • findAny()
      • 返回流中的任意一个元素。在并行流中,这个方法可能会更高效,因为它允许从任何部分获取元素。返回的也是一个Optional<T>

这两种操作类型一起构成了 Stream API 强大的数据处理和分析能力。中间操作允许我们构建复杂的查询和转换管道,而终端操作则负责触发实际的计算和结果提取。

本文引用

函数式编程有哪些好处?_函数式编程语言有哪些_Js函数式编程好处 - 腾讯云开发者社区 - 腾讯云

Stream流API总结_stream流常用api-CSDN博客

 java Stream类的 超全API及实战总结_java stream api-CSDN博客

在Stream流中添加中间操作_stream添加元素-CSDN博客

在Stream上添加终端操作_tounmodifiableset-CSDN博客

相关文章:

  • N-144基于微信小程序在线订餐系统
  • 嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第五天-ARM Linux编程之file_operations详解 (物联技术666)
  • C#既然数组长度不可改变,那么如何动态调整集合类型数组大小,以便添加或删除元素?
  • KingSCADA实现按钮点击效果
  • HCIA-HarmonyOS设备开发认证V2.0-轻量系统内核内存管理-动态内存
  • 在PyTorch中,如何查看深度学习模型的每一层结构?
  • [高并发] - 1.高并发综述
  • 代码随想录算法训练营第三十四天|860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球
  • 有了NULL,为什么C++还需要nullptr?
  • Educational Codeforces Round 135 (Rated for Div. 2)C. Digital Logarithm(思维)
  • 书生浦语-模型微调
  • 用HTML和CSS打造跨年烟花秀视觉盛宴
  • 新的风口:继ChatGPT热潮后,OpenAI又推出视频生成新浪潮
  • 【AIGC】Stable Diffusion介绍
  • nginx upstream server主动健康监测模块添加https检测功能
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CentOS6 编译安装 redis-3.2.3
  • Docker 笔记(2):Dockerfile
  • flutter的key在widget list的作用以及必要性
  • isset在php5.6-和php7.0+的一些差异
  • MQ框架的比较
  • socket.io+express实现聊天室的思考(三)
  • SQLServer之创建数据库快照
  • v-if和v-for连用出现的问题
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 入门到放弃node系列之Hello Word篇
  • 手机端车牌号码键盘的vue组件
  • 听说你叫Java(二)–Servlet请求
  • 【云吞铺子】性能抖动剖析(二)
  • zabbix3.2监控linux磁盘IO
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • $jQuery 重写Alert样式方法
  • (39)STM32——FLASH闪存
  • (42)STM32——LCD显示屏实验笔记
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (二)windows配置JDK环境
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (剑指Offer)面试题34:丑数
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (原)本想说脏话,奈何已放下
  • (转)可以带来幸福的一本书
  • .dwp和.webpart的区别
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET/C# 的字符串暂存池
  • .NET开源快速、强大、免费的电子表格组件
  • /var/log/cvslog 太大
  • @RequestMapping用法详解
  • [ 渗透工具篇 ] 一篇文章让你掌握神奇的shuize -- 信息收集自动化工具
  • [AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯
  • [ARC066F]Contest with Drinks Hard
  • [BZOJ1010] [HNOI2008] 玩具装箱toy (斜率优化)