Stream流是JDK8提供的新特性。使用的是函数式编程的模式。
它可以被用来对集合或数组进行链状流式的操作。
和之前的IO流进行区分,IO流是针对文件和数据操作
可以更方便的对集合和数据进行操作。
依赖准备:lombok+hutool工具包即可
package com.zhc.demo02.entity;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* @Author zhuhuacong
* @Date: 2023/12/12/ 10:40
* @description 作者
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Author {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
public static List<Author> getAuthor(){
List<Author> authors = new ArrayList<>();
Snowflake snowflake = new Snowflake();
for (int i = 0; i < 10; i++) {
Author author = new Author();
List<Book> books = new ArrayList<>();
books.add(getBook(snowflake));
books.add(getBook(snowflake));
books.add(getBook(snowflake));
books.add(getBook(snowflake));
books.add(getBook(snowflake));
author.setAge(RandomUtil.randomInt(20,60));
author.setId(RandomUtil.randomLong());
author.setName(RandomUtil.randomString(5));
author.setIntro(RandomUtil.randomString(100));
author.setBooks(books);
authors.add(author);
}
return authors;
}
public static Book getBook(Snowflake snowflake ){
return new Book(snowflake.nextId(), RandomUtil.randomString(5), RandomUtil.randomString(100), RandomUtil.randomInt(50, 100));
}
}
package com.zhc.demo02.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Author zhuhuacong
* @Date: 2023/12/12/ 10:41
* @description 书
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Book {
private Long id;
private String name;
private String category;
private Integer score;
}
方法一、按照原本是方法遍历对象并打印
(略)
方法二、使用stream()流
需要注意:
java所有的集合类都带有这个stream方法
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
forEach方法需要传入我们前面学到的Consumer
(消费者)的实现类
void forEach(Consumer<? super T> action);
以此记得得出写法如下
List<Author> authors = Author.getAuthor();
authors.stream() // 集合对象的stream方法
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author + "\n");
}
});
使用省略规则简化后,得到的写法是
authors.stream()
.forEach(author -> System.out.println(author + "\n"));
为了更好的模拟场景。可以将名字和年龄的生成逻辑进行修改。使用如下的方法。
// 类似 author.setName(RandomUtil.randomString("张王李",2));
同时难度加大
方法一、传统遍历代码
使用传统方法处理逻辑应该是这样的:
方法二、使用stream()流
使用这个方法可以对元素进行去重处理。需要注意:
Object.equals(Object)
进行判断,所以在判断POJO实体类时,是需要重写equals和hashCode代码的sequential()
方法后再进行去重过滤器,完整方法如下
Stream<T> filter(Predicate<? super T> predicate);
看到了前一章学习的熟悉的方法Predicate
(断言,实现这个函数式接口可以判断是否筛选过滤出该对象
代码如下:(最终优化
// 打印所以年龄小于18的作家名字,并且注意去重
List authors = Author.getAuthor();
authors.stream()
.distinct()
.filter(author -> author.getAge() < 18)
.forEach( a -> System.out.println(a.getName()+"\t"+a.getAge()));
查看打印的结果可以发现,由于stream的distinct()方法是调用equals进行去重的
其实结合实际生产中,往往需要根据作家名称去重
代码如下,使用到的map()方法,后续会进行研究
authors.stream()
.filter(a -> a.getAge() < 18)
.map(Author::getName)
.distinct()
.forEach(System.out::println);
以场景3为例,我们调用了4个不同的stream流方法,打断点进入调试模式后就可以找到对应的“流调试”按钮
进入该模式后就可以看到我们的刚才的操作了
1、转换为stream
2、过滤
3、提取元素
4、去重
5、遍历
这个就不用看了
流在创建好之后,就需要一些中间操作对集合进行修改
操作完成后,需要结束这一次Stream流,必须要有终结操作,前面的代码才能生效
创建流的核心思想:将集合转换为流
像列表、链表、Set就是单列集合
语法:集合对象.stream()
(在父类collection已经新增了stream这个方法,可以直接转换
语法:Arrays.stream(数组)
——使用到了数组的工具类Arrays进行转换操作
或者Stream.of()
也可以转换数组——注意这个方法的参数是一个【可变参数列表】,在java的低层,可变参就是一个数组。
Map本身是无法直接转换为Stream流的,需要转换成单列集合后再转换为stream流
map.entrySet().stream()
可以对流中的元素进行条件过滤,**符合过滤条件(Predicate返回结果为true)**的才能继续留在流中。
前面已经接触过了,就不做赘述
可以对流中的元素进行计算或者转换。
查看map源码,可以发现,maper传入的是一个Function接口,相当于取定义了一个复杂的类型转换(前一章学到的)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
做个小练习:将集合中每一个author对象转换为JSON字符串
List<Author> authors = Author.getAuthor();
// 匿名内部类式编程
authors.stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return JSON.toJSONString(author);
}
})
.forEach(System.out::println);
// lambda
authors.stream()
.map(author -> JSON.toJSONString(author))
.forEach(System.out::println);
// 跟进一步简写(IDEA提供
authors.stream()
.map(JSON::toJSONString)
.forEach(System.out::println);
通过这个案例可以发现:
去除流中的重复元素。
注意:distinct方法是依赖Obiect的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。(前面⭐️distinct()方法提到了,不进行赘述了
可以对集合进行排序。
查看源码可以发现sorted方法其实有两种重载的方式:一种是无参,一种是带有函数式接口参数Comparator
(比较器)。
查看源码可知,要求排序的对象去实现Comparable接口
,才能进行排序。
如果直接调用,则会抛出异常ClassCastException
举个例子:按作家的年龄进行排序,先使作家类实现Comparable接口
,然后添加如下代码
/**
* 比较
*
* @param o o
* @return int -1、0或1,相当于对象小于、等于或大于指定对象。
*/
@Override
public int compareTo(Object o) {
if (o instanceof Author){
Author author = (Author) o;
return this.age.compareTo(author.getAge());
}
return 0;
}
运行验证,通过
authors.stream()
.sorted()
.forEach(System.out::println);
关于顺序,其实不需要特意去记忆,只需要进行一次可测试即可。
源码方法Stream
从匿名内部类开始简化
authors.stream()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o1.getAge().compareTo(o2.getAge());
}
})
.forEach(System.out::println);
// lambda简化
authors.stream()
.sorted((o1, o2) -> o1.getAge().compareTo(o2.getAge()))
.forEach(System.out::println);
// idea提供简化
authors.stream()
.sorted(Comparator.comparing(Author::getAge))
.forEach(System.out::println);
值得一提是idea提供的快捷简写方法。(不一定要会写,但是要懂得看)
设置流的最大长度,超出的部分将会被抛弃。
:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
补充一下题意,为了体现去重的效果,选择补充根据名字+年龄的字段进行去重
// 匿名内部类写法
authors.stream()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o2.getAge()-o1.getAge();
}
})
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName()+author.getAge();
}
})
.distinct()
.limit(2)
.forEach(System.out::println);
// 简化后写法
authors.stream()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.map(author -> author.getName()+author.getAge())
.distinct()
.limit(2)
.forEach(System.out::println);
跳过前面的n个元素,返回剩下的元素。
(顾名思义,不做赘述
flatMap的效果和map类似但也有所不同
**如何理解?**比如一个作者拥有多部作品,如果需要从“作家流”取出所有作品book,作为“作品流”,这个时候就可以用到flatMap了。
阅读源码也不难发现,flatMap要求传入的函数式接口Function中唯一的方法,已经定义好的返回的类型Stream
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
举个例子
// 匿名内部类写法
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println(book);
}
});
匿名内部类在flatMap中是比较繁琐的,因为需要指定 Stream>
的类型才能保证forEach正常介绍到对象类型
// 简化后
authors.stream()
.flatMap(author -> author.getBooks().stream())
.forEach(System.out::println);
终结操作另起一文!