- Java8 的 Stream 使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
2.Stream流
2.1 概述
- Java8 的 Stream 使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
2.2 常用操作
2.2.1 创建流
使用 Stream 流 API 前要先把集合或者数组转换成流。
单列结合:
集合对象.stream()
1
2List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();数组:
Arrays.stream(数组)
或者使用Stream.of(数组)
来创建1
2
3Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2= Stream.of(arr);双列集合:转换成单列集合后再创建
1
2
3
4
5
6Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
//使用entrySet()方法将map集合->set集合
Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();
2.2.2 中间操作
★filter(过滤)
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
例如:打印所有姓名长度大于 1 的作家的姓名
1
2
3
4List<Author> authors = getAuthors();
authors.stream()
.filter(author -> author.getName().length() > 1)
.forEach(author -> System.out.println(author.getName()))
★map(转换或计算)
可以对流中的元素进行转换或计算。(map可以调用多次:比如我先转换再计算,把对象转换成年龄,在计算年龄)
例如:打印所有作家的姓名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//(1)利用forEach直接打印
authors.stream()
.forEach(author -> System.out.println(author.getName()));
//(2)利用map转换类型后再打印
//lambda表达式
authors.stream()
.map(author -> author.getName()) // 流中元素的类型由Author转换成了String
.forEach(s -> System.out.println(s));
//匿名类的方式
authors.stream()
.map(new Function<Author, String>() {
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
public void accept(String s) {
System.out.println(s);
}
});例如:将所有作家用 map 转换成年龄后,再将年龄 +10,并打印出来
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//lambda的方式
authors.stream()
.map(author -> author.getAge()) // 流中元素的类型由Author转换成了Integer
.map(age -> age + 10) // 对流中的元素进行计算
.forEach(age -> System.out.println(age));
//匿名类的方式
authors.stream()
.map(new Function<Author, Integer>() {
public Integer apply(Author author) {
return author.getAge();
}
})
.map(new Function<Integer, Integer>() {
public Integer apply(Integer age) {
return age + 10;
}
})
.forEach(new Consumer<Integer>() {
public void accept(Integer age) {
System.out.println(age);
}
});
distinct(去重)
可以去除流中的重复元素。
例如:打印所有作家的姓名,并且要求其中不能有重复元素
1
2
3authors.stream()
.distinct()
.forEach(author -> System.out.println(author.getName()));- 注意:distinct 方法是依赖 Object 的 equals 方法来判断是否是相同对象的。所以需要注意重写 equals 方法。
sorted(排序)
可以对流中的元素进行排序。
例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素
1
2
3
4
5
6
7
8
9
10
11
12//(1)sorted()使用无参的方式,比较的对象的实体类(Author)需要实现Comparable接口
//public class Author implements Comparable<Author>{}
authors.stream()
.distinct()
.sorted()
.forEach(author -> System.out.println(author));
//(2)sorted()使用有参的方式,使用的是Comparator接口
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.forEach(author -> System.out.println(author));
limit(限制流的长度)
可以设置流的最大长度,超出的部分将被抛弃
例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印出年龄最大的两个作家的姓名。
1
2
3
4
5authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.limit(2)
.forEach(author -> System.out.println(author.getName()));
skip(跳过)
跳过流中的前n个元素,返回剩下的元素
例如:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
1
2
3
4
5authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.skip(1)
.forEach(author -> System.out.println(author.getName()));
★flatMap
map 只能把一个对象转换成另一个对象来作为流中的元素,而 flatMap 可以把一个对象转换成多个对象作为流中的元素。
例一:打印所有书籍的名字。要求对重复的元素进行去重
1
2
3
4
5
6
7
8
9
10//作者实体类中包含List<Book> books作品集合。flatMap的作用就是将一个authors.stream()转换成多个books.stream()的形式。
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(book -> System.out.println(book.getName()));例二:打印现有数据的所有分类。要求对分类进行去重。(不能出现这种格式:哲学,爱情)
1
2
3
4
5
6authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category -> System.out.println(category));
2.2.3 终结操作
★forEach(遍历)
对流中的元素进行遍历操作,我们通过传入的方法去指定对遍历到的元素具体进行什么操作。
例子:输出所有作家的名字
1
2
3
4authors.stream()
.map(author -> author.getName())
.distinct()
.forEach(name -> System.out.println(name));
count
可以用来获取当前流中元素的个数。
例子:打印这些作家所出书籍的总数,注意删除重复元素。
1
2
3
4
5long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println("这些作家的所出书籍的数目为:"+count);
max&min
可以用来获取流中的最值
例子:分别获取这些作家的所出书籍的最高分和最低分并打印
1
2
3
4
5
6
7
8
9
10
11Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream()) // 流中元素由Author转换为Book
.map(book -> book.getScore()) // 流中元素由Book转换为Score
.max((score1, score2) -> score1 - score2);
System.out.println("最高分:"+max.get());
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream()) // 流中元素由Author转换为Book
.map(book -> book.getScore()) // 流中元素由Book转换为Score
.min((score1, score2) -> score1 - score2);
System.out.println("最低分:"+min.get());
★collect(流转换为集合)
把当前流转化为一个集合。
例子 1:获取一个存放所有作者名字的 List 集合
1
2
3
4List<String> nameList = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(nameList) ;例子2:获取一个所有书名的 Set 集合
1
2
3
4
5Set<String> bookNameList = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toSet());
System.out.println(bookNameList);例子3:获取一个 Map 集合,map 的 key 为作者名,value 为 List
1
2
3
4
5Map<String, List<Book>> authorMap = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(),
author -> author.getBooks()));
System.out.println(authorMap);- 关于 Collectors.toMap() 参数有三个的情况:使用 toMap() 函数之后,返回的就是一个Map了,自然会需要 key 和 value。
- toMap() 的第一个参数就是用来传入 key 值的
- 第二个参数就是用来传入 value 值的
- 第三个参数用在 key 值冲突的情况下:如果新元素产生的 key 在 Map 中已经出现过了,第三个参数就会定义解决的办法。
- 关于 Collectors.toMap() 参数有三个的情况:使用 toMap() 函数之后,返回的就是一个Map了,自然会需要 key 和 value。
查找与匹配
allMatch
可以用来判断是否都符合匹配条件,结果为 boolean 类型。如果都符合结果为 true,否则结果为 false。
例子:判断是否所有的作家都是成年人
1
2
3boolean b = authors.stream()
.allMatch(author -> author.getAge() >= 18);
System.out.println(b);
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为 boolean 类型。
例子:判断是否有年龄在 29 以上的作家
1
2
3boolean b = authors.stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(b);
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为 true,否则结果为 false。
例子:判断作家是否都没有超过 100 岁
1
2
3boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b);
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
例子:获取任意一个年龄大于 18 的作家,如果存在就输出他的名字
1
2
3
4Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge() > 18)
.findAny();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
findFirst
获取流中的第一个元素。
例子:获取一个年龄最小的作家,并输出他的姓名。
1
2
3
4Optional<Author> first = authors.stream()
.sorted((author1, author2) -> author1.getAge() - author2.getAge())
.findFirst();
first.ifPresent(author -> System.out.println(author.getName()));
★reduce(归并)
对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
reduce 的作用是把 stream 中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式将流中的元素和初始化值进行计算,计算结果再和后面的元素进行计算。
reduce 内部的计算方式如下:
1
2
3
4T result = identity;
for(T element : this stream)
result = accumulator.apply(result,element)
return result;- 其中 identity 就是我们可以通过方法参数传入的初始值,accumulator 的 apply 具体进行什么计算是我们通过传入的具体方法来确定的。
例子 1:使用 reduce 求所有作者年龄的和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//匿名内部类的方式
Integer result = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, new BinaryOperator<Integer>() {
public Integer apply(Integer result, Integer element) {
return result + element;
}
});
System.out.println(result);
//lambda表达式
Integer result = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, (result1, element) -> result1 + element);
System.out.println(result);例子2:使用 reduce 求所有作者中年龄的最大值。
1
2
3
4
5
6//注:求最大值,那么我们的初始化值就要是Integer.MIN_VALUE最小值。
Integer reduce = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE,
(result, element) -> result > element ? result : element);
System.out.println(reduce);
2.3 注意事项
惰性求值(如果没有终结操作,中间操作是不会得到执行的),例如:
1
2
3
4
5
6
7
8
9public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 1);
nums.stream()
.map(num -> {
num += 10;
System.out.println(num);
return num;
}).distinct();
}- 这个方法是没有输出的,因为没有终结操作,所以中间操作并不会执行,那么
System.out.println(num);
是不会运行的。
而如果加上终结操作:
1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 1);
long count = nums.stream()
.map(num -> {
num += 10;
System.out.println(num);
return num;
}).distinct()
.count();
System.out.println(count);
}输出结果为:
1
2
3
4
5
6
711
12
13
14
15
11
5中间操作的
System.out.println(num);
运行了。
- 这个方法是没有输出的,因为没有终结操作,所以中间操作并不会执行,那么
流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用)
不会影响原数据(我们在流中可以对数据做很多处理,但是正常情况下是不会影响原来集合中的元素的,这往往也是我们所期望的)