JavaSE笔记——函数式编程(类库)

news2024/11/19 14:34:28

文章目录

  • 前言
  • 一、基本类型
  • 二、重载解析
  • 三、@FunctionalInterface
  • 四、默认方法
  • 五、Optional
  • 总结


前言

前面知道了如何编写 Lambda 表达式,下面将详细阐述另一个重要方面:如何使用 Lambda 表达式。即使不需要编写像 Stream 这样重度使用函数式编程风格的类库,学会如何使用 Lambda 表达式也是非常重要的。即使一个最简单的应用,也可能会因为代码即数据的函数式编程风格而受益。

Java 8 中的另一个变化是引入了默认方法和接口的静态方法,它改变了人们认识类库的方式,接口中的方法也可以包含代码体了。

本文还对前面疏漏的知识点进行补充,比如,Lambda 表达式方法重载的工作原理、基本类型的使用方法等。使用 Lambda 表达式编写程序时,掌握这些知识非常重要。


提示:以下是本篇文章正文内容,下面案例可供参考

一、基本类型

以上部分还没有用到基本类型。在 Java 中,有一些相伴的类型,比如 int 和 Integer——前者是基本类型,后者是装箱类型。基本类型内建在语言和运行环境中,是基本的程序构建模块;而装箱类型属于普通的 Java 类,只不过是对基本类型的一种封装。

Java 的泛型是基于对泛型参数类型的擦除——换句话说,假设它是 Object 对象的实例——因此只有装箱类型才能作为泛型参数。这就解释了为什么在 Java 中想要一个包含整型值的列表 List<int>,实际上得到的却是一个包含整型对象的列表 List<Integer>。

麻烦的是,由于装箱类型是对象,因此在内存中存在额外开销。比如,整型在内存中占用 4 字节,整型对象却要占用 16 字节。这一情况在数组上更加严重,整型数组中的每个元素只占用基本类型的内存,而整型对象数组中,每个元素都是内存中的一个指针,指向 Java堆中的某个对象。在最坏的情况下,同样大小的数组,Integer[] 要比 int[] 多占用 6 倍内存。

将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。对于需要大量数值运算的算法来说,装箱和拆箱的计算开销,以及装箱类型占用的额外内存,会明显减缓程序的运行速度。

为了减小这些性能开销,Stream 类的某些方法对基本类型和装箱类型做了区分。如下所示的高阶函数 mapToLong 和其他类似函数即为该方面的一个尝试。在 Java 8 中,仅对整型、长整型和双浮点型做了特殊处理,因为它们在数值计算中用得最多,特殊处理后的系统性能提升效果最明显。
在这里插入图片描述

对基本类型做特殊处理的方法在命名上有明确的规范。如果方法返回类型为基本类型,则在基本类型前加 To,如上所示的 ToLongFunction。如果参数是基本类型,则不加前缀只需类型名即可,如下所示的 LongFunction。如果高阶函数使用基本类型,则在操作后加后缀 To 再加基本类型,如 mapToLong。

在这里插入图片描述

这些基本类型都有与之对应的 Stream,以基本类型名为前缀,如 LongStream。事实上,mapToLong 方法返回的不是一个一般的 Stream,而是一个特殊处理的 Stream。在这个特殊的 Stream 中,map 方法的实现方式也不同,它接受一个 LongUnaryOperator 函数,将一个长整型值映射成另一个长整型值,如下所示。通过一些高阶函数装箱方法,如mapToObj,也可以从一个基本类型的 Stream 得到一个装箱后的 Stream,如 Stream<Long>
在这里插入图片描述

如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能。这些特殊的 Stream 还提供额外的方法,避免重复实现一些通用的方法,让代码更能体现出数值计算的意图。

IntSummaryStatistics intSummaryStatistics = artists.stream().mapToInt(s -> s.getFrom().length()).summaryStatistics();
System.out.println(intSummaryStatistics.getMax());

无需手动计算这些信息,这里使用对基本类型进行特殊处理的方法 mapToInt,将每首曲目映射为曲目长度。因为该方法返回一个IntStream 对象,它包含一个 summaryStatistics 方法,这个方法能计算出各种各样的统计值,如 IntStream 对象内所有元素中的最小值、最大值、平均值以及数值总和。

这些统计值在所有特殊处理的 Stream,如 DoubleStream、LongStream 中都可以得出。如无需全部的统计值,也可分别调用 min、max、average 或 sum 方法获得单个的统计值,同样,三种基本类型对应的特殊 Stream 也都包含这些方法。

二、重载解析

在 Java 中可以重载方法,造成多个方法有相同的方法名,但签名确不一样。这在推断参数类型时会带来问题,因为系统可能会推断出多种类型。这时,javac 会挑出最具体的类型。

BinaryOperator 是一种特殊的 BiFunction 类型,参数的类型和返回值的类型相同。比如,两个整数相加就是一个 BinaryOperator。Lambda 表达式的类型就是对应的函数接口类型,因此,将 Lambda 表达式作为参数传递时,情况也依然如此。操作时可以重载一个方法,分别接受 BinaryOperator 和该接口的一个子类作为参数。调用这些方法时,Java 推导出的 Lambda 表达式的类型正是最具体的函数接口的类型。如下,输出的是IntegerBinaryOperator。

public interface IntegerBiFunction extends BinaryOperator<Integer> {
}

private void overloadedMethod(BinaryOperator<Integer> Lambda) {
    System.out.print("BinaryOperator");
}

private void overloadedMethod(IntegerBiFunction Lambda) {
    System.out.print("IntegerBinaryOperator");
}

在这里插入图片描述
当然,同时存在多个重载方法时,哪个是“最具体的类型”可能并不明确。

public interface IntPredicate {
}
private void overloadedMethod(Predicate<Integer> predicate) {
    System.out.print("Predicate");
}
private void overloadedMethod(IntPredicate predicate) {
    System.out.print("IntPredicate");
}

在这里插入图片描述

传入 overloadedMethod 方法的 Lambda 表达式和两个函数接口 Predicate、IntPredicate 在类型上都是匹配的。在这段代码块中,两种情况都定义了相应的重载方法,这时,javac就无法编译,在错误报告中显示 Lambda 表达式被模糊调用。IntPredicate 没有继承Predicate,因此编译器无法推断出哪个类型更具体。总而言之,Lambda 表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循如下规则

  1. 如果只有一个可能的目标类型,由相应函数接口里的参数类型推导得出;
  2. 如果有多个可能的目标类型,由最具体的类型推导得出;
  3. 如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型。

三、@FunctionalInterface

前面虽已讨论过函数接口定义的标准,但未提及 @FunctionalInterface 注释。事实上,每个用作函数接口的接口都应该添加这个注释。

这究竟是什么意思呢? Java 中有一些接口,虽然只含一个方法,但并不是为了使用Lambda 表达式来实现的。比如,有些对象内部可能保存着某种状态,使用带有一个方法的接口可能纯属巧合。java.lang.Comparable 和 java.io.Closeable 就属于这样的情况。

如果一个类是可比较的,就意味着在该类的实例之间存在某种顺序,比如字符串中的字母顺序。人们通常不会认为函数是可比较的,如果一个东西既没有属性也没有状态,拿什么比较呢?

一个可关闭的对象必须持有某种打开的资源,比如一个需要关闭的文件句柄。同样,该接口也不能是一个纯函数,因为关闭资源是更改状态的另一种形式。

和 Closeable 和 Comparable 接口不同,为了提高 Stream 对象可操作性而引入的各种新接口,都需要有 Lambda 表达式可以实现它。它们存在的意义在于将代码块作为数据打包起来。因此,它们都添加了 @FunctionalInterface 注释。

该注释会强制 javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac 就会报错。重构代码时,使用它能很容易发现问题。

四、默认方法

Collection 接口中增加了新的 stream 方法,如何能让 MyCustomList 类在不知道该方法的情况下通过编译? Java 8 通过如下方法解决该问题:Collection 接口告诉它所有的子类:“如果你没有实现 stream 方法,就使用我的吧。”接口中这样的方法叫作默认方法,在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。

Iterable 接口中也新增了一个默认方法:forEach,该方法功能和 for 循环类似,但是允许
用户使用一个 Lambda 表达式作为循环体。

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

如果已经习惯了通过调用接口方法来使用 Lambda 表达式的方式,那么这个例子理解起来
就相当简单。它使用一个常规的 for 循环遍历 Iterable 对象,然后对每个值调用 accept
方法。

既然如此简单,为何还要单独提出来呢?重点就在于代码段前面的新关键字 default。这个关键字告诉 javac 用户真正需要的是为接口添加一个新方法。除了添加了一个新的关键字,默认方法在继承规则上和普通方法也略有区别。

和类不同,接口没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。

五、Optional

reduce 方法的一个重点尚未提及:reduce 方法有两种形式,一种如前面出现的需要有一个初始值,另一种变式则不需要有初始值。没有初始值的情况下,reduce 的第一步使用Stream 中的前两个元素。有时,reduce 操作不存在有意义的初始值,这样做就是有意义的,此时,reduce 方法返回一个 Optional 对象。

Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们对原有的 null 值有很多抱怨,甚至连发明这一概念的 Tony Hoare 也是如此,他曾说这是自己的一个“价值连城的错误”。作为一名有影响力的计算机科学家就是这样:虽然连一毛钱也见不到,却也可以犯一个“价值连城的错误”。

人们常常使用 null 值表示值不存在,Optional 对象能更好地表达这个概念。使用 null 代表值不存在的最大问题在于 NullPointerException。一旦引用一个存储 null 值的变量,程序会立即崩溃。使用 Optional 对象有两个目的:首先,Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。

下面举例说明 Optional 对象的 API,从而切身体会一下它的使用方法。使用工厂方法 of,可以从某个值创建出一个 Optional 对象。Optional 对象相当于值的容器,而该值可以通过 get 方法提取。

Optional<String> a = Optional.of("a");
System.out.println(a.get());

Optional 对象也可能为空,因此还有一个对应的工厂方法 empty,另外一个工厂方法 ofNullable 则可将一个空值转换成 Optional 对象。同时展示了第三个方法 isPresent 的用法(该方法表示一个 Optional 对象里是否有值)

Optional emptyOptional = Optional.empty();
Optional alsoEmpty = Optional.ofNullable(null);
System.out.println(emptyOptional.isPresent());
System.out.println(alsoEmpty.isPresent());

在这里插入图片描述

使用 Optional 对象的方式之一是在调用 get() 方法前,先使用 isPresent 检查 Optional 对象是否有值。使用 orElse 方法则更简洁,当 Optional 对象为空时,该方法提供了一个备选值。如果计算备选值在计算上太过繁琐,即可使用 orElseGet 方法。该方法接受一个Supplier 对象,只有在 Optional 对象真正为空时才会调用。

System.out.println(emptyOptional.orElse("b"));
System.out.println(emptyOptional.orElseGet(() -> "c"));

总结

使用为基本类型定制的 Lambda 表达式和 Stream,如 IntStream 可以显著提升系统性能。使用为基本类型定制的 Lambda 表达式和 Stream,如 IntStream 可以显著提升系统性能。在一个值可能为空的建模情况下,使用 Optional 对象能替代使用 null 值。

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

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

相关文章

find_package()的使用

find_package()命令是用来查找依赖包的&#xff0c;理想情况下&#xff0c;一句find_package()把一整个依赖包的头文件包含路径、库路径、库名字、版本号等情况都获取到&#xff0c;后续只管用就好了。但实际中往往CMake失败就是出在find_package()的失败上&#xff08;这里不考…

朝花夕拾 - 2023 莽一年

Hello 2023&#xff0c;我来了~今年&#xff0c;又是一个怎样的楚门世界&#xff0c;我要如何在里面撒泼&#xff0c;期待~一 回收 2022 不及格答卷 回首 2022&#xff0c;那真的不堪回首&#xff0c;细节太多了&#xff0c;没做好没把握住~但是&#xff0c;不管 2022 过得怎样…

Qt6 中如何使用 qsb

【写在前面】 Qt 5 的图形体系结构非常依赖 OpenGL 作为底层 3D 图形 API。但过去 8 年来随着 Metal 和 Vulkan 的推出&#xff0c;市场发生了巨大变化。现在&#xff0c;Qt 6 加入了大量不同平台的图形 API&#xff0c;以确保用户可以在所有平台上以最高性能运行 Qt。 在 Qt Q…

【类和对象(完结)】

目录 1. 再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 explicit关键字 2. static成员 2.1 概念 2.2 特性 3. 友元 3.1 友元函数 3.2 友元类 4. 内部类 5.匿名对象 6.拷贝对象时的一些编译器优化 7. 再次理解类和对象 8.总结 1. 再谈构造函数 1.1 构造函数体…

技术分享| 如何使用Prometheus实现系统进程监控

如何监控线上正在运营的系统&#xff1f;如何得知系统目前是正常还是异常&#xff1f; Prometheus是这么一套数据监控解决方案。它能让运维及开发人员随时掌控系统的运行状态&#xff0c;快速定位出现问题的位置&#xff0c;快速排除故障。只要按照 Prometheus的方式来做&#…

力扣刷题记录——258. 各位相加、263.丑数、268.丢失的数字

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《258. 各位相加、263.丑数、268.丢失的数字》。 目录 25…

第三十五讲:神州无线局域网基础知识

1. IEEE 802.11协议 802.11无线标准家族包括802.11a/b/g/n/ac五个标准理论上可以提供高达每秒1Gbit的数据传输能力标准定义了如何使用免授权2.4 GHz 和 5GHz 频带的电磁波进行信号传输。 802.11无线标准家族 802.11a 802.11b 802.11g 802.11n 802.11ac 工作频段 5GHz 2…

内存访问为什么要分段?

内存分段是处理器为访问内存而设计的机制&#xff0c;称为内存分段机制。 简单的内存知识 内存结构&#xff08;连续且地址依次升高&#xff09; 访问方式 内存是随机读写设备&#xff0c;即访问其内部任何处&#xff0c;不需要从头开始找&#xff0c;只要直接给出其地址便可。…

【项目启动】IDEA新建项目同步到Github

文章目录SSH秘钥检查GitHub创建项目IDEA创建项目IDEA同步GitHubSSH秘钥检查 目前&#xff0c;github不支持https形式的远程同步方式&#xff0c;如果使用https形式进行同步会报以下错误&#xff1a; remote: Support for password authentication was removed on August 13, 2…

C# WinForm CAD文件显示(dxf,dwg显示)

找遍全网很难找到开源dxf显示控件(C# winform)&#xff0c;大部分控件都需要收费&#xff0c;对于做软件开发很麻烦 C# WPF倒是有nefdxfZoomableCanvas可以实现&#xff0c;确实很方便&#xff0c;这个在github&#xff1a;https://github.com/shao200/WpfDxfViewer上也能找到开…

c++11 标准模板(STL)(std::deque)(六)

定义于头文件 <deque> std::deque 容量 检查容器是否为空 std::deque<T,Allocator>::empty bool empty() const; (C11 前) bool empty() const noexcept; (C11 起) (C20 前) [[nodiscard]] bool empty() const noexcept; (C20 起)检查容器是否无元素&#xff0c…

大数据NiFi(四):NiFi单节点安装

文章目录 NiFi单节点安装 一、介绍与下载 二、单节点安装

JavaWeb:JSP概述及原理

1&#xff0c;JSP概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java服务端页面。 是一种动态的网页技术&#xff0c;其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容&#xff0c;也就是 JSP HTML Java…

javaee之SpringMVC2

SpringMVC返回值类型以及响应数据类型 1.搭建环境 还是按照springMVC1中的搭建环境进行搭建。这里就不多说。 响应之返回值是String类型 我们先来创建一个User类 User,java package com.pxx.domain;import java.io.Serializable;public class User implements Serializab…

PS 矩形选区工具(1)基本用法 生成图层 选区方式演示讲解

我们先打开PS 然后打开一个项目 我们可以选择一个图层 然后 点击左上角 图像>调整>色相.饱和度 弹出操作框之后 我们拉动色相的色条 对应视图就会发生主体颜色的变化 然后 我们打开一个只有一个图层的图片项目 我们对这个图层操作 整个都会变化 但如果我只是想改其中…

后悔升级iPhone?教你如何把iOS15降回iOS14

还在使用betabeta版iOS 15和iPadOS 15吗&#xff1f;如果你出于某种原因准备返回稳定的iOS 14&#xff0c;本篇文章将会为你详细介绍如何从 iOS 15 beta版降级到 iOS 14&#xff0c;这对于有一定动手能力的人来说并不难。 如何从 iOS 15 beta版降级到 iOS 14 重要提示&#xf…

Spring是怎么回事?新手入门就看这篇吧

前言 今天壹哥给大家介绍一套开源的轻量级框架&#xff0c;它就是Spring。在给大家详细讲解Spring框架之前&#xff0c;壹哥先给大家介绍Spring框架的主要内容&#xff1a; Spring的基本概念 Spring核心思想之ioc Spring核心思想之aop Spring框架对事务的支持 在本系列文章…

解决前端如何使用插件crypto-js进行AES加密方式数据加密

一、问题 目录 一、问题 1.1 问题概述 1.2 操作过程描述 二、解决 2.1 说明 2.2 crypto-js安装 2.3 使用crypto-js 1.1 问题概述 如何进行加密和解密以及采用什么方式进行加密解密是本文主要解决的内容~ 之前有小伙伴问了关于加密解密的事&#xff0c;确实是的&#xff…

pdfbox / XSL + FOP 转换 PDF文档

XSL-FO是XSL Formatting Objects的缩写&#xff0c;它是一种用于文档格式的XML 置标语言。XSL-FO是XSL的一部分&#xff0c;而XSL是一组定义XML数据转换与格式的W3C技术。XSL的其他部分有XSLT与XPath。 XSL-FO是用于格式化XML数据的语言&#xff0c;全称为Extensible Styleshe…

python基础讲解 02

人工智能学习基础四剑客&#xff08;库&#xff09;Numpy ( Numerical Python)使用创建随机数组查看数组的属性数组与标量之间的计算四剑客&#xff08;库&#xff09; Python被大量应用在数据挖掘和深度学习领域,其中使用极其广泛的是Numpy&#xff08;N维数组对象和向量运算&…