第十三章 函数式编程

函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式方法引用 (Method References) 允许你以函数式编程。

OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。

Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。

没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。

Lambda表达式

Lambda 表达式是使用最小可能语法编写的函数定义:

  1. Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。
  2. Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。

任何 Lambda 表达式的基本语法是:

  1. 参数。
  2. 接着 ->,可视为“产出”。
  3. -> 之后的内容都是方法体。

递归

递归函数是一个自我调用的函数。可以编写递归的 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 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。

以下是基本命名准则:

  1. 如果只处理对象而非基本类型,名称则为 FunctionConsumerPredicate 等。参数类型通过泛型添加。
  2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumerDoubleFunctionIntPredicate 等,但基本 Supplier 类型例外。
  3. 如果返回值为基本类型,则用 To 表示,如 ToLongFunction <T>IntToLongFunction
  4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator
  5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
  6. 如果接收的两个参数类型不同,则名称中有一个 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 第十三章 函数式编程的更多相关文章

  1. Java8内置的函数式编程接口应用场景和方式

    首先,我们先定义一个函数式编程接口 @FunctionalInterface public interface BooleanFunctionalInterface<T> { boolea ...

  2. 《Clojure编程》笔记 第2章 函数式编程

    目录 背景简述 第2章 函数式编程 背景简述 本人是一个自学一年Java的小菜鸡,理论上跟大多数新手的水平差不多,但我入职的新公司是要求转Clojure语言的.坊间传闻:通常情况下,最好是有一定Jav ...

  3. [译]Java8:循环与函数式编程

    Java8函数式编程的加入彻底改变了游戏规则.对Java开发者来说这是一个全新的世界,我们也需要做出相应的改变. 在这篇文章中我们将找寻传统循环代码的可替代方案.Java8的函数式编程特性改变了编程思 ...

  4. [译]java8新特性:函数式编程(functional programming)的优点

    Java8引入了函数式编程,他对java是一个极大的扩展.Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数式编程.这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化. 但是为 ...

  5. 20190928 On Java8 第二十三章 注解

    第二十三章 注解 定义在 java.lang 包中的5种标准注解: @Override:表示当前的方法定义将覆盖基类的方法.如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示. ...

  6. Java8新特性:函数式编程

    1. 概述 函数式编程学习目的: 能够看懂公司里的代码 大数据量下处理集合效率更高 代码可读性高 消灭嵌套地狱 函数式编程思想: 面向对象思想需要关注用什么对象完成什么事情.而函数式编程思想就类似于我 ...

  7. python3 第二十三章 - 函数式编程之Partial function(偏函数)

    要注意,这里的偏函数和数学意义上的偏函数不一样,偏函数是2.5版本以后引进来的东西,属于函数式编程的一部分.前面章节中我们讲到,通过设定参数的默认值,可以降低函数调用的难度.而偏函数也可以做到这一点. ...

  8. Python第十三章-网络编程

    网络编程 一.网络编程基础 python 的网络编程模块主要支持两种Internet协议: TCP 和 UDP. 1.1通信协议 通信协议也叫网络传输协议或简称为传送协议(Communications ...

  9. java8 lambda表达式和函数式编程

    什么是函数式接口(Functional Interface) 其实之前在讲Lambda表达式的时候提到过,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法 (可以有def ...

随机推荐

  1. SCUT - 274 - CC B-Tree - 树形dp

    https://scut.online/p/274 首先要判断是一颗树,并且找出树的直径. 是一棵树,首先边恰好有n-1条,其次要连通,这两个条件已经充分了,当然判环可以加速. 两次dfs找出直径,一 ...

  2. CSS-子盒子撑开父盒子,让父盒子的高随内容自适应

    方法一: height:auto!important; height:200px; min-height:200px; ie6并不支持min-height.ie7,opera,火狐没有问题. 方法二: ...

  3. Viewer.js – 强大的JS/jQuery图片查看器

    简介 Viewer.js 是一款强大的图片查看器,像门户网站一般都会有各自的图片查看器,如果您正需要一款强大的图片查看器,也许 Viewer.js 是一个很好的选择.Viewer.js 有以下特点: ...

  4. 处理Chrome等浏览器无法上网,但QQ能正常使用问题

    常见于安装VPN软件后导致的问题: 问题描述:QQ.微信客户端.等聊天工具可以聊天,但不能使用浏览器:打开网页失败:网络连接正常 问题解决步骤: 策略1: 打开网络和共享中心>>> ...

  5. HTTPS证书转换成PEM格式

    PEM 格式的证书文件(*.pem)一般为以下格式: 注意:PEM 格式证书文件可用 notepad++ 等文本编辑器打开. CER / CRT 格式证书转换为 PEM 格式 对于 CER / CRT ...

  6. python内存分析

    安装 首先安装memory_profiler和psutil pip install memory_profiler pip install psutil 在需要分析的函数前面添加装饰器@profile ...

  7. 【技巧】Windows 10 1809无法接收1903解决方法

    这都7月份了,Windows10 1903都升级的有一个月了,然而我的1809的系统一直找不到1903的更新. 虽说1903会有bug,但还是想体验一把.周围同事都更新了,心里还是痒痒的. 于是每天都 ...

  8. RMQ Fanout

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11795256.html RMQ Fanout Project Directory Maven Depe ...

  9. 修改linux的mysql用户名和密码

    MySQL数据库密码忘记之后,可以进入linux下修改原始密码,步骤为下.第一步:登陆服务器管理员权限.第二步:进入MySQL数据配置文件 [root@VM_0_8_centos ~]# vi /et ...

  10. php prev()函数 语法

    php prev()函数 语法 作用:将内部指针指向数组中的上一个元素,并输出.直线电机选型 语法:prev(array) 参数: 参数 描述 array 必需.指定需要操作的数组. 说明:如果数组包 ...