20190825 On Java8 第十三章 函数式编程
第十三章 函数式编程
函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。
OO
(object oriented,面向对象)是抽象数据,FP
(functional programming,函数式编程)是抽象行为。
Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。
没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。
Lambda表达式
Lambda 表达式是使用最小可能语法编写的函数定义:
- Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。
- Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。
任何 Lambda 表达式的基本语法是:
- 参数。
- 接着
->
,可视为“产出”。 ->
之后的内容都是方法体。
递归
递归函数是一个自我调用的函数。可以编写递归的 Lambda 表达式,但需要注意:递归方法必须是实例变量或静态变量,否则会出现编译时错误。
方法引用
Runnable接口
Runnable 接口自 1.0 版以来一直在 Java 中,因此不需要导入。它也符合特殊的单方法接口格式:它的方法 run() 不带参数,也没有返回值。因此,我们可以使用 Lambda 表达式和方法引用作为 Runnable。
class Go {
static void go() {
System.out.println("Go::go()");
}
}
public class RunnableMethodReference {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("Anonymous");
}
}).start();
new Thread(() -> System.out.println("lambda")).start();
new Thread(Go::go).start();
}
}
未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象。
使用未绑定的引用时,函数方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 理由是:你需要一个对象来调用方法。
class This {
void two(int i, double d) {
System.out.println("This two " + i + " , " + d);
}
void three(int i, double d, String s) {
System.out.println("This three " + i + " , " + d + " , " + s);
}
void four(int i, double d, String s, char c) {
System.out.println("This three " + i + " , " + d + " , " + s + " , " + c);
}
}
interface TwoArgs {
void call2(This athis, int i, double d);
}
interface ThreeArgs {
void call3(This athis, int i, double d, String s);
}
interface FourArgs {
void call4(This athis, int i, double d, String s, char c);
}
public class MultiUnbound {
public static void main(String[] args) {
TwoArgs twoargs = This::two;
ThreeArgs threeargs = This::three;
FourArgs fourargs = This::four;
This athis = new This();
twoargs.call2(athis, 11, 3.14);
threeargs.call3(athis, 11, 3.14, "Three");
fourargs.call4(athis, 11, 3.14, "Four", 'Z');
}
}
构造函数引用
class Dog {
String name;
int age = -1; // For "unknown"
Dog() {
name = "stray";
}
Dog(String nm) {
name = nm;
}
Dog(String nm, int yrs) {
name = nm;
age = yrs;
}
@Override
public String toString() {
return "Dog [name=" + name + ", age=" + age + "]";
}
}
interface MakeNoArgs {
Dog make();
}
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public class CtorReference {
public static void main(String[] args) {
MakeNoArgs mna = Dog::new; // [1]
Make1Arg m1a = Dog::new; // [2]
Make2Args m2a = Dog::new; // [3]
Dog dn = mna.make();
System.out.println(dn);
Dog d1 = m1a.make("Comet");
System.out.println(d1);
Dog d2 = m2a.make("Ralph", 4);
System.out.println(d2);
}
}
函数式接口
方法引用和 Lambda 表达式必须被赋值,同时编译器需要识别类型信息以确保类型正确。
怎么知道传递给方法的参数的类型?
为了解决这个问题,Java 8 引入了 java.util.function
包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为函数式方法。
在编写接口时,可以使用 @FunctionalInterface
注解强制执行此“函数式方法”模式。
@FunctionalInterface
注解是可选的。接口中如果有多个方法则会产生编译时错误消息。
Java 8 允许我们以简便的语法为接口赋值函数。
java.util.function
包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。
以下是基本命名准则:
- 如果只处理对象而非基本类型,名称则为
Function
,Consumer
,Predicate
等。参数类型通过泛型添加。 - 如果接收的参数是基本类型,则由名称的第一部分表示,如
LongConsumer
,DoubleFunction
,IntPredicate
等,但基本Supplier
类型例外。 - 如果返回值为基本类型,则用
To
表示,如ToLongFunction <T>
和IntToLongFunction
。 - 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用
UnaryOperator
,两个参数使用BinaryOperator
。 - 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
- 如果接收的两个参数类型不同,则名称中有一个
Bi
。
下表描述了 java.util.function
中的目标类型(包括例外情况):
特征 | 函数式方法名 | 示例 |
---|---|---|
无参数; 无返回值 |
Runnable(java.lang) run() |
Runnable |
无参数; 返回类型任意 |
Supplier get() getAs类型() |
Supplier BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
无参数; 返回类型任意 |
Callable(java.util.concurrent) call() |
Callable |
1 参数; 无返回值 |
Consumer accept() |
Consumer IntConsumer LongConsumer DoubleConsumer |
2 参数Consumer | BiConsumer accept() |
BiConsumer<T,U> |
2 参数Consumer; 1 引用; 1 基本类型 |
Obj类型Consumer accept() |
ObjIntConsumer ObjLongConsumer ObjDoubleConsumer |
1 参数; 返回类型不同 |
Function apply() To类型和类型To类型 applyAs类型() |
Function<T,R> IntFunction LongFunction DoubleFunction ToIntFunction ToLongFunction ToDoubleFunction IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
1 参数; 返回类型相同 |
UnaryOperator apply() |
UnaryOperator IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
2 参数类型相同; 返回类型相同 |
BinaryOperator apply() |
BinaryOperator IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
2 参数类型相同; 返回整型 |
Comparator(java.util) compare() |
Comparator |
2 参数; 返回布尔型 |
Predicate test() |
Predicate BiPredicate<T,U> IntPredicate LongPredicate DoublePredicate |
参数基本类型; 返回基本类型 |
类型To类型Function applyAs类型() |
IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
2 参数类型不同 | Bi操作 (不同方法名) |
BiFunction<T,U,R> BiConsumer<T,U> BiPredicate<T,U> ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction |
在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名,而不是你的方法名。
高阶函数
高阶函数(Higher-order Function)只是一个消费或产生函数的函数。
闭包
Java 8 提供了有限但合理的闭包支持。
从 Lambda 表达式引用的局部变量必须是 final 或者是等同 final 效果的。
等同 final 效果(Effectively Final)。这个术语是在 Java 8 才开始出现的,表示虽然没有明确地声明变量是 final 的,但是因变量值没被改变过而实际有了 final 同等的效果。 如果局部变量的初始值永远不会改变,那么它实际上就是 final 的。
应用于对象引用的 final 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。
函数组合
一些 java.util.function
接口中包含支持函数组合的方法。
组合方法 | 支持接口 |
---|---|
andThen(argument) 根据参数执行原始操作 |
Function BiFunction Consumer BiConsumer IntConsumer LongConsumer DoubleConsumer UnaryOperator IntUnaryOperator LongUnaryOperator DoubleUnaryOperator BinaryOperator |
compose(argument) 根据参数执行原始操作 |
Function UnaryOperator IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
and(argument) 短路逻辑与原始断言和参数断言 |
Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
or(argument) 短路逻辑或原始断言和参数断言 |
Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
negate() 该断言的逻辑否断言 |
Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
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 AMBULANCES"));
}
}
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);
}
}
柯里化和部分求值
柯里化意为:将一个多参数的函数,转换为一系列单参数函数。
对于每个级别的箭头级联(Arrow-cascading),在类型声明中包裹了另一个 Function。
20190825 On Java8 第十三章 函数式编程的更多相关文章
- Java8内置的函数式编程接口应用场景和方式
首先,我们先定义一个函数式编程接口 @FunctionalInterface public interface BooleanFunctionalInterface<T> { boolea ...
- 《Clojure编程》笔记 第2章 函数式编程
目录 背景简述 第2章 函数式编程 背景简述 本人是一个自学一年Java的小菜鸡,理论上跟大多数新手的水平差不多,但我入职的新公司是要求转Clojure语言的.坊间传闻:通常情况下,最好是有一定Jav ...
- [译]Java8:循环与函数式编程
Java8函数式编程的加入彻底改变了游戏规则.对Java开发者来说这是一个全新的世界,我们也需要做出相应的改变. 在这篇文章中我们将找寻传统循环代码的可替代方案.Java8的函数式编程特性改变了编程思 ...
- [译]java8新特性:函数式编程(functional programming)的优点
Java8引入了函数式编程,他对java是一个极大的扩展.Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数式编程.这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化. 但是为 ...
- 20190928 On Java8 第二十三章 注解
第二十三章 注解 定义在 java.lang 包中的5种标准注解: @Override:表示当前的方法定义将覆盖基类的方法.如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示. ...
- Java8新特性:函数式编程
1. 概述 函数式编程学习目的: 能够看懂公司里的代码 大数据量下处理集合效率更高 代码可读性高 消灭嵌套地狱 函数式编程思想: 面向对象思想需要关注用什么对象完成什么事情.而函数式编程思想就类似于我 ...
- python3 第二十三章 - 函数式编程之Partial function(偏函数)
要注意,这里的偏函数和数学意义上的偏函数不一样,偏函数是2.5版本以后引进来的东西,属于函数式编程的一部分.前面章节中我们讲到,通过设定参数的默认值,可以降低函数调用的难度.而偏函数也可以做到这一点. ...
- Python第十三章-网络编程
网络编程 一.网络编程基础 python 的网络编程模块主要支持两种Internet协议: TCP 和 UDP. 1.1通信协议 通信协议也叫网络传输协议或简称为传送协议(Communications ...
- java8 lambda表达式和函数式编程
什么是函数式接口(Functional Interface) 其实之前在讲Lambda表达式的时候提到过,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法 (可以有def ...
随机推荐
- SCUT - 274 - CC B-Tree - 树形dp
https://scut.online/p/274 首先要判断是一颗树,并且找出树的直径. 是一棵树,首先边恰好有n-1条,其次要连通,这两个条件已经充分了,当然判环可以加速. 两次dfs找出直径,一 ...
- CSS-子盒子撑开父盒子,让父盒子的高随内容自适应
方法一: height:auto!important; height:200px; min-height:200px; ie6并不支持min-height.ie7,opera,火狐没有问题. 方法二: ...
- Viewer.js – 强大的JS/jQuery图片查看器
简介 Viewer.js 是一款强大的图片查看器,像门户网站一般都会有各自的图片查看器,如果您正需要一款强大的图片查看器,也许 Viewer.js 是一个很好的选择.Viewer.js 有以下特点: ...
- 处理Chrome等浏览器无法上网,但QQ能正常使用问题
常见于安装VPN软件后导致的问题: 问题描述:QQ.微信客户端.等聊天工具可以聊天,但不能使用浏览器:打开网页失败:网络连接正常 问题解决步骤: 策略1: 打开网络和共享中心>>> ...
- HTTPS证书转换成PEM格式
PEM 格式的证书文件(*.pem)一般为以下格式: 注意:PEM 格式证书文件可用 notepad++ 等文本编辑器打开. CER / CRT 格式证书转换为 PEM 格式 对于 CER / CRT ...
- python内存分析
安装 首先安装memory_profiler和psutil pip install memory_profiler pip install psutil 在需要分析的函数前面添加装饰器@profile ...
- 【技巧】Windows 10 1809无法接收1903解决方法
这都7月份了,Windows10 1903都升级的有一个月了,然而我的1809的系统一直找不到1903的更新. 虽说1903会有bug,但还是想体验一把.周围同事都更新了,心里还是痒痒的. 于是每天都 ...
- RMQ Fanout
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11795256.html RMQ Fanout Project Directory Maven Depe ...
- 修改linux的mysql用户名和密码
MySQL数据库密码忘记之后,可以进入linux下修改原始密码,步骤为下.第一步:登陆服务器管理员权限.第二步:进入MySQL数据配置文件 [root@VM_0_8_centos ~]# vi /et ...
- php prev()函数 语法
php prev()函数 语法 作用:将内部指针指向数组中的上一个元素,并输出.直线电机选型 语法:prev(array) 参数: 参数 描述 array 必需.指定需要操作的数组. 说明:如果数组包 ...