目录
3、Stream流
3.1、概述
Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作,可以更方便我们对集合或数组操作。
之前的inputStream、outputStream都是io流,是数据读写使用的,此处的Stream都是对数组集合操作用的
3.2、案例数据准备
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* @Description 入门案例
* @Author:SQJ
* @DATE: 2022/10/11
*/
public class stream1 {
public static void main(String[] args) {
//需求:我们可以调用getAuthors方法获取到作家的集合,现在需要打印所有年龄小于18的作家的名字,并且注意去重。
getAuthors().stream() //转换为Stream流
.distinct() //去重
.filter(new Predicate<Author>() { //过滤 条件为年龄小于18
@Override
public boolean test(Author author) {
return author.getAge()<18;
}
}).forEach(new Consumer<Author>() { //过滤的结果进行处理
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
System.out.println("-------------");
//对上述的方法通过lambda表达式转换
getAuthors().stream()
.distinct()
.filter(author -> author.getAge()<18)
.forEach((Author author) -> System.out.println(author.getName()));
}
// 初始化一些数据
private static List<Author> getAuthors() {
Author author1 = new Author(1L, "sqj", "my introduction 1", 18, null);
Author author2 = new Author(2L, "www", "my introduction 2", 19, null);
Author author3 = new Author(3L, "hhh", "my introduction 3", 14, null);
Author author4 = new Author(4L, "ddd", "my introduction 4", 29, null);
Author author5 = new Author(5L, "eee", "my introduction 5", 12, null);
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
// 上面是作者和书
books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
books1.add(new Book(2L, "高效", "书名2", 84D, "这是简介哦"));
books1.add(new Book(3L, "喜剧", "书名3", 83D, "这是简介哦"));
books2.add(new Book(5L, "天啊", "书名4", 65D, "这是简介哦"));
books2.add(new Book(6L, "高效", "书名5", 89D, "这是简介哦"));
books3.add(new Book(7L, "久啊", "书名6", 45D, "这是简介哦"));
books3.add(new Book(8L, "高效", "书名7", 44D, "这是简介哦"));
books3.add(new Book(9L, "喜剧", "书名8", 81D, "这是简介哦"));
author1.setBookList(books1);
author2.setBookList(books2);
author3.setBookList(books3);
author4.setBookList(books3);
author5.setBookList(books2);
return new ArrayList<>(Arrays.asList(author1, author2, author3, author4, author5));
}
}
需求:我们可以调用getAuthors方法获取到作家的集合,现在需要打印所有年龄小于18的作家的名字,并且注意去重。
实现:
private static void test00() {
//需求:我们可以调用getAuthors方法获取到作家的集合,现在需要打印所有年龄小于18的作家的名字,并且注意去重。
getAuthors().stream() //转换为Stream流
.distinct() //去重
.filter(new Predicate<Author>() { //过滤 条件为年龄小于18
@Override
public boolean test(Author author) {
return author.getAge()<18;
}
}).forEach(new Consumer<Author>() { //过滤的结果进行处理
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
System.out.println("-------------");
//对上述的方法通过lambda表达式转换
getAuthors().stream()
.distinct() //去重
.filter(author -> author.getAge()<18)
.forEach((Author author) -> System.out.println(author.getName()));
}
3.4、常用操作
3.4.1、创建流
单列集合:集合对象.stream();
数组:Arrays.stream(数组)或者使用Stream.of来创建
Integer [] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
stream.filter(integer -> integer>3)
.forEach(integer -> System.out.println(integer));
双列集合:转换成单列集合后再创建
map不能直接转换为Stream流,可以使用map的entryset方法转换为单列集合set
//双列集合 map 转换为单列集合再创建
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("sqj",15);
hashMap.put("www",16);
hashMap.put("sss",17);
hashMap.put("sss",17);
//Stream<Map.Entry<String, Integer>> stream1 = hashMap.entrySet().stream();
Set<Map.Entry<String, Integer>> entrySet = hashMap.entrySet(); //将map转换为一个set,其中set的每一个元素就是map的一对键值对
entrySet.stream()
.filter(new Predicate<Map.Entry<String, Integer>>() {
@Override
public boolean test(Map.Entry<String, Integer> stringIntegerEntry) {
return stringIntegerEntry.getValue()>15;
}
}).distinct()
.sorted(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue()-o2.getValue();
}
})
.forEach(new Consumer<Map.Entry<String, Integer>>() {
@Override
public void accept(Map.Entry<String, Integer> stringIntegerEntry) {
System.out.println(stringIntegerEntry.getKey());
}
});
entrySet.stream()
.filter((Map.Entry<String, Integer> stringIntegerEntry) ->stringIntegerEntry.getValue()>15).distinct()
.sorted((Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2)-> o2.getValue()-o1.getValue())
.forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry.getKey()));
3.4.2、中间操作
filter
对流中的元素进行条件过滤,符合过滤条件的才会继续留在流中
private static void test00() {
//需求:我们可以调用getAuthors方法获取到作家的集合,现在需要打印所有年龄小于18的作家的名字,并且注意去重。
getAuthors().stream() //转换为Stream流
.distinct() //去重
.filter(new Predicate<Author>() { //过滤 条件为年龄小于18
@Override
public boolean test(Author author) {
return author.getAge()<18;
}
}).forEach(new Consumer<Author>() { //过滤的结果进行处理
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
map
对流中的元素进行计算或者数据类型进行转换
需求:打印所有作家的姓名
实现:
private static void test03() {
List<Author> authors = getAuthors();
//转换
authors.stream()
.map(author -> author.getName())
.forEach(a -> System.out.println(a));
System.out.println("----------");
//计算
authors.stream()
.map(author -> author.getAge())
.map(age -> age -10)
.forEach(a -> System.out.println(a));
}
distinct
去重操作,需要在bean对象重写equals方法,即加注解 @EqualsAndHashCode
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法
sorted
sorted两个方法。无参方法使用的时候需要实现Conparable接口,实现接口下的方法
有参方法使用的时候直接创建Comparable接口
//对流中的元素按照年龄降序排序,并要求不能有重复的元素
private static void test5() {
getAuthors().stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.forEach(a -> System.out.println(a));
}
limit
可以设置流的最大长度,超出的部分将被抛弃掉
例如:对流中元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名
//要求对流中的元素进行排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名
private static void test8() {
getAuthors().stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.limit(2)
.forEach(a-> System.out.println(a));
}
skip
跳过流中的前n个元素,返回剩下的元素
例如:打印除了年龄最大的作家以外的其他作家,要求不能有重复元素,并且按照年龄降序排列。
//打印除了年龄最大的作家以外的其他作家,要求不能有重复元素,并且按照年龄降序排列。
private static void test9() {
getAuthors().stream()
.distinct()
.sorted((o1,o2)-> o2.getAge()-o1.getAge())
.skip(1)
.forEach(author -> System.out.println(author));
}
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素
//打印所有书籍的名字,要求对重复的元素进行去重
private static void test10() {
getAuthors().stream()
.flatMap(author-> author.getBookList().stream())
.distinct()
.forEach( a-> System.out.println(a.getName()));
//打印现有数据的所有分类,要求对分类进行去重,不能出现这种格式:哲学,爱情
private static void test11() {
getAuthors().stream()
.flatMap(author -> author.getBookList().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(a -> System.out.println(a));
}
3.4.3、终结操作
如果流的一系列的操作要成功触发并生效,那必须需要有终结操作,一个流只能做一次终结操作。
foreach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体的操作
count
可以用来获取当前流中的元素
例子:打印这些作家的所处书籍的数目,注意删除重复元素
//打印这些作家的所出书籍的数目,注意删除重复元素
private static void test12() {
long count = getAuthors().stream()
.distinct()
.flatMap(author -> author.getBookList().stream())
.distinct()
.count();
System.out.println(count);
}
max&min
可以用来获取流中的最值
例子:分别获取这些作家的所出书籍的最高分和最低分并打印。
//分别获取这些作家的所出书籍的最高分和最低分并打印。
private static void test13() {
Double aDouble = getAuthors().stream()
.flatMap(author -> author.getBookList().stream())
.distinct()
.map(book -> book.getScore())
.min(new Comparator<Double>() {
@Override
public int compare(Double o1, Double o2) {
return o1.intValue() - o2.intValue();
}
}).get();
System.out.println(aDouble);
}
collect
把当前流转换成一个集合
例子:获取一个存放所有作者名字的 List 集合(tolist方法)
//例子:获取一个存放所有作者名字的 List 集合
private static void test14() {
List<String> collect = getAuthors().stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println("collect list = " + collect);
}
例子:获取一个所有书名的 Set 集合(toset)
//例子:获取一个所有书名的 Set 集合
private static void test15() {
Set<String> collect = getAuthors().stream()
.flatMap(author -> author.getBookList().stream())
.distinct()
.map(book -> book.getName())
.collect(Collectors.toSet());
System.out.println("collect set = " + collect);
}
例子:获取一个 Map 集合,map 的 key 为作者名,value 为 List<Book>(tomap方法)
//例子:获取一个 Map 集合,map 的 key 为作者名,value 为 List<Book>
private static void test16() {
Map<String, List<Book>> collect = getAuthors().stream()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBookList()));
System.out.println("collect map = " + collect);
}
匹配相关
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
例子:判断是否有年龄在29以上的作家
//例子:判断是否有年龄在29以上的作家
private static void test17() {
boolean b = getAuthors().stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println("b = " + b);
}
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果结果都符合则为true,否则为false
例子:判断所有作家是否为成年人
//例子:判断所有作家是否为成年人
private static void test18() {
boolean b = getAuthors().stream()
.allMatch(author -> author.getAge() > 18);
System.out.println("b = " + b);
}
noneMatch
可以判断流中的元素是否都不符合匹配条件,如果都不符合结果为true,否则结果为false
例子:判断作家是否都没有超过100岁
//例子:判断作家是否都没有超过100岁
private static void test19() {
boolean b = getAuthors().stream()
.noneMatch(author -> author.getAge() >= 100);
System.out.println("b = " + b);
}
查找相关
findAny
获取流中的任意一个元素,该方法没有办法保证获取的一定是流中的第一个元素。
例子:获取任意一个大于18的作家,如果存在就输出他的名字
//例子:获取任意一个大于18的作家,如果存在就输出他的名字
private static void test20() {
Optional<Author> any = getAuthors().stream()
.filter(author -> author.getAge()>18)
.findAny();
any.ifPresent((Author author) ->{ //如果存在 执行方法体中方法,避免空指针问题
System.out.println(author.getName());
});
}
findFirst
获取流中的第一个元素
例子:获取一个年龄最小的作家,并输出他的姓名。
//例子:获取一个年龄最小的作家,并输出他的姓名。
private static void test21() {
getAuthors().stream()
.sorted((o1,o2) -> o1.getAge()-o2.getAge())
.map(author -> author.getName())
.findFirst()
.ifPresent(System.out::println);
}
reduce归并(难点)
对流中的数据按照你指定的计算方式计算出一个结果。(多个变成一个 -> 缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素合初始化值进行计算,计算结果在和后面的元素计算。
reduce两个参数的重载形式内部计算方式:
上述内部操作理解:
reduce相当于是将以上操作进行了抽取
例子:求所有作者的年龄和
//例子:求所有作者的年龄和
private static void test22() {
Integer reduce = getAuthors().stream()
.map(author -> author.getAge())//map reduce组合
.distinct()
.reduce(0, (result, integer2) -> result + integer2);
System.out.println("reduce = " + reduce);
}
例子:使用reduce求所有作者中年龄最大的 (前面的max/min方法底层实际就是使用的reduce方法)
//例子:使用reduce求所有作者中年龄最大的
private static void test23() {
Integer reduce = getAuthors().stream()
.map(Author::getAge)
.reduce(Integer.MIN_VALUE, (result, integer) -> result = result < integer ? integer : result);
System.out.println("reduce max = " + reduce);
}
例子:使用reduce求所有作者中年龄最小值
//例子:使用reduce求所有作者中年龄最小值
private static void test24() {
Integer reduce = getAuthors().stream()
.map(Author::getAge)
.reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer < integer2 ? integer : integer2;
}
});
Integer reduce1 = getAuthors().stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (result, integer) -> result < integer ? result : integer);
System.out.println(reduce);
System.out.println(reduce1);
}
reduce单个参数的重载方法
//来自jdk源码
boolean foundAny = false;
* T result = null;
* for (T element : this stream) {
* if (!foundAny) {
* foundAny = true;
* result = element;
* }
* else
* result = accumulator.apply(result, element);
* }
* return foundAny ? Optional.of(result) : Optional.empty();
如果使用单个参数的重载方法,就无需传入初始值,原因看源码可得,如果是第一次进来该方法会将第一个值赋值为初始值
//优化后代码为:取最大值
private static void test25() {
getAuthors().stream()
.map(Author::getAge)
.reduce((result, element) -> result > element ? result : element).ifPresent(a -> System.out.println(a));
//System.out.println("reduce max2 = " + integer);
}
reduce三个参数的重载方法会涉及到并行流,详细后面说明
3.5、Stream使用的注意事项
1、惰性求值,如果没有终结操作,中间操作是不会得到执行
2、流是一次性的,一旦一个流对象经过一个终结操作后,这个流不能再被使用
3、不回影响原数据,流中可以多数据做很多处理,但不回影响原来集合中的元素