第十三章 函数式编程

函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 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;}@Overridepublic 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。

转载于:https://www.cnblogs.com/huangwenjie/p/11409664.html

20190825 On Java8 第十三章 函数式编程相关推荐

  1. 《Kotin 极简教程》第8章 函数式编程(FP)(1)

    第8章 函数式编程(FP) <Kotlin极简教程>正式上架: 点击这里 > 去京东商城购买阅读 点击这里 > 去天猫商城购买阅读 非常感谢您亲爱的读者,大家请多支持!!!有任 ...

  2. 【CSDN软件工程师能力认证学习精选】 Java8新特性学习-函数式编程(Stream/Function/Optional/Consumer)

    CSDN软件工程师能力认证是由CSDN制定并推出的一个能力认证标准,宗旨是让一流的技术人才凭真才实学进大厂拿高薪,同时为企业节约大量招聘与培养成本,使命是提升高校大学生的技术能力,为行业提供人才储备, ...

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

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

  4. Java8新特性【函数式编程API、新时间日期处理API、Optional容器类】总结

    文章目录 1.Lambda表达式 1.1什么是Lambda表达式 1.2从匿名类到 Lambda 的转换 1.3Lambda表达式语法 2.函数式接口 2.1什么是函数式接口 2.2自定义函数式接口 ...

  5. Python学习笔记__4章 函数式编程

    # 这是学习廖雪峰老师python教程的学习笔记 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程 ...

  6. Java8新特性——Lambda函数式编程

    前言    Java一直是一门面向对象语言,万事万物皆对象,如果想要调用一个函数,函数必须属于一个类或对象,然后在使用类或对象进行调用. 在Java中,"."表示调用某个对象的方法 ...

  7. Java8 用法优雅的函数式编程与stream,看这一篇就够了!

    Java 不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带 this 参数的函数. Java 平台从 Java 8 开始,支持函数式编程.函数式编程(Functional Pro ...

  8. WCF 第十三章 可编程站点 所有都与URI相关

    普及的GET 方法 表13.1中所有URIs的一件共性的事情是它们都是用HTTP协议来访问资源.HTTP协议被认为是站点协议.HTTP协议的初衷是交换HTML页,但是它已经被用来访问所有类型的资源,包 ...

  9. 第四章 函数式编程(Lambda表达式Stream流)

    一.Lambda表达式 特点:是匿名函数 2是可传递 匿名函数:需要一个函数,但又不想命名一个函数的场景下使用lambda表达式,使用lambda表达式时函数内容应该简单 可传递:将lambda表达式 ...

最新文章

  1. PHP无状态对象,(PHP)基于Token的身份验证中对无状态的理解
  2. Android测试原理概述(一)
  3. Could not load file or assembly 'System.Web.Extensions
  4. Java基础第十三天总结
  5. 裴蜀定理(note)
  6. 智能机浏览器版本信息获取
  7. Chapter4-1_Speech_Synthesis(Tacotron)
  8. Python获取当前系统时间
  9. 【JVM】GC Roots 根可达
  10. MySQL中的substr()函数
  11. winform mysql 客户端_Winform+Mysql登录
  12. Linux 服务器感染kerberods 病毒
  13. 基于微信小程序做了个交友小程序
  14. 区块链开发之Solidity编程基础(一)
  15. matlab香农编码,用MATLAB编程实现香农编码.doc
  16. 【COM编程】如何往IE工具条添加按钮
  17. 通过快递鸟如何接入韵达速递电子面单
  18. JAVA中初始化线程的两种方法_java中最简单的方式新起一个线程
  19. 2020 0414对象的多态
  20. android系统ime指令

热门文章

  1. delphi 检测网络是否连通_WebRTC:连接建立过程的网络穿透
  2. 高性能计算机储存部件硬盘,高性能计算机的磁盘系统结构.pdf
  3. PhysioToolkit 软件目录
  4. python可视化编程实战代码_Python数据可视化编程实战——导入数据
  5. android wear系统源码,android wear5.1怎么样 android wear5.1更新评测
  6. android全局计时_Android中使用定时器的三种方法
  7. Kafka 副本leader选举
  8. LeetCode 144 ——二叉树的前序遍历
  9. 【20181031T2】几串字符【数位DP思想+组合数】
  10. Intellij Idea 创建maven WebAPP项目