JAVA8--Stream学习

简介: Stream是什么怎么使用StreamStream的建立Stream中的元素操作Stream聚合操作Stream结果处理Stream分组操作Stream注意事项Stream是什么书上说Stream是对JAVA中对集合处理的抽象,在我看来Stream更像是对java集合的一次扩展,因为Stream中的API都是我们对集合操作中可能遇

Stream是什么

书上说Stream是对JAVA中对集合处理的抽象,在我看来Stream更像是对java集合的一次扩展,因为Stream中的API都是我们对集合操作中可能遇到的问题。那为什么要用Stream呢?可以从两个方面去考虑,一方面,使得集合处理可以更加高效,Stream可以并行执行。另一方面,代码更佳优雅,更加简短,因为Stream中也用到了函数式编程接口。

举一个例子来看使用Stream的优势。对一个String集合中的所有字符串做统计,找出长度大于5的字符串。

//集合内容
List<String> list = new ArrayList<>();
list.add("a");
list.add("bc");
list.add("def");
list.add("ghij");
list.add("kmlno");
list.add("pqrstu");

使用传统的集合操作

int count =0;
for(String s : list){
    if (s.length()>5){
        count++;
    }
}

使用Stream API进行处理

//串行执行
long count = list.stream().filter(s->s.length()>5).count()
//并行执行
long count = list.parallelStream().filter(s->s.length()>5).count()

相比于传统的迭代的模式,使用Stream对集合进行操作会更加简洁、方便、高效。

怎么使用Stream

使用Stream只需要三个步骤:

  • 创建一个Stream
  • 指定这个Stream要做的操作
  • 使用一个终止动作来使得Stream产生结果

上面的字符串长度统计的例子中,list.stream()用来建立了一个Stream,filter(s->s.length()>5)告诉了这个流要做哪些事情,最后使用count操作来强制它之前的延迟操作立即执行。Stream执行的一个特点就是延迟执行,count方法被调用的时候才会执行Stream之前定义的操作。另外,Stream执行的过程中不会对源对象产生改变,它们会返回一个新的Stream。

Stream的建立

JAVA8在Collection接口中添加了stream方法,所以我们可以将任何集合转换为一个Stream。另外我们也可以通过Stream.of方法将一个数组转换为Stream。

//集合到Stream
Stream<String> stream = list.stream();
//数组到Stream
String contents = "a,b,c,d,e";
Stream<String> stream = Stream.of(contents.split(","));
//构造含有任意个数的Stream,Stream.of方法接受可变长度的参数
Stream<String> stream = Stream.of("q","w","e","r");

另外还可以建立无限序列,如建立一个行如0 1 2 3 4 5 …的无限序列

Stream<BigInteger> stream = Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE));

另外一种创建无限Stream的方法是generate方法,当需要一个Stream值的时候就会调用该方法来产生一个包含随机数的无限流。

Stream<Double> stream = Stream.generate(Math::random);

Stream中的元素操作

Stream的过滤操作可以使用filter方法,该方法会返回一个新的流,这个流中的元素满足一定的条件。接口方法的定义如下所示

Stream<T> filter(Predicate<? super T> predicate);

该方法的参数是一个Predicate<T>对象,是一个从T->boolean的函数。

如果需要对一个流中的每个元素都做一定的转换操作而不是进行条件过滤,如将一个字符串流中每个元素变为小写,那么可以使用map方法。

Stream<String>  stream = list.stream().map(String::toLowerCase);    

在使用迭代方式对集合进行处理的过程中,我们可以通过控制台打印的方式查看元素的执行情况。

for(集合){
...
打印元素,观察状态
...
}

在使用Stream流的过程中,可以使用peek方法完成相同的事情。该方法会产生一个与原始流具有相同元素的流,但是在每次获取一个元素时,都会调用一个函数,这样利于调试。

String[]  stream =list.stream().peek(e->System.out.println("Fetching:"+e)).map(String::toLowerCase).toArray(String[]::new);

执行结果:

Fetching:a
Fetching:bc
Fetching:def
Fetching:ghij
Fetching:kmlno
Fetching:pqrstu

如果只是简单的打印流里面的元素,可以使用

list.stream().forEach(System.out::println)

Stream聚合操作

前面说过流的执行过程是延迟执行,当一个流遇到了终止操作后,它前面的操作才会执行。聚合方法都是终止操作。它会将流聚合为一个值。常用的聚合方法有返回流中的元素总数count方法。返回流中的最大值和最小值的max和min方法。findFirst方法返回非空集合中的第一个值。findAny方法返回所有匹配的元素。anyMatch方法返回流中是否有匹配的元素。
例子:

Optional<String> optional =  list.stream().max(String::compareToIgnoreCase);
if (optional.isPresent()){
System.out.println(optional.get());   
}

在执行这些方法时,返回值有可能为空,如果对返回值不加以判断就直接执行之后的代码,很有可能会引起空指针异常。JAVA8中推出了一个Optional<T>类型,这是一种更好表示缺少返回值的方式。它是对一个T类型对象的封装或者表示不是任何对象,它比一般的指向T类型的引用更加安全。但是从上面的例子可以看出,使用Optional跟以前的直接对返回值进行判断并没有什么简化。如果没有设计Optional,那么上面的代码调用可能是下面的方式。

T value =  list.stream().max(String::compareToIgnoreCase);
if (value != null){
System.out.println(value);
}

所以高效使用Optional的关键是把它当作一个中间结果,我们可以把这个中间结果传递给其它函数做处理。基本形式:
聚合值->Optional->函数处理,如我们要把上面比较的结果添加到一个result集合中去,那么可以使用

optional.ifPresent(value -> result.add(value));

有关Optional的详细信息可以参考这篇博文: Java 8 Optional类深度解析

Stream结果处理

在流处理的过程中,通常是要把处理的结果转换为集合对象,而不是要聚合为一个值。
流->数组

String[] result = list.stream().toArray(String[]::new)

流->set

Set<String> result = list.stream().collect(Collectors.toSet());

流->list

List<String> result = list.stream().collect(Collectors.toList());

流->map
由于map是k-v形式,所以要指明KV。如我们有一个Stream<Person>对象,将它的id和name存入map中。

Map<Integer,String> idToName = stream.collect(Collectors.toMap(Person::getId,Person::getName))

如果key为id,值为person对象可以用以下方法获取map,如果key重复,会抛出异常。

Map<Integer, Person> idToPerson = stream.collect(Collectors.toMap(Person::getId, Function.identity()));

Stream分组操作

分组操作实际上是对map操作的一种简化与补充。如我们现在需要得到一个map,key为国家名,value为改国家所使用的语言。可以用以下代码实现:

Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());

Map<String, Set<String>> countryLanguageSets = locales.collect(
Collectors.toMap(Locale::getDisplayCountry,
l -> Collections.singleton(l.getDisplayLanguage()),
(a, b) -> {
Set<String> r = new HashSet<>(a); 
r.addAll(b);
return r; }
));

与上面生成map的方法相比,该方法多了一个参数,第三个参数的作用是在发现了一个国家的一种新语言时,我们将已有值和新值组成一个新的集合。上面实际上是对locale流按照国家来分组。所以也可以用以下代码实现

Map<String, List<Locale>> countryToLocales = locales.collect(
Collectors.groupingBy(Locale::getCountry));

Locale::getCountry为分组的依据。如果我们需要对分组后的结果在做一些处理,如转换为set或统计数量

locales = Stream.of(Locale.getAvailableLocales());
Map<String, Set<Locale>> countryToLocaleSet = locales.collect(
groupingBy(Locale::getCountry, Collectors.toSet()));
System.out.println("countryToLocaleSet: " + countryToLocaleSet);   

locales = Stream.of(Locale.getAvailableLocales());
Map<String, Long> countryToLocaleCounts = locales.collect(
groupingBy(Locale::getCountry, counting()));
System.out.println("countryToLocaleCounts: " + countryToLocaleCounts);  

Stream注意事项

当执行一个流操作时,并不会对底层的集合进行修改。

目录
相关文章
|
2月前
|
Java API 数据处理
Java新特性:使用Stream API重构你的数据处理
Java新特性:使用Stream API重构你的数据处理
|
1月前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
268 0
|
2月前
|
Java API 容器
Java基础学习day08-2
本节讲解Java方法引用与常用API,包括静态、实例、特定类型方法及构造器引用的格式与使用场景,并结合代码示例深入解析。同时介绍String和ArrayList的核心方法及其实际应用。
153 1
|
1月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
202 0
|
2月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
241 100
|
2月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
269 101
|
2月前
|
并行计算 Java 大数据
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
242 101
|
1月前
|
负载均衡 Java API
grpc-java 架构学习指南
本指南系统解析 grpc-java 架构,涵盖分层设计、核心流程与源码结构,结合实战路径与调试技巧,助你从入门到精通,掌握高性能 RPC 开发精髓。
250 7
|
1月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
172 1
|
2月前
|
Java
Java基础学习day08-作业
本作业涵盖Java中Lambda表达式的应用,包括Runnable与Comparator接口的简化实现、自定义函数式接口NumberProcessor进行加减乘及最大值操作,以及通过IntProcessor处理整数数组,实现遍历、平方和奇偶判断等功能,强化函数式编程实践。
75 5