Java8 Stream流使用
一、简述
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API借助同样新出现的(Lambda表达式、方法引用、接口默认方法等),极大的提高编程效率和程序可读性,同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。
Stream通用语法:
二、Lambda表达式
Lambda表达式是函数式编程的一个重大特性,语法结构如下
parameter -> expression body
实际应用代码如下
- 没有参数:Runnable
() -> System.out.println("Hello world!");
- 有一个参数: Consumer
a -> System.out.println(a);
- 有两个参数:Comparator
(o1, o2) -> o1 - o2
- 有多条语句:
() -> {
System.out.println("这是第一条语句");
System.out.println("这是第二条语句");
}
针对这个 Java Lambda 表达式语法,有几个重要的特征需要说明
- 可选的参数类型声明 : 无需声明参数的类型。编译器可以从参数的值推断出相同的值。
- 可选的参数周围的小括号 () : 如果只有一个参数,可以忽略参数周围的小括号。但如果有多个参数,则必须添加小括号。
- 可选的大括号 {} : 如果 Lambda 表达式只包含一条语句,那么可以省略大括号。但如果有多条语句,则必须添加大括号。
- 可选的 return 关键字 : 如果 Lambda 表达式只有一条语句,那么编译器会自动 return 该语句最后的结果。但如果显式使用了 return 语句,则必须添加大括号 {} ,哪怕只有一条语句。
2.1 java.lang.FunctionalInterface注解
An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
If a type is annotated with this annotation type, compilers are required to generate an error message unless:
The type is an interface type and not an annotation type, enum, or class.
The annotated type satisfies the requirements of a functional interface.
However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.
大致描述下如果想自定义一个函数式接口,必须在接口上加上上FunctionalInterface。
2.2 常用函数接口
java.util.function 包中定义了大量的函数接口
接口 | 说明 |
---|---|
Consumer | 接收一个参数,无返回值场景:Iterable#forEach |
BiConsumer | 接收两个参数,无返回值场景:Map#forEach |
Predicate | 接收一个参数,返回True/false场景:Stream#filter |
Function | 接收一个参数,返回对象场景:Stream#map |
Comparator | 列表排序:List#sort |
Supplier | 惰性创建对象 |
三、方法引用
方法引用是Lambda语法的简写, 提高了代码可读性,方法引用有三种形式
- 实例::实例方法名
@Test
public void testFunctionReference() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5);
list.forEach(this::println);
}
private void println(Object object) {
System.out.println(object);
}
- 类名::静态方法名
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5);
list.sort(Integer::compare);
- 类名::实例方法名
private class Person {
String name;
String age;
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
/**
* 第一个参数用于 map ,第二个参数用于 reduce
*/
@Test
public void testMapping() {
List<Person> list = new ArrayList<>();
list.add(new Person("kk", "10"));
list.add(new Person("aa", "10"));
list.add(new Person("cc", "20"));
list.add(new Person("bb", "30"));
Map<String, List<String>> collect = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.mapping(Person::getName, Collectors.toList())));
collect.forEach((k, v) -> System.out.println("age=" + k + ",name=" + v));
}
四、接口默认方法
java 8 引入接口默认方法的初衷并不是为了解救一个接口多个实现的痛苦,而是为了向后兼容,以便旧接口也可以使用 Java 8 的 lambda 表达式。
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
// 默认方法
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 默认方法
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
五、Stream的使用
5.1 如何创建Stream?
- 由集合创建:
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();
- 由数组创建
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
5.2 中间操作
- filter:根据一个谓词来过滤元素,以流中的每一个元素作为参数,如果返回 false 则会被过滤掉。
// 执行结果:345
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
intStream.filter(i -> i > 2).forEach(System.out::println);
- skip:跳过Stream中的前几个元素
// 执行结果:2345
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
intStream.skip(1).forEach(System.out::print);
- limit:返回不超过请求大小的元素数量
// 执行结果:123
IntStream.of(1, 2, 3, 4, 5).limit(3).forEach(System.out::print);
- distinct:去除重复
// 执行结果:12345
IntStream.of(1, 2, 3, 4, 5, 3, 2).distinct().forEach(System.out::print);
- sorted:排序
// 执行结果:1223345
IntStream.of(1, 2, 3, 4, 5, 3, 2).sorted().forEach(System.out::print);
- map:将一个对象转换为另外一个对象
// 执行结果:2468
IntStream.of(1, 2, 3, 4).map(i -> i * 2).forEach(System.out::print);
- flatMap:将多个Stream连接成一个Stream,这时候不是用新值取代Stream的值,与map有所区别,这是重新生成一个Stream对象取而代之
// 执行结果:12243648
IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).forEach(System.out::print);
5.3 终端操作
Stream流终端操作是流式处理的最后一步,之前已经对Stream做了一系列的处理之后。该拿出结果了。我们可以在终端操作中实现对流的遍历、查找、归约、收集等等一系列的操作。
- forEach: 遍历流中的元素
@Override
public void forEach(Consumer<? super P_OUT> action) {
evaluate(ForEachOps.makeRef(action, false));
}
注意:由于forEach无法保证排序,所以如果使用parallelStream的时候可能导致数据乱序。
- min:获取最小值
// 执行结果:1
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).min().getAsInt());
- max:获取最大值
// 执行结果:8
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).max().getAsInt());
- count:获取元素数量
// 执行结果:8
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).count());
- toArray:获取数组
// 执行结果:12243648
int[] ints = IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).toArray();
for (int i : ints) {
System.out.print(i);
}
- anyMatch:匹配任何一个返回true
// 执行结果:true
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).anyMatch(i -> i == 2));
- allMatch:匹配所有返回true
// 执行结果:false
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).allMatch(i -> i == 2));
- noneMatch:一个都不匹配返回true
// 执行结果:false
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).noneMatch(i -> i == 2));
- findFirst:返回满足条件的第一个元素
// 执行结果: 1
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).findFirst().getAsInt());
- findAny:返回满足条件的任一个元素
// 执行结果: 1
System.out.println(IntStream.of(1, 2, 3, 4).flatMap(i -> IntStream.of(i, i * 2)).findAny().getAsInt());
- collect:对结果进行收集
// 执行结果:123
Stream.of("1", "2", "3").collect(Collectors.toList()).forEach(System.out::print);
5.4 Collectors
java.util.stream.Collectors实现各种有用的缩减操作的Collector的实现,例如将元素累积到集合中,根据各种标准汇总元素等。
- Collectors.toList
- Collectors.toSet
- Collectors.toMap:流中的所有元素导出到一个哈希表 ( Map ) 中
// 执行结果:{CC=CCCC, BB=BBBB, AA=AAAA}
// 类似Guava的Multimaps#index
System.out.println(Stream.of("AA", "BB", "CC").collect(Collectors.toMap(k -> k, v -> v + v)));
- Collectors.averagingDouble:获取平均值
// 执行结果:average:3.533333333333333
List<Double> list = Arrays.asList(1.0D, 2.2D, 3.0D, 4.0D, 5.0D, 6.0D);
Double average = list.stream().collect(Collectors.averagingDouble(d -> d));
System.out.println("average:" + average);
- Collectors.collectingAndThen:先把流中的所有元素传递给第二个参数,然后把生成的集合传递给第一个参数来处理。
// 先把 [1,2,3,4] 这个集合传递给 s-> s*s lambda 表达式,计算得出结果为 [1,4,9,16] ,然后再把 [1,4,9,16] 传递给 v->v*2 表达式,计算得出 [2,8,18,32] ,然后传递给 Collectors.averagingInt()
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Double average = list.stream().collect(Collectors.collectingAndThen(Collectors.averagingInt(v -> v), v -> 2*v));
System.out.println("average:" + average);
- Collectors.counting
- Collectors.joining
- Collectors.summingInt:获取统计信息
// 执行结果:
// max:6
// min:1
// sum:21
// count:6
// average:3.5
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
IntSummaryStatistics statistics = list.stream().collect(Collectors.summarizingInt(v -> v));
System.out.println("max:" + statistics.getMax());
System.out.println("min:" + statistics.getMin());
System.out.println("sum:" + statistics.getSum());
System.out.println("count:" + statistics.getCount());
System.out.println("average:" + statistics.getAverage());
- Collectors.mapping:先把流中的所有元素传递给第一个参数,然后使用第二个参数来处理
// 执行结果:1 2 3
Stream.of("1", "2", "3").collect(Collectors.mapping(s -> s + " ", Collectors.toList())).forEach(System.out::print);
- Collectors.groupingBy:分组,计数和排序
// 执行结果:
// age=30,name=[bb]
// age=20,name=[cc]
// age=10,name=[kk, aa]
List<Person> list = new ArrayList<>();
list.add(new Person("kk", "10"));
list.add(new Person("aa", "10"));
list.add(new Person("cc", "20"));
list.add(new Person("bb", "30"));
Map<String, List<String>> collect = list.parallelStream().collect(Collectors.groupingBy(Person::getAge, Collectors.mapping(Person::getName, Collectors.toList())));
collect.forEach((k, v) -> System.out.println("age=" + k + ",name=" + v));