Java 8
Lambda
“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。
闭包的概念: 可以把闭包简单理解成”定义在一个函数内部的函数体”,并且在内部函数体中能访问在外部函数中定义的变量
lambda的语法为: expression
= (variable) -> action
, 例如
Runnable r = () -> { log.info"HelloWorld";}
int sum = (x,y) -> x+y;
- 等号的右边即是一个lambda表达式
Lambda表达式要点总结
- lambda表达式可以用于以下几个情况:
- 有单个抽象方法的类, 比如一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式;
- 使用了 @FunctionalInterface 注释的函数式接口,比如
java.util.function
包下面的Predicate、Function、Consumer 或 Supplier, BinaryOperator- 例如ArrayList的
forEach(Consumer<E> action)
方法的形参是Consumer类型, 可以接受一个lambda表达式做实参; - 例如Collection的stream()返回一个Stream, Stream类的
filter()
,map()
的形参分别是Predicate和Function;
- 例如ArrayList的
- lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。例如
list.forEach(System.out::println)
- Lambda表达式在Java中又称为闭包或匿名函数
- Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大致应该长这样:
private static java.lang.Object lambda$0(java.lang.String);
- lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。
- lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量,读取是可以的但不能修改。
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });
创建匿名类
例1:
new Thread(new Runnable() { |
例2: 你们最讨厌的Comparator接口
Comparator<Score> byName = new Comparator<Score>() { |
表达式迭代 forEach
List list = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); |
map() & reduce()
// 为每个订单的价格加上12$的税, 并求和 |
函数式接口
什么是函数式接口? 简单说就是只拥有一个抽象方法的接口,如Runnable
Function功能型函数式接口
类 java.util.function.Function<T,R>
相当于仅含有一个方法的接口类, 这个方法接收一个参数T, 返回类型R.
在Java8中, 这种接口类可以用一个lambda表达式来表示.
Function只有一个方法apply, 该方法接收一个参数并返回一个值:
Function<Integer, Integer> func = x -> x*2; |
除了👆上面这种形式, 在Java8中还增加了::
, 称为”方法引用操作符”, 对象::方法
将返回一个函数接口(function interface),
我们可以使用它来引用类的方法. 例如:
class MyMath{ |
注意被
::
引用的方法需要符合“函数式接口” (一个输入参数一个返回值)
Predicate断言型函数式接口
类 java.util.function.Predicate<T>
相当于一个”接收一个输入参数T, 返回boolean的lambda表达式”类型 :
Predicate<Integer> pred = x -> x>5; |
使用::
方法引用操作符:
Set<String> set = new HashSet<>(); |
Predicate.test()的更多例子:
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); |
Predicate.and(), or(), xor()的例子:
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); |
Consumer消费型函数式接口
@todo
Supplier供给型函数式接口
@todo
Stream API
stream并不是某种数据结构,它只是数据源的一种视图。这里的数据源可以是一个数组,Java容器或I/O channel等。正因如此要得到一个stream通常不会手动创建,而是调用对应的工具方法,比如:
- 调用
Collection.stream()
或者Collection.parallelStream()
方法 - 调用
Arrays.stream(T[] array)
方法 - Map类容器无法直接用
stream()
, 但可以使用map.entrySet().stream()
获得流
常见的stream接口继承关系如图:
流(Stream)的特性
大部分情况下Stream是容器调用Collection.stream()
方法得到的,但Stream和Collections有以下不同:
- 无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
- 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
- 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
- 可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
中间操作 & 结束操作
对stream的操作分为为两类,中间操作(intermediate operations)和结束操作(terminal operations),二者特点是:
- 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。
- 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。
下表汇总了Stream接口的部分常见方法:
operator | function |
---|---|
中间操作 | concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered() |
结束操作 | allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
中间操作
filter
filter(): 函数原型为Stream<T> filter(Predicate<? super T> predicate)
,作用是返回一个只包含满足predicate条件元素的Stream。
predicate 可以看成是返回boolean的lambda表达式
下面例子中, filter方法接收一个predicate
类型的参数:
// 保留长度等于3的字符串 |
下面例子中, filter接收的参数是list2::contains
, 被引用的方法(这里的contain方法)需符合“predicate”原型:
// 求list1和list2的交集 |
distinct
distinct(): 函数原型为Stream<T> distinct()
,作用是返回一个去除重复元素之后的Stream。
stream.distinct() |
limit & skip
limit(n)/skip(n): limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素
sort
sorted(): 排序函数有两个,一个是用自然顺序排序,一个是使用自定义比较器排序,函数原型分别为Stream<T> sorted()
和Stream<T> sorted(Comparator<? super T> comparator)
。
stream().sorted((x, y) -> x-y ).collect(Collectors.toList()); |
map
map(): 对当前Stream所有元素执行mapper操作, 返回新的Stream
stream.map(str -> str.toUpperCase()) |
flatMap
flatMap(): “摊平”
// 把List<Int> 摊平成 Int |
结束操作
结束操作包括collect, reduce, forEach等, 分别用于聚合和遍历.
forEach
forEach是结束操作, 会立刻执行, 执行结束后Stream失效.
方法定义为void forEach(Consumer<? super E> action)
,作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。
通常我们在使用forEach时, 也会用来做合并操作。
使用Stream.forEach()迭代
Stream<String> stream = Stream.of("Java", "Scala", "C++", "Haskell", "Lisp");
stream.forEach(str -> System.out.println(str));在forEach中进行合并:
// Combine map1 and map2
// Map.merge()用于相同k的合并
Map<String,Integer> mergedMap = new HashMap(map1);
map2.forEach((k,v) -> mergedMap.merge(k,v, Integer::Sum));
reduce()
规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作。
Stream类库有两个通用的规约操作reduce()
和collect()
,也有一些为简化书写而设计的专用规约操作,比如sum()
、max()
、min()
、count()
等。
其原型为:Optional<T> reduce(BinaryOperator<T> accumulator)
reduce()
最常用的场景就是从一组值中生成一个值,reduce()
的方法定义有三种重写形式:
Optional<T> reduce(BinaryOperator<T> accumulator)
: 返回的类型Optional
表示(一个)值的容器,使用它可以避免null值的麻烦。// 找出最长的单词
Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
System.out.println(longest.get());T reduce(T identity, BinaryOperator<T> accumulator)
:int[] array = {23,43,56,97,32};
// 求所有元素的和:
Integer sum = Arrays.stream(array).reduce(0, (a, b) -> a+b);
// 等价于:
Integer sum = Arrays.stream(array).reduce(0, Integer::sum);<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
:
它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。// 求单词长度之和
Stream<String> stream = Stream.of("Java", "Scala", "C++", "Haskell", "Lisp");
Integer lengthSum = stream.reduce(0, // 初始值
(sum, str) -> sum+str.length(), // 累加器
(a, b) -> a+b); // 部分和拼接器,并行执行时才会用到更多
reduce()
的例子:// 求最小值, 有起始值
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 字符串连接,有起始值, concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 字符串连接,有起始值, 有filter操作, concat = "ace"
String concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
collect()
Stream.collect()
方法和类Collectors
一起使用, 常用于把一个Stream的结果收集进容器里,
考虑一下将一个Stream转换成一个容器(或者Map)需要做哪些工作?我们至少需要两样东西:
- 目标容器是什么?是ArrayList还是HashSet,或者是个TreeMap。
- 新元素如何添加到容器中?是List.add()还是Map.put()。
- 如果并行的进行规约,还需要告诉collect() 多个部分结果如何合并成一个。
结合以上分析,collect()方法定义为
<R> R collect(Supplier<R> supplier, |
三个参数依次对应上述三条分析。不过每次调用collect()都要传入这三个参数太麻烦,收集器Collectors就是对这三个参数的简单封装,所以collect()的另一定义为<R,A> R collect(Collector<? super T,A,R> collector)
。
一些例子:
将Stream规约成List
Stream<String> stream = Stream.of("Java", "Scala", "C++", "Haskell", "Lisp");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);// 方式1
List<String> list = stream.collect(Collectors.toList());// 方式2
System.out.println(list);将Stream转换成List 或Set
Stream<String> stream = Stream.of("Java", "Scala", "C++", "Haskell", "Lisp");
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());Stream转换成map & map排序:
// Stream转换成map:
// Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t形式的Lambda表达式。
Map<Integer, String> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
// map排序 & 取TopN:
// 对Entry的流进行排序, 然后生成有序的LinkedHashMap:
Map<String ,Long> sortedMap = unsortedMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(topN)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldValue, newValue) -> oldValue,
LinkedHashMap::new));合并Map1, Map2
Map<String, Integer> mx = Stream.of(m1, m2)
.map(Map::entrySet) // converts each map into an entry set
.flatMap(Collection::stream) // converts each set into an entry stream, then
// "concatenates" it in place of the original set
.collect(
Collectors.toMap( // collects into a map
Map.Entry::getKey, // where each entry is based
Map.Entry::getValue, // on the entries in the stream
Integer::max // such that if a value already exist for
// a given key, the max of the old
// and new value is taken
)
);拼接字符串
Stream<String> stream = Stream.of("Java", "Scala", "C++", "Haskell", "Lisp");
String mergedString = stream.filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));上述代码能够满足大部分需求,但由于返回结果是接口类型,我们并不知道类库实际选择的容器类型是什么,有时候我们可能会想要人为指定容器的实际类型,这个需求可通过
Collectors.toCollection(Supplier<C> collectionFactory)
方法完成。// 使用toCollection()指定规约容器的类型
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)
Stream的底层实现
- stream(): Stream只是一个接口,并没有操作的缺省实现。最主要的实现是ReferencePipeline,而它的一些具体实现又是由AbstractPipeline完成的
- parrallelStream(): 底层使用的是ForkJoinPool, 比较适合使用计算密集型并且没有I/O的任务
新的Data & Time
@ref: Java 8 新特性概述
Java 9
@ref: Java 9 新特性概述
Java 10
@ref: Java 10 新特性介绍