初识Java 11-2 函数式编程

目录

高阶函数

闭包

函数组合

柯里化和部分求值


本笔记参考自: 《On Java 中文版》


高阶函数

||| 高阶函数的定义:一个能接受函数作为参数或能把函数当返回值的函数。

        把函数当返回值的情况:

import java.util.function.Function;interface FuncSS extends Function<String, String> {
}public class ProduceFunction {static FuncSS produce() {return s -> s.toLowerCase();}public static void main(String[] args) {FuncSS f = produce();System.out.println(f.apply("ABCDEF"));}
}

        程序执行的结果是:

        这里的produce()就是高阶函数。

  • 使用继承,可以为接口创建别名;
  • 使用lambda表达式,可以在方法中创建并返回一个函数。

        要接受并使用函数,方法必须在其函数列表中正确描述函数类型(把函数当作参数):

import java.util.function.Function;class One {
}class Two {
}public class ConsumeFunction {static Two consume(Function<One, Two> onetwo) {return onetwo.apply(new One());}public static void main(String[] args) {Two two = consume(one -> new Two());}
}

        同时,我们也可以通过所接受的函数生成一个新的函数:

import java.util.function.Function;class I {@Overridepublic String toString() {return "类I";}
}class O {@Overridepublic String toString() {return "类O";}
}public class TransformFunction {static Function<I, O> transform(Function<I, O> in) {return in.andThen(o -> {System.out.println(o);return o;});}public static void main(String[] args) {Function<I, O> f2 = transform(i -> {System.out.println(i);return new O();});O o = f2.apply(new I());}
}

        程序执行的结果是:

        这里,transform()生成了一个与传入函数签名相同的函数,但这是可以按照需要进行更改的。

        在transform()方法内部调用了Function接口中的andThen()方法,这个方法专门为操作函数设计。andThen()会在in函数调用之后调用(与之相对的,还有一个compose()方法,会在in函数之前调用)。

        transform()最终传出了一个新函数,这个函数结合了in的动作和andThen()参数的动作。

闭包

        若一个lambda表达式使用了其作用域之外的变量,那么在返回该函数时,会发生什么?也就是说,当我们调用这个函数时,函数所引用的“外部”变量会变成什么?若语言能够解决这一问题,就说这门语言支持闭包(又称支持语法作用域)。

        Java 8提供了有限但可用的闭包支持。下面的例子中,函数会访问一个对象字段和一个方法参数:

import java.util.function.IntSupplier;public class Closure1 {int i;IntSupplier makeFun(int x) {return () -> x + i++;}
}

        这里还需要提到第二个概念,变量捕获。变量捕获是指在一个方法中定义的变量可以访问到另一个方法中的同名变量。这也被称为外部变量。这一概念主要是为了支持内部类访问外部类的成员变量。在上述例子中,makeFun()捕获了变量i

        此时,i是一个对象中的变量,在我们调用makeFun()后,该对象可能还存在。另外,若对同一个对象调用多次makeFun(),最终将会有多个函数共享同样的i的储存空间:

import java.util.function.IntSupplier;public class SharedStorage {public static void main(String[] args) {Closure1 c1 = new Closure1();IntSupplier f1 = c1.makeFun(0);IntSupplier f2 = c1.makeFun(0);IntSupplier f3 = c1.makeFun(0);System.out.println(f1.getAsInt());System.out.println(f2.getAsInt());System.out.println(f3.getAsInt());}
}

        程序执行的结果是:

        若i是makeFun()中的局部变量时,情况就变了。因为一旦makeFun()执行完毕,i就会被回收。但此时依旧可以编译:

import java.util.function.IntSupplier;public class Closure2 {IntSupplier makeFun(int x) {int i = 0;return () -> x + i;}
}

        在这里,makeFun()返回的IntSupplier就是在ix上构建的闭包,因此调用函数时,两个变量都会有效。但若在这里对i进行像i++这样的操作,就会引发编译错误:

        编译器提示我们需要将x和i标记为最终变量,这样我们就无法对任何变量进行增加操作了:

import java.util.function.IntSupplier;public class Closure4 {IntSupplier makeFun(final int x) {final int i = 0;return () -> x + i;}
}

        当然,在上述这个例子中即使没有final,代码依旧可以正常工作。这就体现了“实际上的最终变量”这一术语,这个术语是为Java 8创建的,其意思是,即使没有显式声明最终变量,但仍然可以用最终变量的方式来对待一个变量——只要不修改它即可。

        另外,即使在返回时的lambda表达式中没有修改变量,而在方法的其他地方进行了修改,依旧会引发报错:

        所谓的“实际上的最终变量”,要求我们不对这些变量进行修改。当然,实际上我们可以这样修改Closure5.java中的问题:在闭包中使用xi之前,对其进行赋值:

import java.util.function.IntSupplier;public class Closure6 {IntSupplier makeFun(int x) {int i = 0;i++;x++;final int iFinal = i;final int xFinal = x;return () -> xFinal + iFinal;}
}

    iFinalxFinal在赋值后没有进行修改,所以这里实际上并不需要final修饰。

        另外,即使使用的是引用,编译器也会看出问题:

        不过倒是可以使用List

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;public class Closure8 {Supplier<List<Integer>> makeFun() {final List<Integer> ai = new ArrayList<>();ai.add(1);return () -> ai;}public static void main(String[] args) {Closure8 c8 = new Closure8();List<Integer>l1 = c8.makeFun().get(),l2 = c8.makeFun().get();System.out.println(l1);System.out.println(l2);l1.add(42);l2.add(96);System.out.println(l1);System.out.println(l2);}
}

        程序执行的结果是:

        这次的修改成功了。因为每次调用makeFun()时,都会创建并返回一个全新的ArrayList。这意味着没有变量是被共享的,每个生成的闭包都有自己单独的ArrayList,不会互相干扰。

    对上述示例而言,即使引用ai没有被final修饰也没有问题。对引用使用final,只是保证这个对象引用本身不会被重新赋值。

        若只修改所指对象的内容,Java是可以接受的,前提是没有其他人获得该对象的引用。否则就意味着不止一个实体可以修改同一个对象,这会造成混乱。

        现在再看Closure1.java,在这里i的修改没有引发报错:

理由显而易见,因为i是外围类的成员,即使它不是最终变量,或“实际上的最终变量”。

        注意:应该考虑的是,lambda表达式捕获的变量是“实际上的最终变量”。若变量是某个对象中的一个字段,因为这个字段有独立的生命周期,所以即使不通过特殊的捕获,在lambda表达式调用之后,这个变量依旧会存在。

内部类作为闭包

        可以通过匿名内部类来实现上述示例:

import java.util.function.IntSupplier;public class AnonymousClosure {IntSupplier makeFun(int x) {int i = 0;// 同样不支持这种语句:// i++;// x++;return new IntSupplier() {@Overridepublic int getAsInt() {return x + i;}};}
}

        只要有内部类,就会存在闭包。在Java 8之前,内部类只能调用显式的最终变量。但Java 8放宽了这一规则,现在内部类可以使用“实际上的最终变量”。

函数组合

||| 函数组合:将多个函数结合使用,以创建新的函数。

        函数组合通常被认为是函数式编程的一部分,之前使用andThen()方法的函数就是一个例子。除此之外,java.util.function中的一些接口也有支持函数组合的方法,这里介绍一些常见的方法:

方法作用
andThen(argument)先执行原始操作,再执行参数操作
compose(argument)先执行参数操作,再执行原始操作
and(argument)对原始谓词和参数谓词执行短路逻辑与(AND)计算
or(argument)对原始谓词和参数谓词执行短路逻辑或(OR)计算
negate()所得谓语为该谓语的逻辑取反

【例子1:Functioncompose()andThen()

import java.util.function.Function;public class FunctionComposition {static Function<String, String> f1 = s -> {System.out.println(s);return s.replace('A', '_');},f2 = s -> s.substring(3),f3 = s -> s.toLowerCase(),f4 = f1.compose(f2).andThen(f3);public static void main(String[] args) {System.out.println(f4.apply("GO AFTER ALL"));}
}

        程序执行的结果是:

        程序会按照 f2f1f3f4 的顺序进行程序执行。这里的重点在于,创建的新函数f4几乎可以像其他任何函数一样使用apply()

    当f1得到String时,因为compose(f2)的存在,f2会在f1之前被调用。

【例子2:Predicate(谓词)的逻辑运算】

import java.util.function.Predicate;
import java.util.stream.Stream;public class PredicateComposition {static Predicate<String>p1 = s -> s.contains(("bar")),p2 = s -> s.length() < 5,p3 = s -> s.contains("foo"),p4 = p1.negate().and(p2).or(p3);public static void main(String[] args) {Stream.of("bar", "foobar", "foobaz", "fongopuckey").filter(p4).forEach(System.out::println);}
}

        程序执行的结果是:

        p4接受了所有的谓词,并将其组合成了一个更加复杂的谓词,这个组合可以理解成:若这个String不包含"bar",②并且长度小于5,③其中包含"foo",则结果为true

        上述程序使用了一个String对象的“流”。其中filter()会对每个对象进行筛选,决定它们的去留。而forEach()会将留下的对象交给println方法引用。

柯里化和部分求值

||| 柯里化:将一个接收多个参数的函数转变为一系列只接受一个参数的函数。

【例子】

import java.util.function.Function;public class CurryingAndPartials {// 未柯里化:static String uncurried(String a, String b) {return a + b;}public static void main(String[] args) {System.out.println(uncurried("Hi ", "Ho"));// 柯里化函数:Function<String, Function<String, String>>sum = a -> b -> a + b; // 在这条语句中,Function中包含了一个FunctionFunction<String, String> // 通过柯里化提供了一个参数,由此来创建一个新函数hi = sum.apply("Hi ");System.out.println(hi.apply("Ho"));// 应用Function<String, String> sumHi = sum.apply("Hup ");System.out.println(sumHi.apply("Ho"));System.out.println(sumHi.apply("Hei"));}
}

        程序执行的结果是:

        柯里化的目的是通过提供一个参数来创建一个新函数,以此获得一个“参数化函数”和剩下的“自由参数”。在这里,有两个参数的函数变为了一个单参数的函数。

        还可以再添一层,对三个参数的函数进行柯里化:

import java.util.function.Function;public class Curry3Args {public static void main(String[] args) {Function<String,Function<String,Function<String, String>>>sum = a -> b -> c -> a + b + c;Function<String,Function<String, String>>hi = sum.apply("Hi ");Function<String, String> ho = hi.apply("Ho ");System.out.println(ho.apply("Hup"));}
}

        程序执行的结果是:

        在处理基本类型和装箱时,还可以使用适当的函数式接口:

import java.util.function.IntFunction;
import java.util.function.IntUnaryOperator;public class CurriedIntAdd {public static void main(String[] args) {IntFunction<IntUnaryOperator>curriedIntAdd = a -> b -> a + b;IntUnaryOperator add4 = curriedIntAdd.apply(4);System.out.println(add4.applyAsInt(5));}
}

    lambda表达式和方法引用并不能将Java变成函数式语言,它们只是提供了对函数式编程风格的更多支持。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

操作EXCEL计算3万条数据的NDVI并填入

Python操作EXCEL&#xff0c;计算3万条数据的NDVI并填入 问题描述 现在是有构建好了的查找表&#xff0c;不过构建了3万条数据&#xff0c;在excel中手动计算每行的NDVI值太麻烦了&#xff0c;也不会操作。 就试试python吧&#xff0c;毕竟python自动处理大型EXCEL数据很方便…

Sentinel学习(1)——CAP理论,微服务中的雪崩问题,和Hystix的解决方案 Sentinel的相关概念 + 下载运行

前言 Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 本篇博客介绍CAP理论&#xff0c;微…

UE5.1编辑器拓展【一、脚本化资产行为,通知,弹窗,高效复制多个同样的资产】

目录​​​​​​​ 插件制作 添加新的类&#xff1a;AssetActionUtility 添加新的模块&#xff1a;EditorScriptingUtilities 路径了解 添加debug的头文件 代码【debug.h】内涵注释&#xff1a; 写函数 .h文件 .cpp文件 插件制作 首先第一步是做一个插件&#xff1a…

吉力宝:智能科技鞋品牌步力宝引领传统产业创新思维

在现代经济环境下&#xff0c;市场经济下产品的竞争非常的激烈&#xff0c;如果没有营销&#xff0c;产品很可能不被大众认可&#xff0c;酒香也怕巷子深&#xff0c;许多传统产业不得不面临前所未有的挑战。而为了冲出这个“巷子”&#xff0c;许多企业需要采用创新思维&#…

Java进阶必会JVM-深入浅出Java虚拟机

系列文章目录 送书第一期 《用户画像&#xff1a;平台构建与业务实践》 送书活动之抽奖工具的打造 《获取博客评论用户抽取幸运中奖者》 送书第二期 《Spring Cloud Alibaba核心技术与实战案例》 送书第三期 《深入浅出Java虚拟机》 文章目录 系列文章目录前言一、推荐书籍二…

vue+Vant,关闭Popup弹框,遮罩层并没有消失

遇到问题&#xff1a; 点击Popup弹框关闭按钮&#xff0c;弹框的遮罩不能正常关闭&#xff0c;如下图。经研究&#xff0c;排除了popup属性问题&#xff0c;最后只能删除代码排除法。 <!--弹框&#xff1a;选号--><van-popupv-model"showNumber"closeablero…

图像处理: ImageKit.NET 3.0.10704 Crack

关于 ImageKit.NET3 100% 原生 .NET 图像处理组件。 ImageKit.NET 可让您快速轻松地向 .NET 应用程序添加图像处理功能。从 TWAIN 扫描仪和数码相机检索图像&#xff1b;加载和保存多种格式的图像文件&#xff1b;对图像应用图像滤镜和变换&#xff1b;在显示屏、平移窗口或缩略…

【新版】系统架构设计师 - 软件架构的演化与维护

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 软件架构的演化与维护考点摘要软件架构演化和定义面向对象软件架构演化对象演化消息演化复合片段演化约束演化 软件架构演化方式静态演化动态演化 软件架构演化原则软件架构演化评估方法大型网站架…

pandas_datareader读取yahoo金融数据超时问题timeout解决方案

在《Python金融数据挖掘》一书中&#xff0c;学习到网络数据源这一章节&#xff0c;利用书中的方法安装了pandas_datareader包&#xff0c;但在获取雅虎数据&#xff08;get_data_yahoo&#xff09;时会出现以下问题&#xff1a; 经过仔细分析和尝试&#xff0c;排除了yahoo受中…

stm32之1602+DHT11+继电器

描述&#xff1a; 1、DHT11监测温室度&#xff0c;并显示到1602液晶上 2、通过串口打印&#xff08;或通过蓝牙模块在手机上查看&#xff09; 3、当温度大于24度时&#xff0c;开启继电器。小于时关闭继电器&#xff08;继电器可连接风扇---假想O(∩_∩)O哈哈~&#xff09; 一、…

JSON.stringify格式化数据美化显示效果(不呆板地一行显示)

一.JSON.stringify 语法: JSON.stringify(value[, replacer[,space]])第二个参数replacer: 过滤属性或者处理值第三个参数space: 美化输出格式 第一个参数: 对象object等 第二个参数replacer: 如果该参数是一个函数: 则在序列化的过程中,被序列化的值的每个属性都会经过该函…

keil调试的时候没问题,下载时候没反应

今天遇到这样一个问题。我下载商家的代码例程后单片机没反应&#xff0c;进入调试的时候一切正常。很奇怪&#xff0c;在网上找了教程问题解决&#xff0c;总结一下。 原因在于程序下载进去后没有按下复位键&#xff0c;导致还是之前的程序。我之前设置的是下载后自动复位运行…

Excel·VBA日期时间转换提取正则表达式函数

标准日期转换 Function 标准日期(ByVal str$) As DateDim pat$, result$arr Array("(\d{4}).*?(\d{1,2}).*?(\d{1,2})", "(\d{4}).*?(\d{1}).*?(\d{1,2})")If Len(str) < 8 Then pat arr(1) Else pat arr(0)With CreateObject("vbscript.r…

数据挖掘(1)概述

一、数据仓库和数据挖掘概述 1.1 数据仓库的产生 数据仓库与数据挖掘&#xff1a; 数据仓库和联机分析处理技术(存储)。数据挖掘&#xff1a;在大量的数据中心挖掘感兴趣的知识、规则、规律、模式、约束(分析)。数据仓库用于决策分析&#xff1a; 数据仓库&#xff1a;是在数…

如何使用ArcGIS Pro直接获取道路中心线

以前使用ArcGIS获取道路中心线&#xff0c;需要先将面要素转换为栅格再获取中心线&#xff0c;现在我们可以通过ArcGIS Pro直接获取道路中心线&#xff0c;这里为大家介绍一下获取方法&#xff0c;希望能对你有所帮助。 新建地理数据库 在存储数据的文件夹上点击右键&#xff…

第4讲:vue内置命令(文本插值,属性绑定,v-text,v-html)

MVVM 什么是MVVM&#xff1f; MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化&#xff0c;让我们将视图 UI 和业务逻辑分开。 View层&#xff1a; 视图层 在我们前端开发中&#xff0c;通常就是 DOM 层。 主要的作用是…

智算创新,美格智能助力智慧支付加速发展

9月21日&#xff0c;以“智算引领创新未来”为主题的紫光展锐2023泛物联网终端生态论坛在深圳举行。作为紫光展锐重要战略合作伙伴&#xff0c;美格智能标准模组产品线总经理郭强华、高级产品总监刘伟鹏受邀出席论坛。美格智能基于紫光展锐5G、4G、智能SoC、Cat.1 bis等芯片平台…

C语言 —— 函数

目录 1. 函数是什么 2. C语言中函数的分类 2.1 库函数 2.2 自定义函数 3. 函数的参数 3.1 实际参数(实参) 3.2 形式参数(形参) 4. 函数的调用 4.1 传值调用 4.2 传址调用 5. 函数的嵌套调用和链式访问 5.1 嵌套调用 5.2 链式访问 6. 函数的声明和定义 6.1函数声明…

CSS基础介绍2

CSS使用三种方式 方式1&#xff1a;在标签的style属性上设置CSS样式&#xff08;行内样式&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>在标签的style属性上设置CSS样式</title>…

【C++11】右值引用和移动语义 {左值引用和右值引用;移动语义;解决函数传值返回的深拷贝问题;完美转发}

一、左值引用和右值引用 传统的C语法中就有引用的语法&#xff0c;而C11中新增了的右值引用语法特性&#xff0c;所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用&#xff0c;都是给对象取别名。 什么是左值&#xff1f;什么是左值引用&#xff1…