重识Java8函数式编程
前言
最近真的是太忙忙忙忙忙了,很久没有更新文章了。最近工作中看到了几段关于函数式编程的代码,但是有点费解,于是就准备总结一下函数式编程。很多东西很简单,但是如果不总结,可能会被它的各种变体所困扰。接触Lambda表达式已经很久了,但是也一直是处于照葫芦画瓢的阶段,所以想自己去编写相关代码,也有些捉襟见肘。
1. Lambda表达式的不同形式
// 基本形式
参数 -> 主体
1.1 形式一
Runnable noArguments = () -> System.out.println("Hello World");
该形式的Lambda表达式不包含参数,使用空括号()表示没有参数。它实现了Runnable接口,该接口也只有一个run方法,没有桉树,且返回类型为void。
1.2 形式二
ActionListener oneArgument = event -> System.out.println("button clicked");
该形式的Lambda表达式包含且只包含一个参数,可省略参数的符号。
1.3 形式三
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};
Lambda表达式的主体不仅可以使一个表达式,而且也可以是一段代码块,使用大括号{}将代码块括起来。该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。只有以行代码的Lambda表达式也可以使用大括号,用以明确Lambda表达式从何处开始,到哪里结束。
1.4 形式四
BinaryOperator<Long> add = (x, y) -> x + y;
Lambda表达式也可以表示包含多个参数的方法,上面的Lambda表达式并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。变量add的类型时BinaryOperator,它不是两个数字的和,而是将两个数字相加的那行代码。
1.5 形式五
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
到目前为止,所有Lambda表达式中的参数类型都是由编译器推断得出的。但有时最好也可以显示声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此。
2. 引用值,而不是变量
如果你曾使用过匿名内部类,也许遇到过这样的情况:需要引用它所在方法里的变量。这是,需要将变量声明为final。
final String name = getUserName();
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("hi " + name);
}
});
将变量声明为 final,意味着不能为其重复赋 值。同时也意味着在使用 final 变量时,实际上是在使用赋给该变量的一个特定的值。
Java 8 虽然放松了这一限制,可以引用非 final 变量,但是该变量在既成事实上必须是 final(意思就是你不能再次对该变量赋值)。虽然无需将变量声明为 final,但在 Lambda 表达式中,也无法用作非终态变量。如 果坚持用作非终态变量,编译器就会报错。 既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值, 而不是变量。
例如:
String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));
3. 函数接口
在 Java 里,所有方法参数都有固定的类型。假设将数字 3 作为参数传给一个方法,则参数 的类型是 int。那么,Lambda 表达式的类型又是什么呢?
使用只有一个方法的接口来表示某特定方法并反复使用,是很早就有的习惯。使用 Swing 编写过用户界面的人对这种方式都不陌生,这里无需再标新立异,Lambda 表达式也使用同样的技巧,并将这种接口称为函数接口。
接口中单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配即可。可在函数接口中为参数起一个有意义的名字,增加代码易读性,便于更透彻 地理解参数的用途。
3.1 Java中重要的函数接口
| 接口 | 参数 | 返回类型 | 示例 |
|---|---|---|---|
| Predicate | T | boolean | 判断是否 |
| Consumer | T | void | 输出一个值 |
| Function<T,R> | T | T | 获得对象的名字 |
| Supplier | None | T | 工厂方法 |
| UnaryOperator | T | T | 逻辑非(!) |
| BinaryOperator | (T, T) | T | 求两个数的乘积(*) |
3.2 函数接口定义
定义函数接口需要使用到注解@FunctionalInterface
例如:
@FunctionalInterface
public interface MyFuncInterface {
void print();
}
使用:
public class MyFunctionalInterfaceTest {
public static void main(String[] args) {
doPrint(() -> System.out.println("java"));
}
public static void doPrint(MyFuncInterface my) {
System.out.println("请问你喜欢什么编程语言?");
my.print();
}
}
说明:
这只是一个很简单的例子,有人觉得为什么要搞这么复杂,去定义一个接口?这个问题还是读者在平时的工作中去感悟吧,总之,先学会怎么用它。不至于看了别人写的代码都看不懂。
至于我个人的理解,可以简单聊聊。以前写过JavaScript,里面有一种语法就是将自定义函数B作为参数传递到另外一个函数A里面,在函数A里面会执行你自定义的函数B逻辑,我当时就非常喜欢这种特性,因为每个人关于函数B的实现可能不一样,亦或者场景不一样也会导致函数B的实现不一样。我觉得Java8的这个函数式编程就是对这一特性的补充。
4. 流
流的常用操作有很多,例如collect(toList())、map、filter、max、min等,下面介绍一下flatMap和reduce。
4.1 flatMap
flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(toList());
assertEquals(asList(1, 2, 3, 4), together);
调用 stream 方法,将每个列表转换成 Stream 对象,其余部分由 flatMap 方法处理。 flatMap 方法的相关函数接口和 map 方法的一样,都是 Function 接口,只是方法的返回值 限定为 Stream 类型罢了。
4.2 reduce
reduce 操作可以实现从一组值中生成一个值。对于 count、min 和 max 方 法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。
如何通过 reduce 操作对 Stream 中的数字求和。以 0 作起点——一个空Stream 的求和结果,每一步都将 Stream 中的元素累加至 accumulator,遍历至 Stream 中的 最后一个元素时,accumulator 的值就是所有元素的和。
int count = Stream.of(1, 2, 3)
.reduce(0, (acc, element) -> acc + element);
assertEquals(6, count);
Lambda 表达式的返回值是最新的 acc,是上一轮 acc 的值和当前元素相加的结果。reducer 的类型是前面已介绍过的 BinaryOperator。
5. Optional
reduce 方法的一个重点尚未提及:reduce 方法有两种形式,一种如前面出现的需要有一 个初始值,另一种变式则不需要有初始值。没有初始值的情况下,reduce 的第一步使用 Stream 中的前两个元素。有时,reduce 操作不存在有意义的初始值,这样做就是有意义的,此时,reduce 方法返回一个 Optional 对象。
Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们对原有的 null 值有很多抱怨。人们常常使用 null 值表示值不存在,Optional 对象能更好地表达这个概念。使用 null 代 表值不存在的最大问题在于 NullPointerException。一旦引用一个存储 null 值的变量,程 序会立即崩溃。使用 Optional 对象有两个目的:首先,Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。
下面我们举例说明 Optional 对象的 API,从而切身体会一下它的使用方法。使用工厂方法 of,可以从某个值创建出一个 Optional 对象。Optional 对象相当于值的容器,而该值可以 通过 get 方法提取。
Optional<String> a = Optional.of("a");
assertEquals("a", a.get());
Optional 对象也可能为空,因此还有一个对应的工厂方法 empty,另外一个工厂方法 ofNullable 则可将一个空值转换成 Optional 对象。下面的代码同时展示 了第三个方法 isPresent 的用法(该方法表示一个 Optional 对象里是否有值)。
Optional emptyOptional = Optional.empty();
Optional alsoEmpty = Optional.ofNullable(null); assertFalse(emptyOptional.isPresent());
使用 Optional 对象的方式之一是在调用 get() 方法前,先使用 isPresent 检查 Optional 对象是否有值。使用 orElse 方法则更简洁,当 Optional 对象为空时,该方法提供了一个 备选值。如果计算备选值在计算上太过繁琐,即可使用 orElseGet 方法。该方法接受一个 Supplier 对象,只有在 Optional 对象真正为空时才会调用。
assertEquals("b", emptyOptional.orElse("b"));
assertEquals("c", emptyOptional.orElseGet(() -> "c"));
最后
实践是检验真理的唯一标准,多写代码,多思考,你的代码才会越来越好。


重识Java8函数式编程的更多相关文章
- Java8 函数式编程详解
Java8 函数式编程详解 Author:Dorae Date:2017年11月1日23:03:26 转载请注明出处 说起Java8,可能很多人都已经知道其最大的改进,就是引入了Lambda表达式与S ...
- 关于Java8函数式编程你需要了解的几点
函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异. 函数作为一等公民 在理解函数作为一等公民这句话时,让我们先来看一下一种非常常 ...
- Java8函数式编程探秘
引子 将行为作为数据传递 怎样在一行代码里同时计算一个列表的和.最大值.最小值.平均值.元素个数.奇偶分组.指数.排序呢? 答案是思维反转!将行为作为数据传递. 文艺青年的代码如下所示: public ...
- [2017.02.23] Java8 函数式编程
以前学过Haskell,前几天又复习了其中的部分内容. 函数式编程与命令式编程有着不一样的地方,函数式编程中函数是第一等公民,通过使用少量的几个数据结构如list.map.set,以及在这些数据结构上 ...
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...
- 漫漫人生路,学点Jakarta基础-Java8函数式编程
接口默认方法 Java8版本以后新增了接口的默认方法,不仅仅只能包含抽象方法,接口也可以包含若干个实例方法.在接口内定义实例方法(但是注意需要使用default关键字) 在此定义的方法并非抽象方法,而 ...
- Java8函数式编程的宏观总结
1.java8优势通过将行为进行抽象,java8提供了批量处理数据的并行类库,使得代码可以在多核CPU上高效运行. 2.函数式编程的核心使用不可变值和函数,函数对一个值进行处理,映射成另一个值. 3. ...
- java8函数式编程实例
什么是函数式编程 函数式编程是java8的一大特色,也就是将函数作为一个参数传递给指定方法.别人传的要么是基本数据类型,要么就是地址引用 ,我们要穿一个“动作”. Stream 说到函数式编程,就不得 ...
- Java8函数式编程以及Lambda表达式
第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...
随机推荐
- Springboot整合MybatisPlus(超详细)完整教程~
新建springboot项目 开发工具:idea2019.2,maven3 pom.xml <dependency> <groupId>org.springframework. ...
- [JavaWeb基础] 029.OGNL表达式介绍
1.OGNL概述 OGNL,全称为Object-Graph Navigation Language,它是一个功能强大的表达式语言,用来获取和设置Java对象的属性,它旨在提供一个更高的更抽象的层次来对 ...
- html5学习之路_007
CSS概述 CSS指层叠样式表 CSS样式表极大地提高了工作效率 CSS基础语法 selector { property:value } 例:hi {color:red; font-size:14px ...
- SSIS 数据类型 第二篇:变量的数据类型
变量(Variable)用于存储在Package运行时用到的值,集成服务支持两种类型的变量:用户自定义的变量和系统变量,自定义的变量由用户来定义,系统变量由集成服务来定义. 变量的用途十分广泛,用于容 ...
- [组合数学] 圆排列和欧拉函数为啥有关系:都是polya定理的锅
本文是一个笨比学习组合数学的学习笔记,因为是笨比,所以写的应该算是很通俗易懂了. 首先,我们考虑这么一个问题:你有无穷多的\(p\)种颜色的珠子,现在你想要的把他们中的\(n\)个以圆形的形状等间距的 ...
- Java实现 LeetCode 350 两个数组的交集 II(二)
350. 两个数组的交集 II 给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2,2] 示例 2: 输入 ...
- Java实现 LeetCode 238 除自身以外数组的乘积
238. 除自身以外数组的乘积 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元 ...
- java实现和为定值的两个数
1 问题描述 输入一个整数数组和一个整数,在数组中查找两个数,满足他们的和正好是输入的那个整数.如果有多对数的和等于输入的整数,输出任意一对即可.例如,如果输入数组[1,2,4,5,7,11,15]和 ...
- Java实现第八届蓝桥杯日期问题
日期问题 题目描述 小明正在整理一批历史文献.这些历史文献中出现了很多日期.小明知道这些日期都在1960年1月1日至2059年12月31日.令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/ ...
- 浅谈Python内置对象类型——数字篇(附py2和py3的区别之一)
Python是一门面向对象的编程设计语言,程序中每一样东西都可以视为一个对象.Python内置对象可以分为简单类型和容器类型,简单类型主要是数值型数据,而容器类型是可以包含其他对象类型的集体,如序列. ...