文章目录
一、Stream简介
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作(bulk data operation)。
Stream API 借助于同样新出现的 Lambda 表达式,而且使用并发模式,极大的提高编程效率和程序可读性。
- 举个栗子
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(3, "Jack"));
userList.add(new User(1, "Many"));
userList.add(new User(2, "Amy"));
// java8之前的排序方式
Collections.sort(userList, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.id.compareTo(o2.id);
}
});
// 使用java8 Stream排序
userList = userList.stream()
.sorted(Comparator.comparing(User::getId)).collect(Collectors.toList());
}
Java 8 使用 Stream,代码更加简洁易读;程序执行速度更快。
二、Stream的构成及操作类型
1、Stream构成
- 当我们使用一个流(也就是Stream)的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果。
2、操作类型
(1)Intermediate(中间的)
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
(2)Terminal(末端的)
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。
- 举个栗子
public static void main(String[] args) {
List<Integer> numList = Arrays.asList(new Integer[]{5, 2, 9, 1, 5, 7, 3, 8, 6});
numList = numList.stream().sorted().filter(num -> num % 2 == 1)
.collect(Collectors.toList());
System.out.println(numList); // 输出:[1, 3, 5, 5, 7, 9]
}
其中,sorted和filter操作作为中间层进行排序和筛选,collect操作作为结束层重新放到List结果中
- 常见的操作可以归类如下
- Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered - Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
三、Stream常用属性
1、collect 收集属性
collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法
public static void main(String[] args) {
Integer[] numArr = {1, 2, 3, 4, 5};
List<Integer> numList = Stream.of(numArr).collect(Collectors.toList());
}
2、map / flatMap 映射转换
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(3, "Jack"));
userList.add(new User(1, "Many"));
userList.add(new User(2, "Amy"));
// 提取User的Name转成List集合
List<String> nameList = userList.stream()
.map(User::getName).collect(Collectors.toList());
System.out.println(nameList); // 输出[Jack, Many, Amy]
}
从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则(例子中的User::getName)转换成为另外一个元素。
还有一些场景,是一对多映射关系的,这时需要 flatMap。
public static void main(String[] args) {
List<User> userList1 = new ArrayList<>();
userList1.add(new User(3, "Jack"));
userList1.add(new User(1, "Many"));
userList1.add(new User(2, "Amy"));
List<User> userList2 = new ArrayList<>();
userList2.add(new User(11, "张三"));
userList2.add(new User(12, "李四"));
List<String> nameList = Stream.of(userList1, userList2)
.flatMap(list -> list.stream()).map(User::getName)
.collect(Collectors.toList());
System.out.println(nameList); // 输出[Jack, Many, Amy, 张三, 李四]
}
3、filter 过滤属性
filter 对原始 Stream 进行某项筛选,通过筛选的元素被留下来生成一个新 Stream。
public static void main(String[] args) {
List<Integer> numList = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6});
// 取出集合中的所有偶数
numList = numList.stream()
.filter(num -> num % 2 == 0).collect(Collectors.toList());
System.out.println(numList ); // 输出[2, 4, 6]
}
4、forEach / peek 遍历属性
forEach 是 terminal 操作,方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(3, "Jack"));
userList.add(new User(1, "Many"));
userList.add(new User(2, "Amy"));
// 使用forEach属性遍历集合
userList.stream().forEach(user -> {
System.out.println(user.getName());
});
}
注意:forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个 Stream 进行两次 terminal 运算。
相反,具有相似功能的 intermediate 操作 peek属性可以达到上述目的,
仍可对stream做再次操作。
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(3, "Jack"));
userList.add(new User(1, "Many"));
userList.add(new User(2, "Amy"));
// 使用peek属性遍历集合操作
userList = userList.stream().peek(user -> {
user.setName(user.getName() + "1");
}).collect(Collectors.toList());
System.out.println(userList); // 输出[User{id=3, name='Jack1'}, User{id=1, name='Many1'}, User{id=2, name='Amy1'}]
}
5、reduce 聚合属性
主要作用是把 Stream 元素组合起来。
它提供一个起始值(种子),然后依照运算规则,和前面 Stream 的第一个、第二个、第 n 个元素组合。
从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
public static void main(String[] args) {
// 字符串拼接
String[] arr = {"a", "b", "c", "d"};
String content = Stream.of(arr).reduce("", String::concat);
System.out.println(content); // 输出 abcd
// 求和, 需要一个起始参数值
int sum = Stream.of(1, 2, 3, 4, 5, -2).reduce(0, Integer::sum);
System.out.println(sum); // 输出 13
// 找最大值
int maxNum = Stream.of(1, 3, 5, -2, 0).reduce(Integer::max).get();
System.out.println(maxNum); // 输出 5
}
6、sorted 排序属性
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
public static void main(String[] args) {
Integer[] numArr = {2, 3, 1, 5, 0 ,8};
List<Integer> numList = Stream.of(numArr)
.sorted().collect(Collectors.toList());
System.out.println(numList); // 输出[0, 1, 2, 3, 5, 8]
// 反向排序
numList = Stream.of(numArr)
.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
System.out.println(numList); // 输出[8, 5, 3, 2, 1, 0]
}
7、match 匹配属性
Stream 有三个 match 方法
属性 | 描述 | 结果 |
---|---|---|
allMatch | Stream 中全部元素符合传入的 predicate | true |
anyMatch | Stream 中只要有一个元素符合传入的 predicate | true |
noneMatch | Stream 中没有一个元素符合传入的 predicate | true |
public static void main(String[] args) {
Integer[] numArr = {1, 2, 3, 4, 5};
boolean flag1 = Stream.of(numArr).allMatch(num -> num > 1);
System.out.println(flag1); // 全部属性都大于1 输出false
boolean flag2 = Stream.of(numArr).anyMatch(num -> num > 1);
System.out.println(flag2); // 部分属性大于1 输出true
boolean flag3 = Stream.of(numArr).noneMatch(num -> num > 1);
System.out.println(flag3); // 没有大于1的属性 输出false
}