Java中Stream操作

前言

Stream是Java 8 API添加的一个新的抽象,称为流Stream,以一种声明性方式处理数据集合(侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式)

Stream流是对集合(Collection)对象功能的增强,与Lambda表达式结合,可以提高编程效率、间接性和程序可读性。

特点

  1. 代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环
  2. 多核友好:Java函数式编程使得编写并行程序如此简单,就是调用一下方法

流的操作过程为

流创建 => 中间操作 => 终端操作

  • 流创建 集合数据创建为流
  • 中间操作 对数据进行处理
  • 终端操作 处理后的数据重新转换为集合对象

常见示例

List=>Array

方式1

1
2
List<Expression> groupExps = new ArrayList<>();
Expression[] groupArr = groupExps.toArray(new Expression[0]);

方式2(不推荐)

1
2
List<Expression> groupExps = new ArrayList<>();
Expression[] groupArr = groupExps.stream().toArray(Expression[]::new);

流创建

Stream创建

1
Stream<Integer> stream1 = Stream.of(1,2,3,4,5);

Collection集合创建(应用中最常用的一种

1
2
3
4
5
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);

Stream<Integer> listStream = integerList.stream();

Array数组创建

1
2
int[] intArr = {1, 2, 3, 4, 5};
IntStream arrayStream = Arrays.stream(intArr);

通过Arrays.stream方法生成流,并且该方法生成的流是数值流【即IntStream】而不是 Stream

注:

使用数值流可以避免计算过程中拆箱装箱,提高性能。

Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream 】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流

文件创建

1
2
3
4
5
try {
Stream<String> fileStream = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}

通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行

函数创建

iterator

1
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(5);

iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数

generator

1
Stream<Double> generateStream = Stream.generate(Math::random).limit(5);

generate方法接受一个参数,方法参数类型为Supplier ,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断

中间操作

filter

用于通过设置的条件过滤出元素

1
2
3
List strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");

List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

map

接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是”创建一个新版本”而不是去”修改”)

1
2
3
List strings = Arrays.asList("abc", "abc", "bc", "efg", "abcd","jkl", "jkl");

List mapped = strings.stream().map(str->str+"-itcast").collect(Collectors.toList());

distinct

返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流

1
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);

sorted

返回排序后的流

1
2
3
List strings1 = Arrays.asList("abc", "abd", "aba", "efg", "abcd","jkl", "jkl");

List sorted1 = strings1.stream().sorted().collect(Collectors.toList());

limit

会返回一个不超过给定长度的流

1
2
3
List strings = Arrays.asList("abc", "abc", "bc", "efg", "abcd","jkl", "jkl");

List limited = strings.stream().limit(3).collect(Collectors.toList());

skip

返回一个扔掉了前n个元素的流

1
2
3
List strings = Arrays.asList("abc", "abc", "bc", "efg", "abcd","jkl", "jkl");

List skiped = strings.stream().skip(3).collect(Collectors.toList());

flatMap

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流

1
2
3
List strings = Arrays.asList("abc", "abc", "bc", "efg", "abcd","jkl", "jkl");

Stream flatMap = strings.stream().flatMap(Java8StreamTest::getCharacterByString);

peek

对元素进行遍历处理

1
2
3
4
5
6
7
8
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "ddd", "kkk");
List<String> list01 = strings.stream().map(str -> str + "-110").collect(Collectors.toList());
System.out.println(Arrays.toString(list01.toArray()));

List<String> list02 = strings.stream().peek(str -> str += "-110").collect(Collectors.toList());
System.out.println(Arrays.toString(list02.toArray()));
}

结果

[abc-110, ddd-110, kkk-110]
[abc, ddd, kkk]

以下两者是等效的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.xhkjedu.test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamTest {
public static void main(String[] args) {
List<Person> pList = Arrays.asList(new Person("abc"), new Person("ddd"), new Person("kkk"));
List<Person> list01 = pList.stream().map(p -> {
p.name += "-110";
return p;
}).collect(Collectors.toList());
System.out.println(Arrays.toString(list01.toArray()));


List<Person> pList02 = Arrays.asList(new Person("abc"), new Person("ddd"), new Person("kkk"));
List<Person> list02 = pList02.stream().peek(p -> p.name += "-110").collect(Collectors.toList());
System.out.println(Arrays.toString(list02.toArray()));
}

static class Person {
public String name;

public Person() {
}

public Person(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

也就是说

map可以操作原对象也可以生成新的对象。

peek只能操作原对象。

终端操作

Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

终端操作的执行,才会真正开始流的遍历。如 count、collect 等

collect

收集器,将流转换为其他形式

1
2
3
4
5
6
7
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");

Set set = strings.stream().collect(Collectors.toSet());

List list = strings.stream().collect(Collectors.toList());

Map<String, String> map = strings.stream().collect(Collectors.toMap(v ->v.concat("_name"), v1 -> v1, (v1, v2) -> v1));

Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类

toList

将用户ID存放到List集合中

1
List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList());

toMap

将用户ID和Name以Key-Value形式存放到Map集合中

1
Map<Integer,String> userMap = userList.stream().collect(Collectors.toMap(User::getId,User::getName));

toSet

将用户所在城市存放到Set集合中

1
Set<String> citySet = userList.stream().map(User::getCity).collect(Collectors.toSet());

counting

符合条件的用户总数

1
long count = userList.stream().filter(user -> user.getId()>1).collect(Collectors.counting());

summingInt

对结果元素即用户ID求和

1
Integer sumInt = userList.stream().filter(user -> user.getId()>2).collect(Collectors.summingInt(User::getId));

minBy

筛选元素中ID最小的用户

1
User maxId = userList.stream().collect(Collectors.minBy(Comparator.comparingInt(User::getId))).get();

joining

将用户所在城市,以指定分隔符链接成字符串;

1
String joinCity = userList.stream().map(User::getCity).collect(Collectors.joining("||"));

groupingBy

按条件分组,以城市对用户进行分组;

1
Map<String,List<User>> groupCity = userList.stream().collect(Collectors.groupingBy(User::getCity));

forEach

遍历流

1
2
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");
strings.stream().forEach(s -> out.println(s));

findFirst

返回第一个元素

1
2
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");
Optional first = strings.stream().findFirst();

findAny

将返回当前流中的任意元素

1
2
3
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");

Optional any = strings.stream().findAny();

count

返回流中元素总数

1
2
3
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");

long count = strings.stream().count();

sum

求和

1
int sum = userList.stream().mapToInt(User::getId).sum();

max

最大值

1
int max = userList.stream().max(Comparator.comparingInt(User::getId)).get().getId();

min

最小值

1
int min = userList.stream().min(Comparator.comparingInt(User::getId)).get().getId();

anyMatch

检查是否至少匹配一个元素,返回boolean

1
2
3
List strings = Arrays.asList("abc", "abd", "aba", "efg", "abcd","jkl", "jkl");

boolean b = strings.stream().anyMatch(s -> s == "abc");

allMatch

检查是否匹配所有元素,返回boolean

1
2
3
List strings = Arrays.asList("abc", "abd", "aba", "efg", "abcd","jkl", "jkl");

boolean b = strings.stream().allMatch(s -> s == "abc");

noneMatch

检查是否没有匹配所有元素,返回boolean

1
2
3
List strings = Arrays.asList("abc", "abd", "aba", "efg", "abcd","jkl", "jkl");

boolean b = strings.stream().noneMatch(s -> s == "abc");

reduce

可以将流中元素反复结合起来,得到一个值

1
2
3
List strings = Arrays.asList("cv", "abd", "aba", "efg", "abcd","jkl", "jkl");

Optional reduce = strings.stream().reduce((acc,item) -> {return acc+item;});if(reduce.isPresent())out.println(reduce.get());

orElse/orElseGet

相同点:

  • orElse(null)/orElseGet(null)表示如果找不到值则返回null。
  • orElse(value)/orElseGet(value) 可以设置默认值。如果找不到就会返回中设置的默认值。

不同点:

在使用方法时,

  • orElse无论是否有值都会执行。

  • orElseGet如果有值,则不也会执行。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.xhkjedu.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamTest {
public static void main(String[] args) {
List<User> list = new ArrayList<>();
//定义用户对象
User user1 = new User("小明", 16, "男");
User user2 = new User("小红", 18, "女");
User user3 = new User("李明", 12, "男");

//添加用户到集合中
list.add(user1);
list.add(user2);
list.add(user3);

/*
在集合中查询出第一个用户名包含 明 的用户
*/
Optional<User> user = list.stream().filter(u -> u.getUserName().contains("明")).findFirst();
System.out.println(user);

System.out.println("无值-设置方法:");
User a = list.stream().filter(u -> u.getAge() == 20).findFirst().orElse(getMethod("a"));
System.out.println("a:" + a);
User b = list.stream().filter(u -> u.getAge() == 20).findFirst().orElseGet(() -> getMethod("b"));
System.out.println("b:" + b);

System.out.println("有值-设置方法:");
User c = list.stream().filter(u -> u.getAge() == 16).findFirst().orElse(getMethod("c"));
System.out.println("c:" + c);
User d = list.stream().filter(u -> u.getAge() == 16).findFirst().orElseGet(() -> getMethod("d"));
System.out.println("d:" + d);

System.out.println("设置默认值对比:");
User a1 = list.stream().filter(u -> u.getAge() == 20).findFirst().orElse(new User("神秘人", 0, "未知"));
System.out.println("a1:" + a1);
User c1 = list.stream().filter(u -> u.getAge() == 16).findFirst().orElse(new User("神秘人", 0, "未知"));
System.out.println("c1:" + c1);
}

static class User {
private String userName;
private int age;
private String sex;

public User(String userName, int age, String sex) {
this.userName = userName;
this.age = age;
this.sex = sex;
}

public User() {
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}

public static User getMethod(String name) {
System.out.println(name + ":执行了方法");

return null;
}
}

结果

Optional[User{userName=’小明’, age=16, sex=’男’}]
无值-设置方法:
a:执行了方法
a:null
b:执行了方法
b:null
有值-设置方法:
c:执行了方法
c:User{userName=’小明’, age=16, sex=’男’}
d:User{userName=’小明’, age=16, sex=’男’}
设置默认值对比:
a1:User{userName=’神秘人’, age=0, sex=’未知’}
c1:User{userName=’小明’, age=16, sex=’男’}