流是
数据渠道
,用于操作数据源
,所生成一种新的元素序列
。集合讲的是数据,流讲的是计算
,是操作
。
Stream是Java8中处理集合
的关键抽象概念,它可以指定希望对集合的操作
,可以执行复杂的查找
、过滤和映射数据
等操作。
使用Stream API 对集合的数据进行操作
,类似于SQL执行的数据库查询
,也可以用来并行执行
操作,其提供了一种高效且易于使用的处理数据方式
。
注意点:
不会存储元素
不会改变数据源对象
,相反会返回产生一个持有结果的新Stream
延迟执行
的,这意味着他们会等到需要结果的时候才执行
。三步走
获取一个数据源(集合,数组),从而获取一个流
产生方式:
Collection 系列集合
提供的串行流:stream()
、并行流: paralleStream()
List list = new ArrayList<>();
Stream stream1 = list.stream();
Arrays
中的静态方法stream(T[] array)
获取数组流
Arrays.stream(T[] array)的源码:
public static Stream stream(T[] array) {
return stream(array, 0, array.length);
}
用例:
Stu[] stus = new Stu[10];
Stream stream2 = Arrays.stream(stus);
/*
public static Stream stream(T[] array) {
return stream(array, 0, array.length);
}
*/
Stream类
中的静态方法 of()
Stream.of() 源码:
//1.单参泛型of
public static Stream of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
//2.可变参数
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static Stream of(T... values) {
return Arrays.stream(values);
}
用例:
Stream stream3 = Stream.of("hxh", "aj", "hhh");
Stream
类的静态方法 iterate
创建无限流
iterate方法:
Stream
参数 seed 种子起始值
,UnaryOperator
函数式接口 继承Function
此时参数类型符合返回值类型一致
用例:
//4.使用Stream类的静态方法 iterate 创建无限流
//Stream iterate(final T seed, final UnaryOperator f)
//参数 seed 种子起始值,
// UnaryOperator 函数式接口 继承Function 此时参数类型符合返回值类型一致
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
//中间操作和终止操作
stream4.limit(5).forEach(System.out::println);
//0
//2
//4
//6
//8
Stream
类的静态方法 generate
创建无限流
generate方法参数为Supplier
供给型接口
//5.使用Stream类的静态方法 generate 创建无限流
//参数为Supplier 供给型接口
Stream generateStream = Stream.generate(() -> Math.random());
generateStream.limit(5).forEach(System.out::println);
//0.4762976596937549
//0.08577913333772513
//0.32149010682857515
//0.31059489250233197
//0.45181354173159927
一个中间操作链,用Stream API 对数据源数据进行操作处理
注意点:
不会执行
一次执行
,此时就称为延迟加载
或者惰性求值
验证是否是延迟加载:
@Test
public void test2(){
//取age>30的Stu元素
//若只有中间操作,则不会执行
Stream stuStream = stuList.stream().filter((i) -> {
System.out.println("验证是否是延迟加载");
return i.getAge() > 40;
});
//此时只有中间操作,无终止操作,无结果,控制台无输出
}
此时只有中间操作,无终止操作
,无结果,控制台无输出
。
此时加上终止操作后:
@Test
public void test2(){
//取age>30的Stu元素
//若只有中间操作,则不会执行
Stream stuStream = stuList.stream().filter((i) -> {
System.out.println("验证是否是延迟加载");
return i.getAge() > 40;
});
//终止操作 执行后,所有的中间操作一次执行,此时就称为延迟加载或者惰性求值
stuStream.forEach(System.out::println);
}
此时结果为:
验证是否是延迟加载
验证是否是延迟加载
验证是否是延迟加载
验证是否是延迟加载
Stu{id=4, name='cc', age=42}
验证是否是延迟加载
Stu{id=5, name='dd', age=52}
结论:若只有中间操作,则不会执行中间操作。终止操作 执行后,所有的中间操作一次执行。最后流中只有经过操作过滤后的元素。
迭代:
- 内部迭代:迭代过程操作由Stream API 内部
自主完成,无需自行再次编写。
- 外部迭代:由程序编写人员
自己通过一些迭代方法进行的迭代操作。
filter
-过滤Stream
断言型
接口参数 即条件判断过滤
用例:
先创建一个Stu类List集合
List stuList = Arrays.asList(
new Stu(1,"hh",22),
new Stu(2,"aa",22),
new Stu(3,"bb",32),
new Stu(4,"cc",42),
new Stu(5,"dd",52)
);
filter过滤实现:
//取age>30的Stu元素
//若只有中间操作,则不会执行
Stream stuStream = stuList.stream()
.filter((i) -> i.getAge() > 40);
//终止操作 执行后,所有的中间操作一次执行,此时就称为延迟加载或者惰性求值
stuStream.forEach(System.out::println);
结果:
Stu{id=4, name='cc', age=42}
Stu{id=5, name='dd', age=52}
limit
-限定元素数量limit(n)
通过截断流
,使流中元素个数不超过
指定数量
stuList.stream()
.filter((s) ->{
System.out.println("测试迭代几次");
return s.getAge()>40;
}).limit(1).forEach(System.out::println);
结果:
测试迭代几次
测试迭代几次
测试迭代几次
测试迭代几次
Stu{id=4, name='cc', age=42}
结果发现:先通过filter()过滤,迭代到想要的过滤结果后
,再根据limit(n)
,直接截断流
,后续操作不继续,限制其流中元素个数为n
,此操作称为短路
操作,短路操作也用于提高效率
;
所以前3次元素不在结果中,但都进行迭代判断,打印了3次后后面的元素再次进行迭代,发现元素满足过滤条件,但limit限制只要一个,即最后一次迭代后直接截断流,结果为第一个满足过滤条件的元素。
skip
-跳过元素skip(n)
返回一个跳过前n个元素
的流,若流中元素不足n个
,则返回一个空流
。
其与limit(n)互补
//skip(n) 跳过前n个元素
stuList.stream()
.skip(2).forEach(System.out::println);
/* 结果:
Stu{id=3, name='bb', age=32}
Stu{id=4, name='cc', age=42}
Stu{id=5, name='dd', age=52}
*/
stuList.stream().skip(6).forEach(System.out::println);
//流中元素个数总数为5,小于6,则返回空流,没有结果值
distinct
-去重hashCode()
和equals()
来去除重复
元素先在stuList中添加几个重复元素用于测试:
List stuList = Arrays.asList(
new Stu(1,"hh",22),
new Stu(2,"aa",22),
new Stu(3,"bb",32),
new Stu(4,"cc",42),
new Stu(4,"cc",42),
new Stu(4,"cc",42),
new Stu(4,"cc",42),
new Stu(5,"dd",52)
);
此时的Stu类中没有生成重写hashCode()
和equals()
方法,测试:
//distinct 去重
stuList.stream().distinct().forEach(System.out::println);
但是结果发现,并没有去重:
Stu{id=1, name='hh', age=22}
Stu{id=2, name='aa', age=22}
Stu{id=3, name='bb', age=32}
Stu{id=4, name='cc', age=42}
Stu{id=4, name='cc', age=42}
Stu{id=4, name='cc', age=42}
Stu{id=4, name='cc', age=42}
Stu{id=5, name='dd', age=52}
此时,在Stu类中生成重写hashCode()
和equals()
方法:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Stu stu = (Stu) o;
return Objects.equals(id, stu.id) &&
Objects.equals(name, stu.name) &&
Objects.equals(age, stu.age);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
再次测试用例后,发现结果已经去重:
Stu{id=1, name='hh', age=22}
Stu{id=2, name='aa', age=22}
Stu{id=3, name='bb', age=32}
Stu{id=4, name='cc', age=42}
Stu{id=5, name='dd', age=52}
结论:
distinct()去重原理为通过流所生成元素的
hashCode()
和equals()
来去除重复
元素
- 接收Lambda
,将元素转换成其他形式
或提取信息
。
- 接收一个Function super T, ? extends R> mapper函数
作为参数,该函数会被应用到每个元素上
,并将其映射到一个新的元素
。
//map映射
List stringList = Arrays.asList("aa", "bb", "cc", "dd");
stringList.stream()
.map((x)->x.length()).forEach(System.out::println);
stringList.stream()
.map((x)->x.toUpperCase()).forEach(System.out::println);
结果:
2
2
2
2
AA
BB
CC
DD
从结果看出,流中的每个元素
都应用了map()里的参数中的Function函数
,并返回经过Function处理的元素。
SQL中的映射
,获取
对象中的某些属性
(即数据库中的某些字段)例如:获取Stu中的name属性
stuList.stream().map(Stu::getName).forEach(System.out::println);
测试结果:
hh
aa
bb
cc
cc
cc
cc
dd
函数
作为参数,将流中的每个值
都转换成另一个流
,然后把所有流连接成一个流
。每个部分流中的每个值成单独小流,再串成一个整体流。对比map映射:
1. map映射是将集合中的部分流添加到整体流中
,而flatMap映射是将集合中的部分流中的每个元素单独一个个地添加到整体流中
。
2. map映射: Stream
, flatMap映射:Stream
测试用例:
1. 写一个函数用于flatMap映射
/**
* 字符串拆分成字符后组成一个字符类型的流
* @param str
* @return
*/
public static Stream filterCharacter(String str){
List characterList = new ArrayList<>();
for (Character ch: str.toCharArray()
) {
characterList.add(ch);
}
return characterList.stream();
}
List stringList = Arrays.asList("aa", "bb", "cc", "dd");
Stream> st1 = stringList.stream()
.map(TestStream::filterCharacter);
//此时流的内容为 {{"aa"},{"bb"},{"cc"},{"dd"}} 4个单独的字符流对象组成的流
st1.forEach(System.out::println);
/* 再次遍历后
结果:4个流对象 即 Stream {{"aa"},{"bb"},{"cc"},{"dd"}}
java.util.stream.ReferencePipeline$Head@470e2030
java.util.stream.ReferencePipeline$Head@3fb4f649
java.util.stream.ReferencePipeline$Head@33833882
java.util.stream.ReferencePipeline$Head@200a570f
*/
System.out.println("----------------");
Stream st2 = stringList.stream().flatMap(TestStream::filterCharacter);
//此时流的内容为{"a","a","b","b","c","c","d","d"}
st2.forEach(System.out::println);
/* 再次遍历后
结果直接返回了单个的字符流
a
a
b
b
c
c
d
d
*/
sorted()
字典顺序
进行排序实现的Comparable中的compare to()
方法List stringList = Arrays.asList("ee", "bb", "ff", "dd","哈哈","啊");
//根据String类中Comparable方式进行默认排序,即compare to()方法
stringList.stream()
.sorted().forEach(System.out::println);
结果:
bb
dd
ee
ff
哈哈
啊
sorted(Comparator com)
实现Comparator接口的指定方法
进行排序stuList.stream().sorted(
(a,b) ->{
if (a.getAge().equals(b.getAge())){
return a.getName().compareTo(b.getName());
}else{
return a.getAge().compareTo(b.getAge());
}
}
).forEach(System.out::println);
终止操作,执行中间链操作
,并产生结果
match
利用断言型函数接口
,返回boolean值
是否匹配Optional类型
避免空指针异常
allMatch
-检查是否匹配所有
元素返回结果:
- true 匹配到了所有的元素
注意:和noneMatch()的false 结果代表集合不同
- false 没有匹配到所有的元素
说明匹配到条件集合中的真子集
boolean b = stuList.stream()
.allMatch((e) -> e.getAge() > 20);
System.out.println(b);//true
boolean b1 = stuList.stream()
.noneMatch((e) -> e.getAge() > 20);
System.out.println(b1);//flase
boolean b2 = stuList.stream()
.noneMatch((e) -> e.getAge() > 40);
System.out.println(b2);//flase
anyMatch
-检查是否至少匹配一个
元素返回结果:
- true 匹配到了条件集合中的真子集元素,一个或者多个
- false 一个元素都没有匹配到,空集
boolean hhh = stuList.stream()
.anyMatch((e) -> e.getName().equals("hhh"));
System.out.println(hhh);//false
boolean hh = stuList.stream()
.anyMatch((e) -> e.getName().equals("hh"));
System.out.println(hhh);//true
noneMatch
-检查是否所有元素都没有匹配到
返回结果:
- true 所有元素都没有匹配到
,空集
- false 不是所有的元素都没有匹配到 即匹配到了元素
,有匹配到的元素即返回false,真子集
boolean b1 = stuList.stream()
.noneMatch((e) -> e.getAge() > 20);
System.out.println(b1);//flase
boolean b2 = stuList.stream()
.noneMatch((e) -> e.getAge() > 40);
System.out.println(b2);//flase
boolean b3 = stuList.stream()
.noneMatch((e) -> e.getAge() > 50);
System.out.println(b3);//此时集合中只有一个元素能匹配到,返回了false
boolean b4 = stuList.stream()
.noneMatch((e) -> e.getAge() > 60);
System.out.println(b3);//所有的元素都没有匹配到 返回了true
findFirst
-返回第一个元素
Optional
Optional first = stuList.stream().findFirst();
System.out.println(first.get());// Stu{id=1, name='hh', age=22}
返回第一个元素,用Optional
集合类来封装,避免了空指针异常
findAny
-返回当前流中的任意一个元素
Optional
//从集合中随便找个age>30的Stu对象 可以使用串行流stream,也可以使用parallelStream 并行流
Optional any = stuList.parallelStream()
.filter((e) -> e.getAge() > 30).findAny();
System.out.println(any.get());//Stu{id=4, name='cc', age=42}
Optional any1 = stuList.stream()
.filter((e) -> e.getAge() > 30).findAny();
System.out.println(any1.get());//Stu{id=3, name='bb', age=32}
count
-返回流中元素总个数
long count();
long count = stuList.stream().count();
System.out.println(count);//8
max
-返回流中的最大值
Optional
//根据年龄大小进行正序排序找出最大值
Optional max = stuList.parallelStream()
.max((a, b) -> Integer.compare(a.getAge(), b.getAge()));
System.out.println(max.get()); //Stu{id=5, name='dd', age=52}
//根据年龄大小进行倒序排序找出最大值
Optional max1 = stuList.parallelStream()
.max((a, b) -> Integer.compare(b.getAge(), a.getAge()));
System.out.println(max1.get()); //Stu{id=1, name='hh', age=22}
//提取最大年龄 先映射提取集合中每个对象的年龄 再直接进行max方法比较 最后返回一个年龄值
Optional<Integer> maxAge = stuList.parallelStream()
.map(Stu::getAge)
.max(Integer::compare);
System.out.println(maxAge.get()); //52
min
-返回流中的最小值
Optional
//根据年龄大小进行正序排序找出最小值
Optional min = stuList.parallelStream()
.min((a, b) -> Integer.compare(a.getAge(), b.getAge()));
System.out.println(min.get());//Stu{id=1, name='hh', age=22}
//根据年龄大小进行倒序排序找出最小值
Optional min1 = stuList.parallelStream()
.min((a, b) -> Integer.compare(b.getAge(), a.getAge()));
System.out.println(min1.get()); //Stu{id=5, name='dd', age=52}
reduce
-将流中元素反复结合
起来,得到一个值
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//利用reduce归约函数,可以指定归约规则,将集合中的元素数值进行求和操作等
//以0为起始值,对集合中的各个值进行相加
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(sum);//45
//求出stuList集合中的年龄总和
//此时利用Integer类中的静态方法sum求和 无起始值,有可能为空,
// 则返回值自动变为Optional容器类封装过后的值
Optional ageSumOp = stuList.stream()
.map(Stu::getAge)
.reduce(Integer::sum);
System.out.println(ageSumOp.get());//296
有可能为空
,则返回值自动变为Optional容器类封装过后的值
collect
-将流转换为其他形式
接收一个Collector
接口的实现,用于Stream中元素做汇总的方法
利用Collectors
实用工具类中提供的很多静态实现Collector接口的方法
,进行相应的转换收集操作。
//收集stuList集合中的所有name值,转换为list集合
List nameList = stuList.stream().map(Stu::getName)
.collect(Collectors.toList());
nameList.forEach(System.out::println);
/*
结果:
hh
aa
bb
cc
cc
cc
cc
dd
*/
//获取年龄转化成set集合 去掉了重复值
Set ageSet = stuList.stream().map(Stu::getAge)
.collect(Collectors.toSet());
ageSet.forEach(System.out::println);
/*
结果:
32
52
22
42
*/
其他没有的现成静态方法
的数据结构集合,就使用Collectors.toCollection()
方法,该方法具体参数和返回值为:Collector toCollection(Supplier collectionFactory)
使用Collectors.toCollection(HashSet::new)
方法 转换成HashSet
集合,该方法参数为Supplier供给型
函数接口,传给一个构造函数
, 用例如下:
//使用`Collectors.toCollection()`方法 转换成`其他没有的现成静态方法`的数据结构集合 比如HashSet
HashSet nameHashSet = stuList.stream().map(Stu::getName)
.collect(Collectors.toCollection(HashSet::new));
nameHashSet.forEach(System.out::println);
/*
result:
hh
aa
bb
cc
dd
*/
Collectors
类中的常用方法counting
-统计数量Long count()
统计元素个数
Long count = stuList.stream()
.collect(Collectors.counting());
System.out.println(count);//8
averagingDouble
-求平均值并转换成Double类型测试用例:求年龄的平均值
Double ageAve = stuList.stream()
.collect(Collectors.averagingDouble(Stu::getAge));
System.out.println(ageAve);//37.0
summingDouble
-求和并转换成Double类型测试用例:求年龄之和
Double ageSum = stuList.stream()
.collect(Collectors.summingDouble(Stu::getAge));
System.out.println(ageSum);//296.0
maxBy
-根据函数条件求最大值测试用例:根据年龄找出最大年龄值的stu对象
//根据年龄找出最大年龄值的stu对象
Optional stuOptional = stuList.stream()
.collect(Collectors.maxBy((a, b) -> Double.compare(a.getAge(), b.getAge())));
System.out.println(stuOptional.get());//Stu{id=5, name='dd', age=52}
groupingBy
-分组单级分组
Collector>> groupingBy(Function super T, ? extends K> classifier)
测试用例:根据年龄分组
//根据年龄分组
Map> ageGroup = stuList.stream()
.collect(Collectors.groupingBy(Stu::getAge));
System.out.println(ageGroup);
结果:
{32=[Stu{id=3, name='bb', age=32}],
52=[Stu{id=5, name='dd', age=52}],
22=[Stu{id=1, name='hh', age=22}, Stu{id=2, name='aa', age=22}],
42=[Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}]}
多级分组
两个参数,第二个参数为Collector
,即实现无限分组 Collector> groupingBy(Function super T, ? extends K> classifier, Collector super T, A, D> downstream)
先根据name分组,再根据年龄分组
//先根据name分组,再根据年龄分组
Map>> groupmap = stuList.stream()
.collect(Collectors.groupingBy(Stu::getName, Collectors.groupingBy((e) -> {
if (e.getAge() <= 20) {
return "年轻人";
} else if (e.getAge() <= 50) {
return "中年人";
} else {
return "老年人";
}
})));
System.out.println(groupmap);
结果:
{dd={老年人=[Stu{id=5, name='dd', age=52}]},
cc={中年人=[Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}, Stu{id=4, name='cc', age=42}]},
bb={中年人=[Stu{id=3, name='bb', age=32}]},
aa={中年人=[Stu{id=2, name='aa', age=22}]},
hh={中年人=[Stu{id=1, name='hh', age=22}]}}
partitioningBy
-分区满足条件
的分到一个区,不满足条件
分到另一个区
true , false
Map
测试用例:是否年龄大于40,分两个区
Map> booleamGroup = stuList.stream()
.collect(Collectors.partitioningBy((e) -> e.getAge() > 40));
System.out.println(booleamGroup);
结果:
{
false=[
Stu{id=1, name='hh', age=22},
Stu{id=2, name='aa', age=22},
Stu{id=3, name='bb', age=32}
],
true=[
Stu{id=4, name='cc', age=42},
Stu{id=4, name='cc', age=42},
Stu{id=4, name='cc', age=42},
Stu{id=4, name='cc', age=42},
Stu{id=5, name='dd', age=52}
]
}
summarizingDouble
-计算方法总括函数summarizingDouble
返回 DoubleSummaryStatistics
类型 可以直接调用各种计算方法
summarizingInt
summarizingLong
实例:
DoubleSummaryStatistics ageSummaryStatis = stuList.stream()
.collect(Collectors.summarizingDouble(Stu::getAge));
ageSummaryStatis.getAverage();
ageSummaryStatis.getCount();
ageSummaryStatis.getMax();
ageSummaryStatis.getMin();
ageSummaryStatis.getSum();
joining
-连接字符串Collector
测试用例:将stuList集合中所有的名字连接在一起
//将集合中所有的名字连接在一起
String allNameStr = stuList.stream().map(Stu::getName)
.collect(Collectors.joining());
System.out.println(allNameStr);//hhaabbccccccccdd
测试用例:将stuList集合中所有的名字连接在一起,并使用逗号分割
//将集合中所有的名字连接在一起,并逗号分割
String allNameStr1 = stuList.stream().map(Stu::getName)
.collect(Collectors.joining(","));
System.out.println(allNameStr1);
//hh,aa,bb,cc,cc,cc,cc,dd