一、概述
大家应该都知道面向对象的特性(抽象、封装、继承、多态)。其实在 Java8
出现之前,我们关注的往往是某一类对象应该具有什么样的属性,当然这也是面向对象的核心–对数据进行抽象。但是java8
出现以后,这一点开始出现变化,在某种场景下,更加关注某一类共有的行为(这与之前的接口有些类似),这也就是java8
提出函数式编程的目的。
面向对象编程到面向行为编程的变化
二、什么是 Lambda
我们知道,对于一个Java
变量,我们可以赋给其一个**“值”**。
如果你想把**“一块代码”**赋给一个Java
变量,应该怎么做呢?
比如,我想把右边那块代码,赋给一个叫做aBlockOfCode
的Java
变量:
在Java 8
之前,这个是做不到的。但是Java 8
问世之后,利用Lambda
特性,就可以做到了。
当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant
, 我们可以移除一些没用的声明。
这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。
但是这里仍然有一个问题,就是变量aBlockOfCode
的类型应该是什么?
在Java 8
里面,**所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。**这是我认为理解Lambda
的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode
加上一个类型:
这种只有**一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。**为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface
, 这样别人就无法在里面添加新的接口函数了:
这样,我们就得到了一个完整的Lambda
表达式声明:
三、Lambda 表达式作用
最直观的作用就是使得代码变得异常简洁。
我们可以对比一下Lambda
表达式和传统的Java
对同一个接口的实现:
这两种写法本质上是等价的。但是显然,Java 8
中的写法更加优雅简洁。并且,由于Lambda
可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行:
有些情况下,这个接口实现只需要用到一次。传统的Java 7
必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl
,而相较之下Java 8
的Lambda
, 就显得干净很多。
四、Lambda 结合新特性
Lambda
结合FunctionalInterface Lib
, forEach
, stream()
,method reference
等新特性可以使代码变的更加简洁!
直接上例子。
假设Person
的定义和List<Person>
的值都给定。
现在需要你打印出guiltyPersons List
里面所有LastName
以"Z"
开头的人的FirstName
。
原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda
表达式。
这个代码实际上已经比较简洁了,但是我们还可以更简洁么?
当然可以。在Java 8
中有一个函数式接口的包,里面定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8 ))。所以,我们在这里压根都不需要定义NameChecker
和Executor
这两个函数式接口,直接用Java 8
函数式接口包里的Predicate<T>
和Consumer<T>
就可以了——因为他们这一对的接口定义和NameChecker/Executor
其实是一样的。
第一步简化 - 利用函数式接口包:
静态函数里面的for each
循环其实是非常碍眼的。这里可以利用Iterable
自带的forEach()
来替代。forEach()
本身可以接受一个Consumer<T>
参数。
第二步简化 - 用Iterable.forEach()取代foreach loop:
由于静态函数其实只是对List
进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()
特性来完成。stream()
的几个方法都是接受Predicate<T>
,Consumer<T>
等参数的(java.util.stream (Java Platform SE 8 ))。你理解了上面的内容,stream()
这里就非常好理解了,并不需要多做解释。
第三步简化 - 利用stream()替代静态函数:
对比最开始的Lambda
写法,这里已经非常非常简洁了。但是如果,我们的要求变一下,变成print
这个人的全部信息,及p -> System.out.println(p);
那么还可以利用Method reference
来继续简化。所谓Method reference
, 就是用已经写好的别的Object/Class
的method
来代替Lambda expression
。格式如下:
第四步简化 - 如果是println§,则可以利用Method reference代替forEach中的Lambda表达式:
这基本上就是能写的最简洁的版本了。
五、Lambda 配合 Optional
Lambda
配合Optional<T>
可以使Java
对于null
的处理变的异常优雅
这里假设我们有一个person object
,以及一个 person object
的 Optional wrapper
。
Optional<T>
如果不结合Lambda
使用的话,并不能使原来繁琐的null check
变的简单。
只有当Optional<T>
结合Lambda
一起使用的时候,才能发挥出其真正的威力!
我们现在就来对比一下下面四种常见的null
处理中,Java 8
的Lambda+Optional<T>
和传统Java
两者之间对于null
的处理差异。
1、ifPresent
值不为null,则执行方法。
2、orElse
值不为null则返回值,否则返回自定义的值。
3、orElseGet
值不为null则返回值,否则由函数产生返回值。
4、map
连续判断非空。
由上述四种情况可以清楚地看到,Optional<T>+Lambda
可以让我们少写很多ifElse
块。尤其是对于情况四那种夺命连环null
检查,传统java
的写法显得冗长难懂,而新的Optional<T>+Lambda
则清新脱俗,清楚简洁。
关于Java
的Lambda
, 还有东西需要讨论和学习。比如如何handle lambda exception
,如何利用Lambda
的特性来进行parallel processing
等。
参考链接:
https://www.zhihu.com/question/20125256/answer/324121308
六、函数式接口
关于接口的变动,Java8
中新定义了一种接口类型,函数式接口,与其他接口的区别就是:
- 函数式接口中只能有一个抽象方法(我们在这里不包括与Object的方法重名的方法);
- 可以有从Object继承过来的抽象方法,因为所有类的最终父类都是Object;
- 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持
Lambda
表达式。
Java8
之前已经存在的函数式接口有:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
另外,Java8
还提供了@FunctionalInterface
注解来帮助我们标识函数式接口。另外需要注意的是函数式接口的目的是对某一个行为进行封装,某些接口可能只是巧合符合函数式接口的定义。
如图所示,为java8
的Function
包的结构(即新引入的函数式接口),图中绿色表示主要引入的新接口,其他接口基本上都是为了支持基本类型而添加的接口,方法的具体作用图中有具体说明。
关于更多的Lambda表达式如何让使用这里就不做过多介绍,可以参考**菜鸟编程**。
下面来说说Function接口。
在JDK8中共提供43个现有的函数接口,可以满足基本的开发需要,如果需要特别的函数接口可以自定义,使用@FunctionalInterface注解。
总共分为6大类:
- Function:(函数)代表其参数与返回类型不一致的函数(17个接口)。Lambda中Function函数接口(二)——Function系列接口
- UnaryOperator:(一元操作)表示结果和参数类型一致的函数(4个)。Lambda中Function函数接口(三)——Operator系列接口
- BinaryOperator:(二元操作)表示结果和参数类型一致的函数(4个)。Lambda中Function函数接口(三)——Operator系列接口
- Predicate:(断言)该接口代表传入一个参数并返回boolean的函数(5个)。Lambda中Function函数接口(四)——Predicate系列接口
- Supplier:(供应者)表示没有输入参数,但返回一个值(5个)。Lambda中Function函数接口(五)——Supplier系列接口
- Consumer:(消费者)表示传入一个参数,但不返回任何值(8个)。Lambda中Function函数接口(六)——Consumer系列接口
JDK8中所有函数式接口
Function | UnaryOperator | BinaryOperator | Predicate | Supplier | Consumer |
---|---|---|---|---|---|
BiFunction | IntUnaryOperator | IntBinaryOperator | BiPredicate | IntSupplier | BiConsumer |
IntFunction | LongUnaryOperator | LongBinaryOperator | IntPredicate | LongSupplier | IntConsumer |
LongFunction | DoubleUnaryOperator | DoubleBinaryOperator | LongPredicate | DoubleSupplier | LongConsumer |
DoubleFunction | DoublePredicate | BooleanSupplier | DoubleConsumer | ||
IntToLongFunction | ObjIntConsumer | ||||
IntToDoubleFunction | ObjLongConsumer | ||||
LongToIntFunction | ObjDoubleConsumer | ||||
LongToDoubleFunction | |||||
DoubleToIntFunction | |||||
DoubleToLongFunction | |||||
ToIntFunction | |||||
ToIntBiFunction | |||||
ToLongFunction | |||||
ToLongBiFunction | |||||
ToDoubleFunction | |||||
ToDoubleBiFunction |
七、案例
1、排序
//1、单条件升序:
list.sort(Comparator.comparing(User::getId);
list.sort(Comparator.comparing(m-> (int)m.get("age")-(int)m.get("age")));
list.sort((m1, m2) -> (int) m1.get("age") - (int) m2.get("age"));
//2、降序:
list.sort(Comparator.comparing(User::getId).reversed());
//3、多条件升序排序:
list.sort(Comparator.comparing(User::getId).thenComparing(User::getName));
//4、用 Collections.reverse 反转一个 List
list.sort(Comparator.comparing(User::getId);
Collections.reverse(list);
//等价于
list.sort(Comparator.comparing(User::getId).reversed());
2、合并 Map.merge()
(1)说明
1、当map
中不存在指定的key
时,便将传入的newValue
设置为key
的值。
2、当key存在值时,则执行一个方法;该方法有2个参数,分别为key对应的旧值
和传入的新值
。
3、执行完自定义的方法,将返回值
设置为key的值
。
(2)案例
Map map = new HashMap();
map.put("id", 1);
map.put("score", 80);
int key = 2;
int newValue = 90;
map.merge(key, newValue, (oldValue, newValue) -> {return 99;});
(3)源码
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
(4)使用场景
现在有一个学生各科成绩的集合,要统计每个学生的总成绩
public static Map<Integer, Integer> sum2(List<StudentScore> list) {
Map<Integer, Integer> map = new HashMap<>();
list.stream().forEach(studentScore -> map.merge(studentScore.getSid()
, studentScore.getScore(), Integer::sum));
return map;
}
3、合并map
// 使用concat合并
Map<Object, Object> map = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1));
// 使用flatMap合并流
Map<String, Object> map = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream()).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1));
// 利用自定义map,合并
Map<String, Object> map = map1.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1, () -> {
return map2;
}));