Java8 Stream 的基本使用
1、初识 Stream
Java8
中Collection
新增了两个流方法, 分别是 Stream()
和 parallelStream()
,分别定义在java.util.stream
包中。Stream主要用于取代部分Collection
的操作,每个流代表一个值序列,流提供一系列常用的聚集操作,可以在它上面进行各种运算。集合类库也提供了便捷的方式使我们可以以操作流的方式使用集合、数组以及其它数据结构。
Stream
是 Java8
中处理集合的关键抽象概念,它可以帮助你执行你希望对集合进行的操作,比如可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式
特点:
-
不是数据结构,不会保存数据。
-
不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。
-
惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
Stream是JDK1.8版本更新的一个亮点,给开发者对集合(Collection
)的操作提供了极大的便利。
-
Stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
-
2、操作分类
Stream操作分类 | ||
---|---|---|
中间操作 | 无状态 | unordered(), filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong() , flatMapToDouble(), peek() |
有状态 | distinct(), sorted(), limit(), skip() | |
终结操作 | 非短路操作 | forEach(), forEachOrdered(), toArray(), reduce(), collect(), max(), min(), count() |
短路操作 | anyMatch(), allMatch(), noneMatch(), findFirst(), findAny() |
中间操作
当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。Stream 提供了多种类型的中间操作,如 filter、distinct、map、sorted 等等。 中间操作又可以分为无状态(Stateless)
与有状态(Stateful)
操作:
-
无状态是指元素的处理不受之前元素的影响
-
有状态是指该操作只有拿到所有元素之后才能继续下去
终结操作
当所有的中间操作完成后,若要将数据从流水线中拿下来,则需要执行终结操作。Stream 对于终结操作,可以直接提供一个中间操作的结果,或者将结果转换为特定的 collection、array、String 等。 终结操作又可以分为短路(Short-circuiting)
与非短路(Unshort-circuiting)
操作:
-
短路是指遇到某些符合条件的元素就可以得到最终结果
-
非短路是指必须处理完所有元素才能得到最终结果
常用操作类型
操作类型 | 作用 |
---|---|
map() | 将流中的元素进行再次加工形成一个新流, 流中的每一个元素映射为另外的元素 |
filter() | 返回结果生成新的流中只包含满足筛选条件的数据 |
limit() | 返回指定数量的元素的流。 返回的是 Stream 里前面的 n 个元素 |
skip() | 和 limit()相反, 将前几个元素跳过(取出) 再返回一个流, 如果流中的元素小于或者等于 n, 则返回一个空的流 |
sorted() | 将流中的元素按照自然排序方式进行排序 |
distinct() | 将流中的元素去重之后输出 |
peek() | 对流中每个元素执行操作, 并返回一个新的流, 返回的流还是包含原来流中的元素 |
注意事项
stream has already been operated upon or closed
Stream API只能被消费一次,后续重复使用已建立的流会报异常!所以stream流是线程安全的!
比如如下代码将会报如上异常:
@Test
public void testMin(){
// 创建一个整形数字流
Stream<Integer> stream = Stream.of(5, 6, 7, 8, 9, 10);
System.out.println(stream.min((o1, o2) -> o1.compareTo(o2)).get()); // 5
System.out.println(stream.max(Integer::compareTo).get());
}
解决方法: 创建一个新的Stream流 ,使用不同的Stream流 来执行遍历操作。
@Test
public void testMin(){
// 创建一个整形数字流
List<Integer> integers = Arrays.asList(5, 6, 7, 8, 9, 10);
System.out.println(integers.stream().min((o1, o2) -> o1.compareTo(o2)).get()); // 5
System.out.println(integers.stream().max(Integer::compareTo).get()); //10
}
Collection集合类
使用Collection下的 stream() 和 parallelStream() 方法:
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
和parallelStream的简单区分:
-
Stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。如果流中的数据量足够大,并行流可以加快处速度。
数组
使用Arrays 中的 stream() 方法,将数组转成流:
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
Stream中的静态方法
使用Stream中的静态方法:of()
、iterate()
、generate()
:
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);
// 0.3302079414967978
// 0.4313804346265182
其他
使用 BufferedReader.lines() 方法,将每行内容转成流:
BufferedReader reader = new BufferedReader(new FileReader("d:\\a.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
使用 Pattern.splitAsStream() 方法,将字符串分隔成流:
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
终结操作
当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终结操作。
forEach()
Stream forEach(Consumer action) 为流中的每个元素执行一个动作。Stream forEach(Consumer action)是一个 终端操作 ,即它可以遍历流,产生一个结果或一个副作用。
forEachOrdered()
Stream forEachOrdered(Consumer action) 对该流的每个元素执行一个动作,如果该流有定义的相遇顺序,则按照该流的相遇顺序执行。Stream forEachOrdered(Consumer action)是一个 终端操作 ,也就是说,它可以遍历流,产生一个结果或副作用。
按原始顺序对字符串流的每个元素进行打印操作。
@Test
public void testForEachOrdered(){
List<String> strings = Arrays.asList("apple", "orange", "Bananas", "strawberries", "mangoes", "grapes");
Stream<String> stringStream = strings.parallelStream();
// 在使用parallelStream并行流情况下,forEach方法打印数据时,每次打印顺序可能不一致
stringStream.forEach(System.out :: println);
// 在使用parallelStream并行流情况下,forEachOrdered方法打印数据时,每次打印顺序都一致
stringStream.forEachOrdered(System.out::println);
// 注意:在Stream顺序流情况下,两种方法一致。
}
forEachOrdered()
和forEach()
方法的区别在于forEachOrdered()
将始终按照流(stream)中元素的遇到顺序执行给定的操作,而forEach()
方法是不确定的。
-
在并行流(parallel stream)中,forEach()方法可能不一定遵循顺序,而forEachOrdered()将始终遵循顺序。
-
在序列流(sequential stream)中,两种方法都遵循顺序。
如果我们希望在每种情况下,不管流(stream)是连续的还是并行的,都要按照遵循顺序执行操作,那么我们应该使用forEachOrdered()方法。
toArray()
Stream toArray() 返回一个包含该流元素的数组。这是一个 终端操作 ,也就是说,它可以遍历流,产生一个结果或副作用。在执行终端操作后,流管道被认为已被消耗,不能再被使用。
reduce()
collect()
将经过操作的数据流进行收集到另一个数据容器中。
max()
Stream.max()根据提供的比较器返回流的最大元素。比较器是一个比较函数,它对一些对象的集合施加一个总排序。max()是一个终端操作,它结合了流元素并返回一个汇总结果。所以,max()是还原的一个特例。该方法返回Optional实例。
min()
Stream.min() 根据提供的比较器返回流中的最小元素。比较器是一个比较函数,它对一些对象的集合施加总的排序。min()是一个 终端操作 ,它结合流元素并返回一个汇总结果。所以,min()是缩减的一个特例。该方法返回Optional实例。
count()
long count() 返回流中元素的数量。这是 还原 操作的一个特例(还原操作需要一连串的输入元素,并通过重复应用合并操作将它们合并成一个单一的汇总结果)。这是一个 终端操作 ,也就是说,它可以遍历流,产生一个结果或副作用。在执行终端操作后,流管道被认为已被消耗,不能再被使用。
anyMatch()
Stream anyMatch(Predicate predicate) 返回此流中是否有任何元素
与提供的谓词相匹配。这是一个 短路的终端操作。 如果一个终端操作在面对无限的输入时,可能在有限的时间内终止,那么它就是短路的。
allMatch()
Stream allMatch(Predicate predicate) 返回此流的所有元素
是否与提供的谓词相匹配。这是一个 短路的终端操作。如果一个终端操作在面对无限的输入时,可以在有限的时间内结束,那么它就是短路的。
noneMatch()
提示: 它的作用与 Stream anymatch()方法相反。
findFirst()
Stream findFirst() 返回一个Optional(一个容器对象,可以包含也可以不包含一个非空值),描述这个流的 第一个 元素,如果流是空的,则返回一个空的Optional。如果该流没有相遇顺序,那么任何元素都可以被返回。
findAny()
返回流中的任意元素
中间操作
distinct() 返回一个由流中不同元素组成的流。 distinct()是 Stream 接口的方法。这个方法使用hashCode()和equals()方法来获取不同的元素。在有序流的情况下,独特元素的选择是稳定的。但是,在无序流的情况下,不同元素的选择不一定是稳定的,可能会发生变化。 distinct()执行 有状态的中间操作 ,即它在内部保持一些状态以完成操作。
sorted()
Stream sorted() 返回一个由这个流的元素组成的流,按照自然顺序进行排序。对于有序的流,排序方法是稳定的,但对于无序的流,不保证稳定性。它是一个 有状态的中间操作 ,也就是说,在处理新的元素时,它可能会加入以前看到的元素的状态。
limit()
limit(long N)是 java.util.stream. Stream
对象的一个方法。limit()在有序的并行管道上可能是相当昂贵的,如果N的值很大,因为limit(N)被限制为返回遇到的顺序中的前N个元素,而不是任何N个元素。
其中N是流应限制的元素数,该函数返回新的流作为输出。
异常: 如果N的值是负的,那么函数会抛出 IllegalArgumentException 。
skip()
skip(long N)是 java.util.stream. Stream
对象的一个方法。这个方法接受一个long (N)作为参数,并在删除前N个元素后返回一个流。如果N的值很大,在有序的并行管道上,skip()可能相当昂贵,因为skip(N)被限制为跳过遇到的顺序中的前N个元素,而不是任何N个元素。
注意:
异常: 如果N的值是负的,那么函数会抛出 IllegalArgumentException 。
filter()
Stream filter(Predicate predicate) 返回一个由这个流中符合给定谓词的元素组成的流。这是一个 中间操作。这些操作总是懒惰的,即执行中间操作如filter()实际上并不执行任何过滤,而是创建一个新的流,当被遍历时,包含初始流中符合给定谓词的元素。
map()
Stream map(Function mapper) 返回一个由对该流的元素应用给定函数的结果组成的流。
Stream map(Function mapper)是一个 中间操作 这些操作始终是懒惰的。中间操作是在一个流实例上调用的,在它们完成处理后,会给出一个流实例作为输出。
Stream flatMap(Function mapper) 返回一个流,该流由将此流的每个元素替换为对每个元素应用所提供的映射函数而产生的映射流的内容的结果组成。Stream flatMap(Function mapper)是一个 中间操作 这些操作始终是懒惰的。中间操作在一个Stream实例上被调用,在它们完成处理后,它们给出一个Stream实例作为输出。
注意: 每个映射的流在其内容被放入该流后被关闭。如果一个映射的流是空的,就会使用一个空的流来代替。
flatMap() V/s map() :
-
map()接收一个流并将其转换为另一个流。它在Stream的每个元素上应用一个函数,并将返回值存入新的Stream中。它不对流进行平移。但flatMap()是map和flat操作的结合,即它对元素应用一个函数,并对它们进行平坦化处理。
-
map()只用于转换,但flatMap()既用于转换也用于平坦化。
peek()
peek(),它是一个消费者动作 ,基本上返回一个由这个流的元素组成的流,另外在每个元素上执行所提供的动作,因为元素从结果流中被消耗。这是一个中间操作, 因为它创建了一个新的流,当它被遍历时,包含了初始流中符合给定谓词的元素。
-
-
从Java 9开始,如果事先知道元素的数量并且在流中没有变化,由于性能优化,.peek()语句将不会被执行。可以通过改变元素数量的命令(正式的)来强制执行其操作,例如:.filter(x -> true)。
-
5、综合案例
Author.java
package cn.imyjs.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Author {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
}
Book.java
package cn.imyjs.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Long id;
private String name;
private String category;
private Integer score;
private String intro;
}
DataSource.java
package cn.imyjs.utils;
import cn.imyjs.entity.Author;
import cn.imyjs.entity.Book;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DataSourceUtils {
public static List<Author> getAuthors() {
//数据初始化
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
//书籍列表
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
return authorList;
}
}
测试环境
package cn.imyjs.demo;
import cn.imyjs.entity.Author;
import cn.imyjs.utils.DataSourceUtils;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
/**
* @author YJS
* @ClassName Demo
* @Description Stream案例
* @webSite www.imyjs.cn
* @date 2023/3/18 19:16
*/
public class Demo {
private static List<Author> authors = new ArrayList<>();
@Before
public void setUp() {
authors = DataSourceUtils.getAuthors();
}
@Test
public void test1(){
}
}
打印所有年龄小于18的作家的名字,并且要注意去重
@Test
public void test1(){
// 方式一:
authors.stream() // 获取流
// 去重
.distinct()
// 过滤掉年龄大于等于18的
.filter(author -> author.getAge() < 18)
// 终结操作 打印流中剩余对象的名称
.forEach(author -> System.out.println(author.getName()));
System.out.println("==========================");
// 方式二:
authors.stream()
.distinct()
// 注意:必须先进行条件过滤操作,因为经过map操作后,流中的对象不再是Author
.filter(author -> author.getAge() < 18)
.map(Author::getName)
.forEach(System.out::println);
}
@Test
public void test2(){
authors.stream()
.distinct()
.map(Author::getName)
.forEach(System.out::println);
}
@Test
public void test3(){
authors.stream()
.distinct()
.map(Author::getName)
.filter(name -> name.length() > 1)
.forEach(System.out::println);
}
@Test
public void test4(){
authors.forEach(author -> System.out.println(author.getName()));
System.out.println("====================");
authors.stream()
.map(Author::getName)
.forEach(System.out::println);
System.out.println("====================");
authors.stream()
// 注意:以下操作将会流修改为Integer类型对象
.map(author -> author.getAge() + 10)
//.map(Author::getName)
.map(age -> age + 10)
.forEach(System.out::println);
}
@Test
public void test5(){
Set<String> collect = authors.stream()
.flatMap(author -> author.getBooks().stream())
// Redundant 'distinct()' call: elements will be distinct anyway when collected to a Set
// 冗余的“distinct()”调用:元素在收集到集合时无论如何都是不同的
.distinct()
.map(Book::getName)
.collect(Collectors.toSet());
collect.forEach(System.out::println);
}
@Test
public void test6(){
List<String> collect = authors.stream()
.map(Author::getName)
.distinct()
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
获取这些作家所出书籍的最高分和最低分并打印。
@Test
public void test7(){
// 方式一:
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getScore)
.max((b1, b2) -> b1 - b2);
System.out.println("最高分:" + max.get());
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getScore)
.min((b1, b2) -> b1 - b2);
System.out.println("最低分:" + min.get());
// 方式二:
Optional<Integer> maxScore = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getScore)
.sorted((o1, o2) -> o2.compareTo(o1))
.findFirst();
System.out.println(maxScore.get());
Optional<Integer> minScore = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getScore)
.sorted()
.findFirst();
System.out.println(minScore.get());
}
@Test
public void test8(){
long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
}
@Test
public void test9(){
authors.stream().distinct().map(Author::getName).forEach(System.out::println);
}
@Test
public void test10(){
authors.stream()
.flatMap(author -> author.getBooks().stream())
.flatMap(book -> Stream.of(book.getCategory().split(",")))
.distinct()
.forEach(System.out::println);
}
打印所有书籍的名字。要求对重复的元素进行去重。
@Test
public void test11(){
authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getName)
.distinct()
.forEach(System.out::println);
}
@Test
public void test12(){
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge()))
.skip(1).forEach(author -> System.out.println(author.getName() + "====" + author.getAge()));
}
@Test
public void test13(){
authors.stream()
.distinct()
.sorted((a1, a2) -> a2.getAge().compareTo(a1.getAge()))
.limit(2)
// Result of 'Author.getName()' is ignored
// 以下写法会导致“Author.getName()”的结果被忽略
// .forEach(Author::getName);
.forEach(author -> System.out.println(author.getName()));
}
@Test
public void test14(){
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(author -> System.out.println(author.getName() + "====" + author.getAge()));
}
获取一个Map集合,map的key为作者名,value为List<Book>
@Test
public void test15(){
Map<String, List<Book>> collect = authors.stream()
.distinct()
.collect(Collectors.toMap(
Author::getName, Author::getBooks
));
collect.forEach((s, books) -> {
System.out.println(s);
System.out.println(books.toString());
});
}
判断是否有年龄在29以上的作家
@Test
public void test16(){
boolean b = authors.stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(b);
}
判断是否所有的作家都是成年人
@Test
public void test17(){
System.out.println(authors.stream()
.allMatch(author -> author.getAge() >= 18));
}
@Test
public void test18(){
System.out.println(authors.stream()
.noneMatch(author -> author.getAge() < 100));
}
@Test
public void test19(){
Optional<Author> any = authors.stream()
.filter(author -> author.getAge() > 18)
.findAny();
any.ifPresent(author -> System.out.println(author.getName()));
}
@Test
public void test20(){
authors.stream()
.min(Comparator.comparing(Author::getAge))
.ifPresent(author -> System.out.println(author.getName()));
}
@Test
public void test21(){
Integer sumAge = authors.stream()
.distinct()
.map(Author::getAge)
.reduce(0, Integer::sum);
System.out.println(sumAge);
}
@Test
public void test22(){
Integer maxAge = authors.stream()
.map(Author::getAge)
.reduce(Integer.MIN_VALUE, (result, integer) -> integer > result ? integer : result);
System.out.println(maxAge);
}
使用reduce求所有作者中年龄的最小值
@Test
public void test23(){
Integer minAge = authors.stream()
.map(Author::getAge)
.reduce(Integer.MAX_VALUE, (result, integer) -> integer < result ? integer : result);
System.out.println(minAge);
}
微信关注
编程那点事儿