第十三章 函数式编程

函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 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. Jquery中$(function(){})

    1. 在哪书写js文件 如果我们要执行一段js代码,我们该怎么办呢? 1.我们可以写一个js文件,在js文件里写执行函数,然后再<script src='...'> ... </sc ...

  2. 模板 - 可持久化无旋Treap

    空间消耗非常玄学,有多大开多大就完事了.其实是因为单次操作可能会有数次Merge和Split操作,按照下面的版本的话Merge和Split都进行复制,所以一次操作可能复制了4个版本. 四个函数式查询, ...

  3. Python 3实现网页爬虫

    1 什么是网页爬虫 网络爬虫( 网页蜘蛛,网络机器人,网页追逐者,自动索引,模拟程序)是一种按照一定的规则自动地抓取互联网信息的程序或者脚本,从互联网上抓取对于我们有价值的信息.Tips:自动提取网页 ...

  4. Log4Net 之将日志记录到数据库的后台实现 (二)

    原文:Log4Net 之将日志记录到数据库的后台实现 (二) 大家下午好,昨天讲了配置,今天我们讲讲后台实现,在完成了后台实现后,我们才能真正意义上的解决把自定义属性字段值录入到数据库中. 在开写之前 ...

  5. .NET的优点(转载)

    一:什么是.NET?它包括什么? .Net是为简化在第三代因特网的高分布式环境下的应用程序开发,基于开放互联网标准和协议之上,实现异质语言和平台高度交互性,而构建的新一代计算和通信平台. .Net主要 ...

  6. axios动态数据的获取

    跨域:前端处理.后端处理 前端方法:代理 后端加header    第一步:全局安装axios cnpm install axios --save-dev        第二步: methods:{ ...

  7. 利用tesseract-ocr进行验证码识别

    因为爬虫项目需要模拟登陆,可是有一个网站的登录需要输入验证码.其实这种登录有2种解决方案,一种是利用cookie,一种是识别图片.前者需要人工登录一次,而且有时效限制,故不太现实.后者可以,但是难点是 ...

  8. python pip安装模块报错 "Can't connect to HTTPS URL because the SSL module is not available."

    在升级python版本为3.6之后,pip安装模块报错. 报错信息如图: 原因是系统自带的openssl版本与python3的版本不匹配,所以这里只要升级openssl版本就可以解决问题. yum - ...

  9. MYSQL中IN与EXISTS的区别

    在MYSQL的连表查询中,最好是遵循‘小表驱动大表的原则’ 一.IN与EXISTS的区别1.IN查询分析SELECT   *  FROM A WHERE id IN (SELECT id FROM B ...

  10. LinkedList与ArrayList的区别(内部实现)

    ArrayList的内部实现是基于内部数组Object[],所以从概念上讲,它更像数组: LinkedList的内部实现是基于一组连接的记录,所以,它更像一个链表结构,所以,它们在性能上有很大的差别. ...