Java8新特性探索之Stream接口
一、为什么引入Stream流
流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。
在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将编程的主要焦点从集合转移到了流上,流的一个核心的好处是,它使得程序更加短小并且更易理解。当Lambda表达式和方法引用和流一起使用的时候会让人感觉自成一体。
二、如何使用Stream流
流操作的类型有三种:创建流,修改流元素(中间操作 Intermediate Operations),消费流元素(终端操作 Terminal Operations)
创建Stream流
-
使用
Arrays.stream()
方法创建Integer[] arr = new Integer[]{1,2,3,4,5}; Arrays.stream(arr).filter(num -> num > 3);
-
使用
Stream.of ()
方法创建Integer[] arr = new Integer[]{1,2,3,4,5}; Stream.of(arr).filter(num -> num > 3);
查看
of()
的源码中得知,该方法也是调用了Arrays.stream()
方法实现的/** * Returns a sequential ordered stream whose elements are the specified values. * * @param <T> the type of stream elements * @param values the elements of the new stream * @return the new stream */ @SafeVarargs @SuppressWarnings("varargs") // Creating a stream from an array is safe public static<T> Stream<T> of(T... values) { return Arrays.stream(values); }
-
使用
Collection.stream()
方法创建List<String> list = new ArrayList<>(1); list.stream().forEach(str -> System.out.println(str));
-
使用
Stream.iterate()
方法创建Stream.iterate(1, num -> num + 2).limit(10).forEach(num -> System.out.println(num));
-
使用
Stream.generate()
方法创建Stream.generate(() -> Arrays.asList(arr)).limit(1).forEach(num -> System.out.println(num));
修改流元素(中间操作 Intermediate Operations)
中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。
1、跟踪和调试
peek()
操作的目的是帮助调试,允许你无修改地查看流中的元素
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
输出结果:
Well WELL well it IT it s S s so SO so
因为 peek()
符合无返回值的 Consumer 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。
2、流元素排序
sorted()
方法是需要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后集合的第一个元素会在未排序集合的最后一位。
@Test
public void sortedTest() {
List<Integer> numList = Lists.newArrayList();
numList.add(8);
numList.add(2);
numList.add(6);
numList.add(9);
numList.add(1);
List<Integer> sortList = numList.stream().sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println(sortList);
}
输出结果:
[1, 2, 6, 8, 9]
3、移除元素
-
distinct()
可用于消除流中的重复元素。相比创建一个 Set 集合,该方法的工作量要少得多。@Test public void distinctTest() { Stream.of(6, 8, 9, 6, 2, 8).distinct().forEach(i -> System.out.print(i + ", ")); }
输出结果:
6, 8, 9, 2,
-
filter(Predicate)
:若元素传递给过滤函数产生的结果为true
,则过滤操作保留这些元素。@Test public void filterTest() { Stream.of(6, 9, 2, 8).filter(num -> num > 5).sorted().forEach(i -> System.out.print(i + ", ")); }
输出结果:
6, 8, 9,
4、映射,应用函数到元素
-
map(Function)
:将函数操作应用在输入流的元素中,对一个流中的值进行某种形式的转换,并将返回值传递到输出流中@Test public void mapTest() { Stream.of("abc", "qw", "mnkh").map(String::length).forEach(n -> System.out.format("%d ", n)); }
输出结果:
3 2 4
-
mapToInt(ToIntFunction)
:操作同上,但结果是IntStream
Stream.of("5", "7", "9").mapToInt(Integer::parseInt).forEach(n -> System.out.format("%d ", n));
-
mapToLong(ToLongFunction)
:操作同上,但结果是LongStream
Stream.of("17", "19", "23").mapToLong(Long::parseLong).forEach(n -> System.out.format("%d ", n));
-
mapToDouble(ToDoubleFunction)
:操作同上,但结果是DoubleStream
Stream.of("17", "1.9", ".23").mapToDouble(Double::parseDouble).forEach(n -> System.out.format("%f ", n));
-
flatMap()
做了两件事:将产生流的函数应用在每个元素上(与map()
所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。List<Integer> listA = Lists.newArrayList(); listA.add(1); listA.add(6); List<Integer> listB = Lists.newArrayList(); listB.add(10); listB.add(2); Map<String, List<Integer>> abMap = Maps.newHashMap(); abMap.put("A", listA); abMap.put("B", listB); // 需获取A和B集合中大于5的元素 abMap.values().stream().flatMap(num -> num.stream().filter(n -> n > 5)).collect(Collectors.toList()) .forEach(System.out::println);
输出结果:
6 10
-
flatMapToInt(Function)
:当Function
产生IntStream
时使用。 -
flatMapToLong(Function)
:当Function
产生LongStream
时使用。 -
flatMapToDouble(Function)
:当Function
产生DoubleStream
时使用。
5、集合流切片,可实现分页
-
limit(n)
方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)。 -
skip(n)
方法正好相反,它会丢弃掉前面的n个元素。// 查询第二页的数据 Integer pageNumber = 2; Integer pageSize = 10; Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).skip((pageNumber - 1) * pageSize).limit(pageSize) .forEach(System.out::println);
输出结果:
11 12
消费流元素(终端操作 Terminal Operations)
终端操作总是我们在流管道中所做的最后一件事,该操作将会获取流的最终结果。
1、数组结果输出
toArray()
:将流转换成适当类型的数组。toArray(generator)
:在特殊情况下,生成自定义类型的数组。
2、循环结果输出
-
forEach(Consumer)
常见如System.out::println
作为 Consumer 函数。 -
forEachOrdered(Consumer)
: 保证forEach
在并行流处理时按照原始流顺序操作。Arrays.stream(new Random(45).ints(0, 1000).limit(100).toArray()).limit(10).parallel().forEachOrdered(n -> System.out.format("%d ", n));
3、collect收集结果
-
collect(Collector)
:使用 Collector 收集流元素到结果集合中。 -
collect(Supplier, BiConsumer, BiConsumer)
:同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。Collectorts
类为我们提供了常用的收集类的各个工厂方法:
-
将一个流收集到一个List中可以这样用
Lists.newArrayList().stream().collect(Collectors.toList());
-
收集到Set中可以这样用
Lists.newArrayList().stream().collect(Collectors.toSet());
-
收集到Map中可以这样用
Lists.newArrayList(new User("Johnson", "重庆")).stream().collect(Collectors.toMap(User::getName, User::getAddress));
-
收集到Set时,控制Set的类型可以这样用
Lists.newArrayList().stream().collect(Collectors.toCollection(TreeSet::new));
-
将字流中的字符串连接并收集起来
Lists.newArrayList().stream().collect(Collectors.joining(","));
-
各种聚合操作
// 获取流中的总和,平均值,最大值,最小值,一次性收集流中的结果 List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5); listA.stream().collect(Collectors.summingInt(Integer::intValue)); listA.stream().collect(Collectors.averagingInt(Integer::intValue)); listA.stream().collect(Collectors.maxBy(Integer::compareTo)); listA.stream().collect(Collectors.minBy(Integer::compareTo)); listA.stream().collect(Collectors.summarizingInt(Integer::intValue)); // 分组分片,返回结果:{"重庆渝北":[{"address":"重庆渝北","name":"Johnson"},{"address":"重庆渝北","name":"Jack"}],"重庆江北":[{"address":"重庆江北","name":"Tom"}]} List<User> listB = Lists.newArrayList(new User("Johnson", "重庆渝北"), new User("Tom", "重庆江北"), new User("Jack", "重庆渝北")); System.out.println(JSON.toJSONString(listB.stream().collect(Collectors.groupingBy(User::getAddress))));
4、组合流中元素
-
reduce(BinaryOperator)
:使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional// 结果为15 System.out.println(Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y).get());
-
reduce(identity, BinaryOperator)
:功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果// 设置初始值为10则结果为25 System.out.println(Stream.of(1, 2, 3, 4, 5).reduce(10, (x, y) -> x + y)); // 集合流为空,则结果默认为初始值a List<String> list = Lists.newArrayList(); System.out.println(list.stream().reduce("a", (x, y) -> x.length() > 1 ? x : y));
-
reduce(identity, BiFunction, BinaryOperator)
:在串行流(stream)中,该方法跟第二个方法一样,即第三个参数不会起作用。在并行流中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,BiFunction)
一样,而第三个参数BinaryOperator
函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(BinaryOperator)
流程进行规约// 第三个参数在并行流中起效,将每个线程的执行结果当成一个新的流 List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5); // 串行流运行结果:15 System.out.println(listA.stream().reduce(0, (x, y) -> x + y, (i, j) -> i * j)); // 并行流运行结果:120 System.out.println(listA.parallelStream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
5、流中元素匹配
-
allMatch(Predicate)
:如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。// 数组中第一个元素小于2,则停止匹配返回结果:flase System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 2)); // 数组中所有元素都大于0,则停止匹配返回结果:true System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 0));
-
anyMatch(Predicate)
:如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。// 数组中第三个元素大于2,则停止匹配返回结果:true System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(n -> n > 2));
-
noneMatch(Predicate)
:如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。// 数组中第三个元素大于2,则停止匹配返回结果:true System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(n -> n > 2));
6、流中元素查找
-
findFirst()
:返回第一个流元素的 Optional,如果流为空返回 Optional.empty。// 根据条件过滤后取第一个元素 System.out.println(Stream.of(1, 2, 3, 4, 5).filter(n -> n > 2).findFirst().get());
-
findAny()
:返回含有任意流元素的 Optional,如果流为空返回 Optional.empty。// 根据条件过滤后找到任何一个所匹配的元素,就返回,此方法在对流并行执行时效率高 System.out.println(Stream.of(1, 2, 3, 4, 5).parallel().filter(n -> n > 2).findAny().get());
7、收集流信息
-
count()
:流中的元素个数。 -
max(Comparator)
:根据所传入的 Comparator 所决定的“最大”元素。 -
min(Comparator)
:根据所传入的 Comparator 所决定的“最小”元素。 -
average()
:求取流元素平均值。 -
sum()
:对所有流元素进行求和。 -
summaryStatistics()
:生成有关此流元素的各种摘要数据。// 获取流中元素数量,返回结果:5 System.out.println(Stream.of(1,2,3,4,5).count()); // 获取流中最大值,返回结果:5 System.out.println(Stream.of(1,2,3,4,5).max(Integer::compareTo).get()); // 获取流中最小值,返回结果:1 System.out.println(Stream.of(1,2,3,4,5).min(Integer::compareTo).get()); // 获取流中元素平均值,返回结果:3.0 System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).average().getAsDouble()); // 获取流中各种摘要数据,返回结果:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5} System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).summaryStatistics()); // 获取流中元素总和,返回结果:15 System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).sum());
三、总结
流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)