Stream
流(stream)是Java8中的一个重要特性,利用匿名函数和流可以极大简化一些繁琐的操作。
# Lambda Function
# 一般形式
lambda的一般形式,由两部分组成:
(par1, par2···) -> {
// code block
return xxx;
}
(par1, par2···)
表示参数,类型可以省略,因为编译器可以自动推断出参数类型,{···}
中间的内容表示方法体,就是要执行的代码。
如果方法体中只有一行return xxx
的代码,那么完全可以更加简洁:
(par1, par2···) -> xxx;
比如下面的例子:
(s1, s2) -> {
return s1.equals(s2);
}
// 等同于
(s1, s2) -> s1.equals(s2);
函数返回值的类型也是由编译器自动判断的。
# FunctionalInterface
由于lambda创建了一个匿名函数,我们可以把这个函数交给一个变量,然后利用这个变量去执行我们写好的函数,比如在python中,我们可以这样做:
In [1]: func = lambda x : x+2
In [2]: print(func(1))
3
但由于Java是强类型语言,在基本类型和引用类型中没有一个用来表示“这个变量是一个函数”的类型,但是Java8为我们提供一种函数接口
,一个lambda表达式可以赋值给一个函数接口的对象,然后利用这个对象去调用lambda表达式。
我们可以使用注解@FunctionalInterface
来定义一个函数接口,比如这样:
@FunctionalInterface
interface LambdaFunc {
boolean strCompare(String s1, String s2);
}
public class Demo1 {
public static void main(String[] args) {
LambdaFunc test = (s1, s2) -> s1.equals(s2);
System.out.println(test.strCompare("123", "456"));
}
}
/*
false
Process finished with exit code 0
*/
一个函数接口具有一下特点:
- 函数接口是单(抽象)方法接口
- 接口中抽象方法的返回值就是我们所写的lambda表达式中函数体的返回值
- 接口中抽象方法的参数就是我们所写的lambda表达式中的参数
- 只有显式调用
函数接口对象.抽象方法
并传入相应的参数才会调用对应的lambda表达式
其实有些函数接口我们没有必要自己实现,Java8中的java.util.function
包中给我们提供了很多的函数接口,比如下面这样:
public class Demo1 {
public static void main(String[] args) {
BiPredicate<String, String> function = (String s1, String s2) -> s1.equals(s2);
System.out.println(function.test("123", "456"));
}
}
/*
false
Process finished with exit code 0
*/
BiPredicate
就是一个function
包中内置的一个函数接口,它的抽象方法会接受两个参数,并且返回布尔值:
# 方法引用
上面的例子还可以进一步化简:
public class Demo1 {
public static void main(String[] args) {
BiPredicate<String, String> function = String::equals;
System.out.println(function.test("123", "456"));
}
}
/*
false
Process finished with exit code 0
*/
我们把(String s1, String s2) -> s1.equals(s2)
简化成了String::equals
,这种简化方式称作方法引用,所谓方法引用,就是某个方法的签名和接口的抽象方法签名恰好一致,那么就可以直接把方法引用作为lambda表达式传入接口。
s -> System.out.println(s) <==> System.out::println
# Stream
Java 8 STREAMS Tutorial - YouTube (opens new window)
# Steam介绍
Stream(流)是Java8中引入的全新API
Steam本身并不会保存数据,而是类似于一个比较高级的迭代器,数据源中的元素单向传递到流中,不可以往复,只能遍历一次,就像水流一样一去不复返。
在图中涉及到了两个名词:Intermediate operations和Ternimal operations,这是流中的两个重要操作。
Stream分为中间操作(Intermediate operations)和终端操作(Ternimal operations):
- 中间操作用于对流进行过滤、转换、排序等,比如
filter
、map
、sort
,多个中间操作可以被串联起来,一个流经过中间操作之后还是一个流。 - 终端操作用于将流转换为其他Java对象,比如
forEach
、collect
、reduce
等。
一个流中可以有零个或者多个中间操作,对于比较大的数据集:我们应该遵循的一个原则是先使用filter
将其过滤,然后再使用其他的中间操作比如map
、sort
等。
一个流只能有一个终端操作,一些常用的终端操作作用如下:
forEach
:对于对于每个流中的元素使用相同的函数collect
:将流中的元素汇总到一个集合里- ······
在使用stream的时候,基本会遵循下面三个步骤:
- 创建stream
- 进行流的转换,返回一个新的stream对象(链式操作)
- 将流转换为其他Java对象
一个流可以通过Collections
、Lists
、Sets
、ints
、longs
、doubles
、arrays
、lines of file
创建出来。
# Intermediate operation
intermediate ooperation(中间操作):一个流后面可以串联一个或多个中间操作。中间操作可以打开流,对流中的数据进行过滤、映射等,并返回一个新的流供之后操作实用。
# map
public class Demo1 {
public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5, 6).map(x -> x + 2).forEach(System.out::println);
}
}
/*
3
4
5
6
7
8
Process finished with exit code 0
*/
中间操作map
用于将一个对象转换为另一个对象,它的签名要求实现一个Function
函数接口:
这个函数接口的静态方法是接受一个T类型的参数,然后返回一个R类型的对象:
对于上面的例子,我们做的操作就是把X + 2,所以T和R都是Integer
。
public class Demo1 {
public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5, 6).map(String::valueOf).forEach(s -> System.out.println(s.getClass().getName()));
}
}
/*
java.lang.String
java.lang.String
java.lang.String
java.lang.String
java.lang.String
java.lang.String
Process finished with exit code 0
对于这个例子来说,T就是Integer
,R就是String
。
# filter
public class Demo1 {
public static void main(String[] args) {
Stream.of("AI", "Ankit", "Kushal", "Bob", "Susan", "Hans").filter(s -> s.startsWith("A")).forEach(System.out::println);
}
}
/*
AI
Ankit
Process finished with exit code 0
filter
是一个过滤器,可以通过给定的条件把流中不符合条件的元素剔除掉,它的签名要求我们实现一个Predicate
函数接口,这个函数接口的静态方法接收一个T类型的参数,而返回一个布尔值:
下一个例子,通过map将流中的每个元素首字母转换为小写,然后再用filter过滤出符合条件的元素:
public class Demo1 {
public static void main(String[] args) {
Stream.of("AI", "Ankit", "Kushal", "Bob", "Susan", "Hans").map(String::toLowerCase).filter(s -> s.startsWith("a")).forEach(System.out::println);
}
}
/*
ai
ankit
Process finished with exit code 0
# distinct
对于流中的元素进行去重,不需要先把其转换为Set
,只需要实用distinct
中间操作即可:
public class Demo1 {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5, 1);
list.stream().distinct().forEach(System.out::println);
}
}
/*
1
2
3
4
5
# Ternimal operations
Ternimal operations(终端操作):终端操作是流的链式调用的最后一步,被终端操作处理过的流就会变成其他的Java对象。在一个流的链式调用流程中,终端操作只会被调用一次,调用之后流就会关闭。
# forEach
forEach
是最常用的一个终端操作,它的参数是一个Consumer
函数接口(一元),对流中的每一个元素执行相同的操作,最常见的就是打印流中的每个元素:
public class Demo1 {
public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5).map(x -> x + 1).forEach(System.out::println);
}
}
/*
2
3
4
5
6
# reduce
reduce(聚合)操作可以通过聚合函数把流中的元素聚合成一个结果。
reduce方法传入的是一个BinaryOperator
接口对象,它的apply
方法是一个二元抽象方法,会把传入的两个参数进行运算,并返回运算的结果:
T apply(T t, T u);
下面的代码实用了聚合操作来进行求和:
public class Demo1 {
public static void main(String[] args) {
Integer integer = Stream.of(1, 2, 3, 4, 5).reduce(0, (sum, i) -> sum + i);
System.out.println("integer = " + integer);
}
}
/*
integer = 15
这样似乎不是很好理解,可以使用for循环来改写一下上面的代码:
public class Demo1 {
public static void main(String[] args) throws Exception {
int[] arrays = {1, 2, 3, 4, 5};
int sum = 0;
for (int i : arrays) {
System.out.printf("sum = sum + i = %d + %d\n", sum, i);
sum += i;
}
System.out.println(sum);
}
值得注意的是,我们上面的代码在reduce方法中传入一个初始值0,这样一来,在第一轮中,0就会被作为函数(sum, i) -> sum + i
中sum的初始值,而i的初始值就是Stream中的第一个元素1
,执行完第一轮的结果为0 + 1 = 1
,而这个结果又会作为第二轮中sum
的初始值,i
的值为Stream中的第二个元素2
,如此达到了累加的效果。
sum = sum + i = 0 + 1 = 1
sum = sum + i = 1 + 2 = 3
sum = sum + i = 3 + 3 = 6
sum = sum + i = 6 + 4 = 10
sum = sum + i = 10 + 5 = 15
15
此外,reduce方法还可以不传入初始值,这样一来,第一轮中的sum
的初始值就变为了Stream中的第一个元素1
,i
的初始值就变成了Stream中的第二个元素2
;reduce操作返回的结果也变成了Optional<Integer>
对象,需要多调用一个get方法才能得到运算的结果。
public class Demo1 {
public static void main(String[] args) throws Exception {
Optional<Integer> integer = Stream.of(1, 2, 3, 4, 5).reduce((sum, i) -> sum + i);
System.out.println("integer = " + integer.get());
}
}
/*
integer = 15
sum = sum + i = 1 + 2 = 3
sum = sum + i = 3 + 3 = 6
sum = sum + i = 6 + 4 = 10
sum = sum + i = 10 + 5 = 15
15
灵活使用reduce,可以达到一些神奇的效果,比如把配置文件中的key=value
这种形式的字符串作为键值对储存在Map中:
public class Demo2 {
public static void main(String[] args) {
List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
Map<String, String> propMap = props.stream().map(line -> {
String[] split = line.split("=");
return Map.of(split[0], split[1]);
}).reduce(new HashMap<String, String>(), (map, kvmap) -> {
System.out.println(kvmap.toString() + " -> HashMap");
map.putAll(kvmap);
return map;
});
propMap.forEach((key, value) -> {
System.out.println(key + ": " + value);
});
}
}
{profile=native} -> HashMap
{debug=true} -> HashMap
{logging=warn} -> HashMap
{interval=500} -> HashMap
logging: warn
interval: 500
debug: true
profile: native
# Other features
Stream具有延时性:只有当终端操作存在的时候,中间的操作才会被执行。
public class Demo1 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3", "a4").filter(s -> {
System.out.println("filter: " + s);
return true;
});
}
}
// 不打印结果
public class Demo1 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3", "a4").filter(s -> {
System.out.println("filter: " + s);
return true;
}).forEach(s -> System.out.println("forEach: " + s));
}
}
/*
filter: a1
forEach: a1
filter: a2
forEach: a2
filter: a3
forEach: a3
filter: a4
forEach: a4
Process finished with exit code 0
*/
这样设计是出于性能的考虑,Stream垂直执行可以减少对每个元素的操作个数
public class Demo1 {
public static void main(String[] args) {
Stream.of("a1", "b1", "c1").map(x -> {
System.out.println("map: " + x);
return x.toUpperCase();
}).anyMatch(x -> {
System.out.println("anyMatch: " + x);
return x.startsWith("B");
});
}
}
/*
map: a1
anyMatch: A1
map: b1
anyMatch: B1
*/
anyMatch()
方法是一个聚合操作,和中间操作filter()
类似,可以传入一个返回值为布尔类型的lambda表达式,当表达式返回值为True的时候就结束循环。
Stream是不能复用的,当调用聚合操作之后流就关闭了,此时如果再调用流则会抛出异常。
public class Demo1 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a1", "b1", "c1").map(x -> {
System.out.println("map: " + x);
return x.toUpperCase();
});
stream.anyMatch(s -> {
System.out.println("Stream close");
return true;
});
stream.filter(s -> true);
}
}
/*
map: a1
Stream close
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)
at java.util.stream.ReferencePipeline$StatelessOp.<init>(ReferencePipeline.java:618)
at java.util.stream.ReferencePipeline$2.<init>(ReferencePipeline.java:163)
at java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:162)
at steam.Demo1.main(Demo1.java:18)
*/
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16