设计模式学习笔记 - 开源实战三(下):借助Google Guava学习三大编程范式中的函数式编程

概述

现在主流的编程范式主要有三种,面向过程、面向对象和函数式编程。在理论部分,已经介绍了前面两种编程范式。本章再讲讲剩下的编程范式,函数式编程。

函数式编程并非是一个很新的东西,早在 50 年前就已经出现。近几年,函数式编程越来越被人关注,出现了很多新的函数式编程语言,比如 Clogure、Scala、Erlang 等。一些函数式编程语言也加入了很多特性、语法、类库类支持函数式编程,比如 Java、Python、Ruby、JavaScript 等。此外,Google Guava 也有对函数式编程的增强功能。

函数式编程因其编程的特性,仅在科学计算、数据处理、统计分析等领域,才能更好地发挥它的优势,所以个人觉得,它并不能完全替代更加通用的面向对象编程范式。但是,作为一种补充,它也有很大存在、发展和学习的意义。


到底什么是函数式编程?

函数式编程的英文是 Functional Programming。那到底什么是函数式编程呢?

函数式编程也没有一个严格的官方定义,所以,接下来就从特性上告诉你,什么是函数式编程。

严格来讲,函数式编程中的 “函数”,并不是指我们编程语言中的 “函数” 概念,而是指数学 “函数” 或者 “表达式”(比如,y=f(x)。不过,在编程实现时,对于数学 “函数” 或者 “表达式”,一般习惯性地将它们设计成函数。所以,如果不深究的话,函数式编程中的 “函数” 也可以理解为编程语言中的 “函数”。

每个编程范式都有自己独特的地方,这就是它们会被抽象出来作为一种范式的原因。面向对象编程的最大特点是:以类、对象作为组织代码的单元以及它的四大特性。面向过程编程语言的最大特点是:以函数作为组织代码的单元,数据与方法相分离。

函数式编程最独特的地方在于它的编程思想。函数式编程任务,程序可以用一系列数学函数或表达式的组合来表示。函数式编程是程序面向数学的更底层抽象,将计算过程描述为表达式。不过,这样说你肯定会有疑问,真的可以把任何程序都表示成一组数学表达式吗?

理论上是可以的。但是,并不是所有的程序都适合这么做。函数式编程有它自己适合的应用场景,比如开篇提到的科学计算、数据处理、数据处理、统计分析等。在这些领域,程序往往容易使用数学表达式来表示,比起非函数式编程,实现同样的功能,函数式编程可以 用很少的代码就能搞定。但是,对于强业务相关的大型业务系统开发来说,耗费精力地将它抽象成数学表达式,硬要用函数式编程来实现,显然是自讨苦吃。相反,在这种应用场景下,面向对象编程更加合适,写出来的代码更加可读、可维护。

刚刚讲的是函数式编程的编程思想,如果落实到编程实现,函数式编程跟面向过程编程一样,也是以函数作为组织代码的单元。不过,它跟面向过程编程的区别在于,它的函数是无状态的。

何为无状态?简单地讲,函数内部涉及的变量都是局部变量,不会像面向对象编程那样,共享类成员变量,也不会像面向过程编程那样,共享全局变量。函数的执行结果只与入参有关,跟其他任何外部变量无关。同样的入参,不管怎么执行,得到的结果是一样的。这实际上就是数学表达式或数学函数的基本要求。下面举个简单的例子来解释下:

// 有状态函数:执行结果依赖b的值是多少,即便入参相同,多次执行函数,函数的返回值有可能不同,因为b值有可能不同
int b
int increase(int a) {return a + b;
}// 无状态函数:执行结果不依赖任何外部变量,只要入参相同,不管执行多少次,函数的返回值就相同
int increase(int a, int b) {return a + b;
}

不同的编程范式之间并不是截然不同的,总有一些相同的编程规则。比如,不管是面向过程、面向对象还是函数式编程,它们都有变量、函数的概念,最顶层都要有 main 函数执行入口,来组装编程单元(类、函数等)。只不过,面向对象的编程单元是类或对象,面向过程的编程单元室函数,函数式编程的编程单元是无状态函数

Java 对函数式编程的支持

前面章节讲过,实现面向对象编程不一定非得使用面向对象编程语言。同理,实现函数式编程也不一定非得使用函数式编程语言。现在,很多面向对象编程语言,也提供了相应的语法、类库来支持函数式编程。

接下来,看下 Java 这种面向对象编程语言,对函数式编程的支持,加深一下你对函数式编程的理解。下面是一段非常典型地 Java 函数式编程的代码。

public class FPDemo {public static void main(String[] args) {Optional<Integer> result = Stream.of("f", "a", "hello").map(s -> s.length()).filter(l -> l <= 3).max((o1, o2) -> o1 - o2);System.out.println(result.get()); // 输出2}
}

这段代码的作用是从一组字符串数组中,过滤出长度小于等于 3 的字符串,并且求得这其中的最大长度。

如果你不了解 Java 函数式编程的语法,看了上面的代码或许会有些懵,主要的原因是 Java 为函数式编程引入了三个新的语法概念:StreamLambda 表达式函数接口(Function Interface)。

  • Stream 类用来支持通过 “.” 级联多个函数操作的代码编写方式;
  • 引入 Lambda 表达式的作用是简化代码编写;
  • 函数接口的作用是可以把函数包裹成函数接口,来实现把函数当做参数一样来使用(Java 不像 C 一样支持函数指针,可以把函数当做参数来使用)。

首先,看下 Stream 类

假设要计算这样一个表达式:(3-1)*2 + 5。如果按照普通的函数调用方式写出来,就是下面这个样子:

add(multiply(subtract(3, 1), 2), 5);

这样看起来,代码会比较难理解,换个更易懂的写法:

subtract(3, 1).multiply(2).add(5);

在 Java 中, “.” 用来表示某个对象的方法。为了支持上面这种级联调用方式,我们让每个函数都返回一个通用的类 型:Stream 类对象。在 Stream 类上的操作有两种:中间操作和终止操作。中间操作仍返回 Stream 类对象,而终止操作返回的是确定的值结果。

再看下前面的例子。我们对代码做了注释解释,如下所示。其中,mapfilter 是中间操作,返回 Stream 类对象,可以继续级联其他操作;max 是终止操作,返回的不是 Stream 类对象,无法继续往下级联处理了。

public class FPDemo {public static void main(String[] args) {Optional<Integer> result = Stream.of("f", "a", "hello") // of返回Stream<String>对象.map(s -> s.length()) // map返回Stream<Integer>对象.filter(l -> l <= 3) // filter返回Stream<Integer>对象.max((o1, o2) -> o1 - o2); // max 终止操作:返回Option<Integer>System.out.println(result.get()); // 输出2}
}

其次,再看下 Lambda 表达式

Java 引入 Lambda 表达式的主要作用是简化代码编写。实际上,我们也可以不用 Lambda 表达式来书写书中的例子。我们拿其中的 map 函数来举例说明下。

// Stream中map的定义:
public interface Stream<T> extends BaseStream<T, Stream<T>> {<R> Stream<R> map(Function<? super T, ? extends R> mapper);// 省略其他函数...
}// Stream中map的使用方法:
Stream.of("fo", "far", "hello").map(new Function<String, Integer>() {@Overridepublic Integer apply(String s) {return s.length();}
});// 用Lambda表达式简化后的写法:
Stream.of("fo", "far", "hello").map(s -> s.length());

Lambda 表达式语法不是学习的重点,这里只是稍微介绍下。

Lambda 表达式包含三部分:输入、函数体、输出。表示出来就是下面这个 样子:

(a, b) -> { 语句1; 语句2; ...; return 输出;} // a,b是输入参数

实际上,Lambda 表达式的写法非常灵活。刚刚给出的是标准写法,还有很多简化写法。比如,如果输入参数只有一个,可以省略 (),直接写成 a ->{...};如果没有入参,可以直接将输入和箭头都省略只保留函数体;如果函数体只有一个语句,可以将 {} 省略掉; 如果函数没有返回值, return 语句就可以不用写了。

如果把之前的例子中的 Lambda 表达式,全部替换为函数接口的实现方式,就是下面这样子的。代码是不是多了很多?

Optional<Integer> result = Stream.of("f", "a", "hello").map(s -> s.length()).filter(l -> l <= 3).max((o1, o2) -> o1 - o2);// 还原为函数接口的实现方式
Optional<Integer> result2 = Stream.of("fo", "far", "hello").map(new Function<String, Integer>() {@Overridepublic Integer apply(String s) {return s.length();}}).filter(new Predicate<Integer>() {@Overridepublic boolean test(Integer integer) {return integer <= 3;}}).max(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o1 - o2;}});

最后,看下函数接口

实际上,上面一段代码中的 FunctionPredicateComparator 都是函数接口。我们知道,C 语言支持函数指针,它可以把函数直接当变量来使用。但是,Java 没有函数指针这样的语法。所以,它通过接口函数,将函数包裹在接口中,当做变量来使用。

实际上,函数接口就是接口。不过,它有自己特别的地方,那就是要求只包含一个未实现的方法。因为,只有这样,Lambda 表达式才能明确知道匹配的是哪个接口。如果有两个为实现的方式,并且接口入参、返回值都一样,那 Java 在翻译 Lambda 表达式时,就不知道表达式对应哪个方法了。

我们把 Java 提供的 FunctionPredicate 这两个函数接口的源码,摘抄过来贴到了下面,你可以看下。

@FunctionalInterface
public interface Function<T, R> {R apply(T t);default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}static <T> Function<T, T> identity() {return t -> t;}
}@FunctionalInterface
public interface Predicate<T> {boolean test(T t);default Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}default Predicate<T> negate() {return (t) -> !test(t);}default Predicate<T> or(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) || other.test(t);}static <T> Predicate<T> isEqual(Object targetRef) {return (null == targetRef)? Objects::isNull: object -> targetRef.equals(object);}
}

以上讲的就是 Java 对函数式编程的语法支持。

Guava 对函数式编程的增强

如果你是 Google Guava 的设计者,对于 Java 函数式编程,Google Guava 还能做些什么呢?

Google Guava 并没有提供太多函数式编程的支持,仅封装了几个遍历集合操作的接口,代码如下所示:

Iterables.transform(Iterable, Function);
Iterables.transform(Iterator, Function);
Collections.transform(Collection, Function);
List.transform(List, Function);
Maps.transform(Map, Function);
Multimaps.transform(Multimap, Function);
...Iterables.filter(Iterable, Predicate);
Iterators.filter(Iterable, Predicate);
Collections2.filter(Collection, Predicate);
...

从 Google 的 Wiki 中,我们发现,Google 对于函数式编程的使用还是很谨慎的,认为过度使用函数式编程,会导致代码的可读性变差,强调不滥用。所以,在函数式编程方面,Google Guava 并没有提供太多的支持。

之所以对遍历集合操作做了优化,主要是因为函数式编程一个重要的应用场景就是遍历集合。如果不适用函数式编程,我们只能 for 循环,一个一个的处理集合中的属性。使用函数式编程,可以大大简化遍历集合操作的代码编写,一行代码就能搞定,而且在可读性方面也没有太大的损失。

总结

本章,讲了一下三大编程范式中的最后一个,函数式编程。尽管越来越多的编程语言开始支持函数式编程,但我个人觉得,它只能是其他编程语范式的补充,用在一些特殊的领域发挥它的特殊作用,没法完全替代面向对象、面向过程编程范式。

关于什么是函数式编程,实际上不是很好理解。函数式编程中的 “函数”,并不是只我们编程语言中的 “函数” 概念,而是数学中的 “函数” 或者 “表达式” 概念。函数式编程认为,程序可以用一系列数学函数或者表达式的组合来表示。

具体到编程实现,函数式编程以无状态函数作为组织代码的单元。函数的执行结果只与入参有关,跟其他任何外部变量无关。同样的入参,不管怎么执行,得到的结果都是一样的。

具体到 Java 语言,它提供了三个语法机制来支持函数式编程。它们分别是 Stream 类、Lambda 表达式和函数接口。Google Guava 对函数式编程的一个重要应用场景,遍历集合,做了优化,但并没有太多的支持,并且强调,不要为了节省代码行数,滥用函数式编程,导致代码可读性变差。

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

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

相关文章

全量知识系统 程序详细设计 之“因果关系模型”(QA百度搜索)

Q1. 今天聊聊全量知识系统&#xff08;简称“全知系统”&#xff09;中 对“因果”的理解。&#xff08;地位、意义和作用&#xff09; 在全量知识系统&#xff08;全知系统&#xff09;中&#xff0c;“因果”的理解占据着核心地位&#xff0c;其意义和作用不容忽视。以下是对…

AI安全之问:我们的智能助手真的安全吗?

在我们日益依赖人工智能来撰写文档、编写程序代码、甚至创作艺术作品的今天&#xff0c;我们是否曾经想过这些智能系统可能面临的被恶意操纵的风险&#xff1f; 分享几个网站 GPT-3.5研究测试&#xff1a; https://hujiaoai.cn GPT-4研究测试&#xff1a; https://higpt4.cn…

MKS 质量MFC流量控制器原理及应用课件PPT

MKS 质量MFC流量控制器原理及应用课件PPT

Git | Git基本命令

Git | Git基本操作 文章目录 Git | Git基本操作一、创建Git本地仓库1、创建Git仓库2、配置Git3、理解工作区、暂存区、版本库关系 二、添加、修改与查看添加文件查看历史提交记录 修改文件查看.git文件 三、版本回退版本回退撤销修改尚未add已add但还未commit已add并commit 删除…

安信可 ESP_01SWIFI模块的使用 (电脑通过usb转tll模块连接wifi模块进行调试)

一&#xff1a;需要用到的模块 &#xff08;1&#xff09;安信可的ESP_01wifi模块 ESP-01是深圳安信可科技基于ESP8266芯片开发的串口wifi模块&#xff0c;模组集成了透传功能&#xff0c;即买即用&#xff0c;支持串口指令集&#xff0c;用户通过串口即可实现网络访问…

STM32相关手册

目录 数据手册 STM32F103C8T6数据手册 STM32F429IGT6数据手册 参考手册 STM32F103C8T6参考手册 STM32F429IGT6参考手册 数据手册 STM32F103C8T6数据手册 链接&#xff1a;https://url80.ctfile.com/d/38076880-60948910-e7c11b?p9868 密码&#xff1a;9868 STM32F429IGT6数…

List的介绍

前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&#x1…

ASP.Net MVC 登录页面实现RSA非对称加密

一、什么是RSA非对称加密 RSA是1977年由罗纳德李维斯特&#xff08;Ron Rivest&#xff09;、阿迪萨莫尔&#xff08;Adi Shamir&#xff09;和伦纳德阿德曼&#xff08;Leonard Adleman&#xff09;一起提出的。 RSA算法是一种非对称加密算法&#xff0c;与对称加密算法不同…

广州大学2023-2024学年第一学期《计算机网络》A卷

选择题&#xff1a; 考得和往年试卷&#xff08;往年试卷&#xff0c;我都做了&#xff0c;但是都是些09年、10年、11年、12年、13年的试卷&#xff0c;离现在最近的都是17-28年的。选择填空题的参考性已经不高了&#xff0c;但是大题的题型还是差不多的。&#xff09;大相径庭…

Java之继承和多态

继承 一 、继承相关基础 1.1 为什么需要继承 先看下代码 // Dog.java public class Dog { string name ; int age ; float weight ; public void eat (){ System . out . println ( name " 正在吃饭 " ); } public void sleep (){ System . ou…

Spring Cloud Feign面试题

Spring Cloud Feign面试题 1. Feign基础概念1.1 什么是Feign&#xff1f;1.2 Feign的主要用途是什么&#xff1f;1.3 Feign和OpenFeign有何区别&#xff1f;1.4 Feign和Ribbon以及RestTemplate的区别是什么&#xff1f; 2. Feign的使用和配置2.1 如何创建一个Feign客户端&#…

如何在本地创建一个新的Git仓库?

文章目录 **步骤一&#xff1a;开启项目之旅****步骤二&#xff1a;启动Git引擎****步骤三&#xff1a;验证仓库初始化情况****步骤四&#xff1a;填充项目内容****步骤五&#xff1a;保存更改——初次提交****&#xff08;可选步骤六&#xff1a;关联远程仓库并推送&#xff0…

基于Spring Cloud Alibaba+Skywalking的分布式链路追踪设计

胡弦&#xff0c;视频号2023年度优秀创作者&#xff0c;互联网大厂P8技术专家&#xff0c;Spring Cloud Alibaba微服务架构实战派(上下册)和RocketMQ消息中间件实战派(上下册)的作者&#xff0c;资深架构师&#xff0c;技术负责人&#xff0c;极客时间训练营讲师&#xff0c;四…

双向链表-(增删减改)

声明 单链表&#xff08;增删减改&#xff09;单链表实现通讯录项目链表的专用题型-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137722729 双链表和单链表就是异曲同工 链表的分类 这里我们主要讲解的是不带头的单向不循环链表&#xff0c;在题型解析里面…

将Python机器学习模型集成到C++ Qt客户端应用程序中|Qt调用python详解

0、前言 有几个不同的选项可以将你的Python机器学习模型集成到你的C Qt客户端应用程序中。以下是一些可能的解决方案&#xff1a; 创建API&#xff1a; 将你的机器学习模型部署为一个API服务。你可以使用像Flask这样的轻量级Web框架来创建一个简单的HTTP服务。这样&#xff0…

关于文件上传的前后端优化

此前做过单文件、多文件上传和大文件分片上传&#xff0c;虽然功能有了&#xff0c;效果也还可以。不过最近重新思考一下&#xff0c;突然注意到一个重要的事情&#xff0c;大文件上传中&#xff0c;系统做了很多无用功&#xff0c;导致效率太低&#xff01; 怎么说呢&#xff…

JAVA高阶私房菜:JVM虚拟机核心概念及参数微调实验

目录 基础快速掌握 什么是JVM虚拟机 JVM的的实现 操作系统-虚拟机-JRE-JDK的关系 生产环境部署JDK还是JRE JVM内存组成部分和堆空间分布 内存组成 堆空间内存分布 内存分布 堆空间分配 JVM堆空间垃圾回收流程及JVM参数 垃圾回收流程 JVM参数分类 JVM参数格式分类 …

浅识数据结构之时间复杂度

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 文章目录 前言一. 时间复杂度1.1 时间复杂度的概念1.2 时间复杂度如何计算1.3 时间复杂度如何表…

XSS-跨站脚本攻击 漏洞详解

一、初识XSS 1、什么是XSS XSS全称跨站脚本(Cross Site Scripting)&#xff0c;为避免与层叠样式表(Cascading Style Sheets, CSS)的缩写混淆&#xff0c;故缩写为XSS。这是一种将任意 Javascript 代码插入到其他Web用户页面里执行以达到攻击目的的漏洞。攻击者利用浏览器的动…

【已解决简单好用】notepad++怎么设置中文

打开Notepad软件。点击软件界面顶部菜单栏中的“Settings”选项。在下拉菜单中选择“Preferences”进行语言设置。在打开的设置窗口中&#xff0c;找到“General”选项。在“General”选项中&#xff0c;找到“Localization”&#xff08;界面语言&#xff09;项。在下拉菜单中…