Java8 Stream流的使用

编程基础
9 篇文章0 订阅
订阅专栏
Stream 概述
在 Java 8 中,新增了 Stream 这个重要的抽象概念,结合同版本出现的 Lambda 表达式,Stream 通过其 API 提供了一系列高效、友好的处理集合数据的操作方式。

从字面上理解,Stream 就是把集合中将要处理的元素集合看作一个流,通过使用其 API 对流中的元素进行各种操作,如过滤、映射、聚合等。它具有以下几个特性:

Stream 只是一个抽象接口概念,其本身并不是一个数据结构,所以并不能用来存储数据。
Stream API 不会改变数据源,所有操作的最终结果会保存到另外一个对象中。(peek 方法除外,它会修改流中的元素)。
Stream 采用惰性计算的方式,流的中间过程只是记录了操作的步骤,并不会立即执行,在执行终端操作后才会进行实际的计算,返回一个新的对象。
Stream 的分类
Stream 的操作方式主要分为两大类,根据其执行特点还能继续向下细分,具体划分如下:

中间操作(Intermediate operations):每次操作返回一个流,可以进行多次中间操作
无状态操作(Stateless):可以直接对单个元素进行的操作,不受之前操作元素的影响。
有状态操作(Stateful):只有获取到所有元素后才能执行的操作。
终端操作(Terminal operations):每个流只能进行一个终端操作,执行后流无法再次使用,返回一个新的集合或者对象。
非短路操作(non-short circuit):必须处理所有元素才能得到最终结果
短路操作(short circuiting):遇到某些符合条件的元素就可以得到最终结果。
Stream 的创建
Stream 定义在 java.util.stream 包中,作为一个重要接口概念,在各种集合类以及数组类中都提供了初始化方法。Stream 自身也提供了一系列创建方法。

// 1.通过集合类的stream()或者parallelStream()方法
List<String> list = Arrays.asList("one", "two", "three");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();

// 2.通过数组的stream()方法
String array = {1,2,3,4,5};
IntStream stream = Arrays.stream(array);

// 3.通过Stream的静态方法
Stream<Character> charStream = Stream.of('a','b','c');
Stream<Integer> intStream = Stream.iterate(0, (x) -> x + 1).limit(5);
Stream<Double> doubleStream = Stream.generate(Math::random).limit(5);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stream是顺序流,而parallelStream是并行流,内部以多线程运行的方式对流进行操作,使用并行流时必须确保数据的处理对顺序是没有要求的。

在其他的场合,还有一些常见的使用 Stream 的方式,比如:

// 通过BufferedReader.lines()方法将每行的内容转换成流
BufferedReader reader = new BufferedReader(new FileReader("C:\file.txt"));
Stream<String> lineStream = reader.lines();

// 通过Pattern.splitAsStream()方法将字符串转换成流
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d,e");
1
2
3
4
5
6
7
Stream 的使用
我们这里按照 Stream 操作的分类分别提供示例和进行讲解。

Stream 的中间操作
无状态操作
筛选
filter:根据给定的条件过滤流中的某些元素。
distinct:通过流中元素的 hashCode() 和 equals() 方法去除重复元素。
Stream<Integer> stream = Stream.of(3,5,132,6,856,9,132,2,53);
Stream<Integer> newStream = stream.filter(s -> s > 10) // 132, 856, 132, 53
.distinct(); // 132, 856, 53
1
2
3
切片
limit(n):获取 n 个元素。
skip(n):跳过 n 个元素,可以与 limit 配合实现分页。
Stream<Integer> stream = Stream.of(3,5,132,6,856,9,132,2,53);
Stream<Integer> newStream = stream.skip(4) // 9, 132, 2, 53
.limit(2); // 9, 132
1
2
3
映射:将一个流的元素按照给定的规则映射到另一个流中。
map:接收一个函数作为参数,该函数会应用到每个元素上,将其映射长一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个元素都转换成另一流,然后把所有的流连接成一个新的流。
在上述两种映射的基础上,还有其他类似的、指定映射结果数据类型的映射操作。
List<String> list = Stream.of("a,b,c,d", "1,2,3,4");
// 去除每个字符串里面的逗号
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
// 将每个字符串按照逗号分割后转换成一个流,并将所有流组合起来
Stream<String> s2 = list.stream().flatMap(s -> {
String[] split = s.split(",");
Stream<String> newStream = Arrays.stream(split);
return newStream;
});
1
消费
peek:对流中的每个元素进行操作。与 map 类似,但 map 接收一个 Function 表达式,有返回值;而 peek 接收的是一个 Consumer 表达式,没有返回值,可能会修改流中元素的初始数据值。
User u1 = new User("John", "john@gmail.com");
User u2 = new User("Jenny", "jenny@qq.com");
List<User> list = Arrays.asList(u1, u2);
// 修改每个用户的邮箱信息为指定值,内置修改,无返回值
list.stream().peek(u -> u.setEmail("default@test.com"));

有状态操作
排序(sorted)
sorted():自然排序,流中的元素需要实现Comparable接口。
sorted(Comparator c):定制排序,自定义Comparator排序器。
User u1 = new User("John", "john@gmail.com");
User u2 = new User("Jenny", "jenny@qq.com");
User u3 = new User("Zed", "zed@outlook.com");

// 自然排序,按照名字字母升序排列
list.stream().sorted();
// 自定义排序,按照名字字母倒序排列,名字相同时按照邮箱长度升序排序
list.stream().sorted((e1, e2) -> {
if (e1.getName().equals(e2.getName())) {
return e1.getEmail.length() - e2.getEmail.length();
} else {
return e2.getName.compareTo(e1.getName());
}
})

Stream 的终端操作
短路操作:主要是一些匹配与提取结果操作。
allMatch:接收一个 Predicate 表达式,当流中每个元素都符合表达式的条件时返回 true,否则返回 false。
noneMatch:接收一个 Predicate 表达式,当流中的每个元素都不满足表达式的条件时返回 true,否则返回 false。
anyMatch:接收一个 Predicate 表达式,当流中出现某一个元素满足表达式的条件时返回 true,否则返回 false。
findFirst:返回流中的第一个元素。
findAny:返回流中的任意一个元素。
List<Integer> list = Arrays.asList(1,2,3,4,5,6);

boolean allMatch = list.stream().allMatch(e -> e > 2); // false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); // true
boolean anyMatch = list.stream().anyMatch(e -> e > 5); // true

Integer findFirst = list.stream().findFirst().get(); // 1
Integer findAny = list.stream().findAny().get(); // 4

在这里的 findFirst 和 findAny 方法返回的结果都是 Optional 对象

具体说明可以去看看这篇文章:Java 8 Optional使用介绍

非短路操作:主要是一些对结果进行聚合、规约或者收集等操作。
聚合
count:返回流中元素的总数。
max:返回流中元素的最大值。
min:返回流中元素的最小值。
List<Integer> list = Arrays.asList(1,2,3,4,5,6);

long count = list.stream().count(); // 6
Integer max = list.stream().max(Integer::compareTo).get(); //6

收集(collect):收集一个 Collector 实例,将流中返回的元素收集成另外一个数据结构。
在收集中常常使用到 Collectors 工具库,通过其静态方法返回一个 Collector 实例。具体应用方式如下:

// 构造对象列表,构建函数入参分别为name,gender, email,age
User u1 = new User("John", "m", "john@gmail.com", 14);
User u2 = new User("Jenny", "f", "jenny@qq.com", 15);
User u3 = new User("Zed", "m", "zed@outlook.com", 27);
List<User> users = Arrays.asList(u1, u2, u3);

// 提取元素转换成list
List<Integer> ageList = users.stream().map(User::getAge).collect(Collectors.toList());
// 提取元素转换成set
Set<Integer> ageSet = users.stream().map(User::getAge).collect(Collectors.toSet());
// 提取元素转换成map(这里key不能重复)
Map<String, Integer> userMap = users.stream().collect(Collectors.toMap(User::getName, User::getAge));
// 提取元素转换成map(这里相同的key允许value新值覆盖旧值;若是(m, m0) -> m则为保留原来的值)
Map<String, Integer> userMap = users.stream().collect(Collectors.toMap(User::getName, User::getAge, (m, m0) -> m0));
// 对象去重
List<User> distinctUserList = users.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))),
ArrayList::new));

// 字符串分隔符连接
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")"));

除了 stream( ) 自身的聚合方法,使用 Collector 实例同样可以进行聚合运算。

// 1.学生总数
Long count = list.stream().collect(Collectors.counting());
// 2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
// 3.所有人的年龄
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge));
// 4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge));

分组:按照指定条件将 stream 划分为多个 map。
分区:按照指定条件将 stream 划分为 true 和 false 两个 map。
//分组
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//多重分组,先根据性别分再根据年龄分
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getGender, Collectors.groupingBy(Student::getAge)));

//分区
//分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));

归约(reduce):将流中的元素缩减成一个值进行返回,通常用于求和、求积等操作。
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);

Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
Integer sum3 = list.stream().reduce(0, Integer::sum);

Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
Integer max2 = list.stream().reduce(1, Integer::max);

Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get();

通过上面的例子可以看出,Collectors 工具类提供的 reducing 方法和 Stream 本身的 reduce 方法都实现了归约的操作,两者的主要差别在于,前者增加了对自定义归约方法的支持。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容