初识Java 11-2 函数式编程

news2024/11/25 4:46:22

目录

高阶函数

闭包

函数组合

柯里化和部分求值


本笔记参考自: 《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 {
    @Override
    public String toString() {
        return "类I";
    }
}

class O {
    @Override
    public 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() {
            @Override
            public 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中包含了一个Function

        Function<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.coloradmin.cn/o/1048703.html

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

相关文章

26551-2011 畜牧机械 粗饲料切碎机

声明 本文是学习GB-T 26551-2011 畜牧机械 粗饲料切碎机. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了粗饲料切碎机的产品型号、技术要求、试验方法、检验规则、标志、包装、运输与贮存。 本标准适用于加工农作物秸秆等粗饲料…

Python接口自动化之unittest单元测试

以下主要介绍unittest特性、运行流程及实际案例。 一、单元测试三连问 1、什么是单元测试&#xff1f; 按照阶段来分&#xff0c;一般就是单元测试&#xff0c;集成测试&#xff0c;系统测试&#xff0c;验收测试。单元测试是对单个模块、单个类或者单个函数进行测试。 将…

迅为龙芯2K1000开发板通过汇编控制GPIO

上一小节&#xff0c;我们使用了 C 语言控制了 gpio&#xff0c;这一小节我们来看一下如何使用汇编来控制 gpio 呢&#xff1f;有的 同学可能会有疑问了&#xff0c;既然我们可以使用 C 语言来控制 gpio&#xff0c;为什么我们还要使用更底层的汇编语言呢&#xff0c; 如果我…

【Read View】Read View如何在MVCC里面工作、事务的隔离级别如何实现等重点知识汇总

目录 Read View 在 MVCC 里如何工作的 隔离级别如何实现的呢 Read View 在 MVCC 里如何工作的 ReadView用于管理事务之间的数据一致性&#xff0c;特别是在并发访问数据库时。 那 Read View 到底是个什么东西&#xff1f; Read View 有四个重要的字段&#xff1a; m_ids &…

Python函数:chr()和ord()

两个函数是基于Unicode编码表进行进行字符与字码之间的转换。 chr()函数是通过字码转换成字符: 如图,坐标(1,4e10)丑 使用chr需要线将坐标相加得到&#xff1a;4e11 chr默认传入10进制的字码. 如图是各进制的字码。 也可以传入其他进制&#xff0c;不过需要在前面传入的参数最前…

canvas+javascript实现点击更改图片中指定部位的颜色(将近似的颜色值变更为自定义的颜色)

原图&#xff1a; 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

前端架构师之02_ES6_高级

1 类和继承 1.1 class类 JavaScript 语言中&#xff0c;生成实例对象的传统方法是通过构造函数。 // ES5 创建对象 // 创建一个类&#xff0c;用户名 密码 function User(name,pass){// 添加属性this.name name;this.pass pass; } // 用 原型 添加方法 User.prototype.sho…

跨境电商如何利用海外代理IP,提高竞争力?

随着经济全球化的深度发展&#xff0c;跨境电商已经成为外贸发展的主要赛道&#xff0c;跨境电商王者般的新业态&#xff0c;近年来&#xff0c;我国跨境电商发展得到政府的大力扶持。而代理IP也逐渐成为助力跨境业务的强大工具之一。可以为我们跨境电商种出现的如地域限制、安…

中秋国庆,双节同乐

中秋国庆双节将至&#xff0c;送上最诚挚的祝福&#xff1a; 愿你的中秋月圆&#xff0c;家庭团圆更圆满&#xff0c;幸福围绕&#xff0c;笑声不断。 愿国庆日祖国繁荣昌盛&#xff0c;人民幸福安康&#xff0c;和谐美好永恒。 让我们共同庆祝这个特殊的时刻&#xff0c;祝福您…

MDETR:端到端多模态理解的调制检测

代码&#xff1a;https://github.com/ashkamath/mdetr 摘要 多模态推理系统依靠预训练的目标检测器从图像中提取感兴趣的区域。然而&#xff0c;这个关键模块通常用作黑匣子&#xff0c;独立于下游任务进行训练&#xff0c;并使用固定的对象和属性词汇表。这使得此类系统难以捕…

【数据仓库设计基础(三)】数据集市

文章目录 一. 数据集市的概念二. 数据集市与数据仓库的区别三. 数据集市设计 一. 数据集市的概念 数据集市是数据仓库的一种简单形式&#xff0c;通常由组织内的业务部门自己建立和控制。一个数据集市面向单一主题域&#xff0c;如销售、财务、市场等。 数据集市的数据源可以是…

Lucene-MergePolicy详解

简介 该文章基于业务需求背景&#xff0c;因场景需求进行参数调优&#xff0c;下文会尽可能针对段合并策略&#xff08;SegmentMergePolicy&#xff09;的全参数进行说明。 主要介绍TieredMergePolicy&#xff0c;它是Lucene4以后的默认段的合并策略&#xff0c;之前采用的合并…

网络时钟程序Net_Clock

网络时钟程序Net_Clock 小程序的源代码,着重编程思想,巧妙的构思架构... 主要功能:简单的Socket网络套接从时间服务器获取时间,timeSetEvent精度定时器,颜色方案,自绘标题栏,自绘菜单...等等 VS2015平台的精度时钟程序,北京,美东,美中三个时区日期时间显示,多种颜色方案显示…

深入理解常见应用级算法思想

1 概论 1.1 概念 1.1.1 数据结构 1&#xff09;概述 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率。 2&#xff09;划分 从关注的维度…

洗文案神器,不同洗文案神器怎么选择

在数字时代&#xff0c;文字的重要性变得愈发突出&#xff0c;不论是在商业领域的广告宣传、自媒体的文章创作&#xff0c;还是学术界的研究论文&#xff0c;文字都是传递信息和思想的关键媒介。然而&#xff0c;创作高质量的文案并非易事&#xff0c;这就是为什么越来越多的人…

FFmpeg安装教程

一、下载安装 打开官方网站&#xff0c;ffmpeg管网地址&#xff0c;点击左侧download或页面中的绿色Download按钮。 选择要下载的平台&#xff0c;windows版本选择红框第一个&#xff0c;第二个是github平台 点击左侧release builds&#xff0c;选择要下载的软件版本&#x…

【STM32】sct 分散加载文件的格式与应用

简介 当工程按默认配置构建时&#xff0c;MDK 会根据我们选择的芯片型号&#xff0c;获知芯片的内部FLASH 及内部 SRAM 存储器概况&#xff0c;自动生成一个以工程名命名的后缀为*.sct 的分散加载文件(Linker Control File&#xff0c;scatter loading)&#xff0c;链接器根据…

巨人互动|Google海外户Google SEO工作为何要把控细节

Google SEO&#xff08;搜索引擎优化&#xff09;是一项为了提高网站在Google搜索结果中的排名和可见性的策略和技术。在进行SEO工作时&#xff0c;把控细节非常重要&#xff0c;本文小编讲讲关于为何要把控细节的原因。 巨人互动|Google海外户&Google内容定位介绍&#xf…

当当网商品详情数据接口

当当网商品详情数据接口可以通过当当网的开放平台获取相关信息。您可以注册当当开放平台账号&#xff0c;并按照要求提交申请获取API接口的调用凭证。获得授权后&#xff0c;您将会收到一组AccessKey和SecretKey。使用编程语言&#xff08;如Java&#xff09;调用API接口&#…

聊一聊JDK21-虚拟线程

目录 前言 Virtual Threads的开始 为什么需要Virtual Threads JDK19 预览版初次出现 JDK21 Virtual Threads的正式发布 Virtual Threads 该怎么使用 简单聊聊Virtual Threads的实现 使用时候的注意事项 本地尝鲜一下JDK21及Virtual Threads 结语 前言 2023年9月19日…