最近真的是太忙忙忙忙忙了,好久没有更新文章了。最近工做中看到了几段关于函数式编程的代码,可是有点费解,因而就准备总结一下函数式编程。不少东西很简单,可是若是不总结,可能会被它的各类变体所困扰。接触Lambda表达式已经好久了,可是也一直是处于照葫芦画瓢的阶段,因此想本身去编写相关代码,也有些捉襟见肘。java
// 基本形式 参数 -> 主体
Runnable noArguments = () -> System.out.println("Hello World");
该形式的Lambda表达式不包含参数,使用空括号()表示没有参数。它实现了Runnable接口,该接口也只有一个run方法,没有桉树,且返回类型为void。程序员
ActionListener oneArgument = event -> System.out.println("button clicked");
该形式的Lambda表达式包含且只包含一个参数,可省略参数的符号。编程
Runnable multiStatement = () -> { System.out.print("Hello"); System.out.println(" World"); };
Lambda表达式的主体不只可使一个表达式,并且也能够是一段代码块,使用大括号{}将代码块括起来。该代码块和普通方法遵循的规则别无二致,能够用返回或抛出异常来退出。只有以行代码的Lambda表达式也可使用大括号,用以明确Lambda表达式从何处开始,到哪里结束。编程语言
BinaryOperator<Long> add = (x, y) -> x + y;
Lambda表达式也能够表示包含多个参数的方法,上面的Lambda表达式并非将两个数字相加,而是建立了一个函数,用来计算两个数字相加的结果。变量add的类型时BinaryOperator
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
到目前为止,全部Lambda表达式中的参数类型都是由编译器推断得出的。但有时最好也能够显示声明参数类型,此时就须要使用小括号将参数括起来,多个参数的状况也是如此。函数
若是你曾使用过匿名内部类,也许遇到过这样的状况:须要引用它所在方法里的变量。这是,须要将变量声明为final。设计
final String name = getUserName(); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("hi " + name); } });
将变量声明为 final,意味着不能为其重复赋 值。同时也意味着在使用 final 变量时,其实是在使用赋给该变量的一个特定的值。code
Java 8 虽然放松了这一限制,能够引用非 final 变量,可是该变量在既成事实上必须是 final(意思就是你不能再次对该变量赋值)。虽然无需将变量声明为 final,但在 Lambda 表达式中,也没法用做非终态变量。如 果坚持用做非终态变量,编译器就会报错。 既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值, 而不是变量。orm
例如:对象
String name = getUserName(); button.addActionListener(event -> System.out.println("hi " + name));
在 Java 里,全部方法参数都有固定的类型。假设将数字 3 做为参数传给一个方法,则参数 的类型是 int。那么,Lambda 表达式的类型又是什么呢?
使用只有一个方法的接口来表示某特定方法并反复使用,是很早就有的习惯。使用 Swing 编写过用户界面的人对这种方式都不陌生,这里无需再标新立异,Lambda 表达式也使用一样的技巧,并将这种接口称为函数接口。
接口中单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配便可。可在函数接口中为参数起一个有意义的名字,增长代码易读性,便于更透彻 地理解参数的用途。
接口 | 参数 | 返回类型 | 示例 |
---|---|---|---|
Predicate
|
T | boolean | 判断是否 |
Consumer
|
T | void | 输出一个值 |
Function<T,R> | T | T | 得到对象的名字 |
Supplier
|
None | T | 工厂方法 |
UnaryOperator
|
T | T | 逻辑非(!) |
BinaryOperator
|
(T, T) | T | 求两个数的乘积(*) |
定义函数接口须要使用到注解@FunctionalInterface
例如:
@FunctionalInterface public interface MyFuncInterface { void print(); }
使用:
public class MyFunctionalInterfaceTest { public static void main(String[] args) { doPrint(() -> System.out.println("java")); } public static void doPrint(MyFuncInterface my) { System.out.println("请问你喜欢什么编程语言?"); my.print(); } }
说明:
这只是一个很简单的例子,有人以为为何要搞这么复杂,去定义一个接口?这个问题仍是读者在平时的工做中去感悟吧,总之,先学会怎么用它。不至于看了别人写的代码都看不懂。
至于我我的的理解,能够简单聊聊。之前写过JavaScript,里面有一种语法就是将自定义函数B做为参数传递到另一个函数A里面,在函数A里面会执行你自定义的函数B逻辑,我当时就很是喜欢这种特性,由于每一个人关于函数B的实现可能不同,亦或者场景不同也会致使函数B的实现不同。我以为Java8的这个函数式编程就是对这一特性的补充。
流的经常使用操做有不少,例如collect(toList())
、map
、filter
、max
、min
等,下面介绍一下flatMap
和reduce
。
flatMap 方法可用 Stream 替换值,而后将多个 Stream 链接成一个 Stream。
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) .flatMap(numbers -> numbers.stream()) .collect(toList()); assertEquals(asList(1, 2, 3, 4), together);
调用 stream 方法,将每一个列表转换成 Stream 对象,其他部分由 flatMap 方法处理。 flatMap 方法的相关函数接口和 map 方法的同样,都是 Function 接口,只是方法的返回值 限定为 Stream 类型罢了。
reduce 操做能够实现从一组值中生成一个值。对于 count、min 和 max 方 法,由于经常使用而被归入标准库中。事实上,这些方法都是 reduce 操做。
如何经过 reduce 操做对 Stream 中的数字求和。以 0 做起点——一个空Stream 的求和结果,每一步都将 Stream 中的元素累加至 accumulator,遍历至 Stream 中的 最后一个元素时,accumulator 的值就是全部元素的和。
int count = Stream.of(1, 2, 3) .reduce(0, (acc, element) -> acc + element); assertEquals(6, count);
Lambda 表达式的返回值是最新的 acc,是上一轮 acc 的值和当前元素相加的结果。reducer 的类型是前面已介绍过的 BinaryOperator。
reduce 方法的一个重点还没有说起:reduce 方法有两种形式,一种如前面出现的须要有一 个初始值,另外一种变式则不须要有初始值。没有初始值的状况下,reduce 的第一步使用 Stream 中的前两个元素。有时,reduce 操做不存在有意义的初始值,这样作就是有意义的,此时,reduce 方法返回一个 Optional 对象。
Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们对原有的 null 值有不少抱怨。人们经常使用 null 值表示值不存在,Optional 对象能更好地表达这个概念。使用 null 代 表值不存在的最大问题在于 NullPointerException。一旦引用一个存储 null 值的变量,程 序会当即崩溃。使用 Optional 对象有两个目的:首先,Optional 对象鼓励程序员适时检查变量是否为空,以免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单不少。
下面咱们举例说明 Optional 对象的 API,从而切身体会一下它的使用方法。使用工厂方法 of,能够从某个值建立出一个 Optional 对象。Optional 对象至关于值的容器,而该值能够 经过 get 方法提取。
Optional<String> a = Optional.of("a"); assertEquals("a", a.get());
Optional 对象也可能为空,所以还有一个对应的工厂方法 empty,另一个工厂方法 ofNullable 则可将一个空值转换成 Optional 对象。下面的代码同时展现 了第三个方法 isPresent 的用法(该方法表示一个 Optional 对象里是否有值)。
Optional emptyOptional = Optional.empty(); Optional alsoEmpty = Optional.ofNullable(null); assertFalse(emptyOptional.isPresent());
使用 Optional 对象的方式之一是在调用 get() 方法前,先使用 isPresent 检查 Optional 对象是否有值。使用 orElse 方法则更简洁,当 Optional 对象为空时,该方法提供了一个 备选值。若是计算备选值在计算上太过繁琐,便可使用 orElseGet 方法。该方法接受一个 Supplier 对象,只有在 Optional 对象真正为空时才会调用。
assertEquals("b", emptyOptional.orElse("b")); assertEquals("c", emptyOptional.orElseGet(() -> "c"));