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 ...
随机推荐
- 放弃等待,故障到来:少一个 await 引发的 tcp 连接泄漏故障
更新:后来升级至 .NET Core 2.2 Preview 3 ,并将 System.Net.Http 升级至 4.3.4 之后没出现这个问题,问题与 https://github.com/dotn ...
- spark复习笔记(4):RDD变换
一.RDD变换 1.返回执行新的rdd的指针,在rdd之间创建依赖关系.每个rdd都有一个计算函数和指向父rdd的指针 Spark是惰性的,因此除非调用某个转换或动作,否则不会执行任何操作,否则将触发 ...
- Sql 语句中使用参数
using System; using System.Data; using System.Data.SqlClient; namespace ConsoleApplication2 { public ...
- webpack webpack.config.js配置
安装指定版本的webpack npm install webpack@3.6 -g 安装live-server 运行项目插件 输入live-server 运行后自动打开网页 npm ins ...
- JS合并两个函数
/** * 合并两个函数 * @param functionA 先执行 * @param functionB 执行完 functionA 后返回 * @returns {*} */ function ...
- tensor与数组转化
import tensorflow as tfimg1 = tf.constant(value=[[[[1],[2],[3],[4]],[[1],[2],[3],[4]],[[1],[2],[3],[ ...
- 前端每日实战:35# 视频演示如何把 CSS 径向渐变用得出神入化,只用一个 DOM 元素就能画出国宝熊猫
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/odKrpy 可交互视频教程 此视频 ...
- 英国已有500万宽带用户接入并开始使用IPv6技术
2018年英国首家为客户提供IPv6的主要ISP.随着所有现有的符合条件的用户线路启用,约90%的固定宽带用户群接入并开始使用IPv6,为IPv6互联网增加了超过500万个新眼球. 英国IPv6项目于 ...
- Python实例教程
转自:http://codingdict.com/article/9026 Python 100例-01 题目: 输有1.2.3.4个数字,能组成多少个互不相同且无重复数字的三位数? Python 1 ...
- OUC-NULL -凡事遇则立
[OUC-NULL-凡事遇则立] 一.项目的GITHUB地址 https://github.com/OUC-null/null- 二.对遇到的问题思考及总结 一开始进度较慢,大家一开始也没太找到前进的 ...