Stream
1.介绍
Stream流是一种高效操作集合的新方式,主要是为了解决以前对集合数据操作的问题
2.为什么需要Stream流
传统对集合数据操作的方式,代码可读性不好比较繁琐
3.体验Stream流
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。我们真正要做的事情内容被更好地体现在代码中
/**
* @author sgy
* @date 2022/5/30 15:23
* @description 使用Stream流来初步优化
*/
public class Demo01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
/*
* 需求1:只要字符串开头为张的,不符合的舍弃
* 需求2:主要集合中元素长度为3的,不符合的舍弃
* 需求3:遍历所有满足条件的数据并输出
*/
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
}
}
4.Stream流式思想概述
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品
简述:Stream流可以对一组数据进行加工处理最后输出你想要的结果
5.获取Stream流的两种方式
方式总览
- 所有的Collection集合都可以通过stream默认方法获取流
- Stream接口的静态方法of可以获取数组对应的流
集合获取
Stream stream = new ArrayList().stream();
of方法
/**
* @author sgy
* @date 2022/5/30 15:23
* @description 使用of方法获取Stream流
*/
public class Demo03 {
public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
}
}
6.终结方法与非终结方法
- 终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用。本小节中,终结方法包括count和forEach方法
- 非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用(除了终结方法外,其余方法均为非终结方法)
7.Stream常识
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
8.Stream常用方法
总览
forEach方法
介绍
用来遍历流中的数据
代码
void forEach(Consumer<? super T> action);
案例
介绍
遍历集合
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one,"a","b","c");
one.stream().forEach(t->{
System.out.println(t);
});
}
count方法
介绍
返回流中元素的个数
代码
long count();
案例
目标
获取集合元素数量
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one,"a","b","c");
System.out.println(one.stream().count());
}
filter方法
介绍
filter用于过滤数据,返回符合过滤条件的数据
代码
Stream<T> filter(Predicate<? supmer T> predicate)
案例
目标
过滤宋远桥,留下其他的元素然后输出
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one,"宋远桥","b","c");
one.stream().filter((t)->{
if(t.equals("宋远桥")){
return false;
}
return true;
}).forEach(System.out::println);
}
limit方法
介绍
limit方法可以对流进行截取,只取用前n个
参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作
代码
Stream<T> limit(long maxSize);
案例
目标
截取流的前三个并输出
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one,"宋远桥","b","c","d");
one.stream().limit(3).forEach(System.out::println);
}
skip方法
介绍
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
代码
Stream<T> skip(long n);
案例
目标
跳过第一个元素
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one,"宋远桥","b","c","d");
one.stream().skip(1).forEach(System.out::println);
}
map方法
介绍
如果需要将流中的元素映射到另一个流中,可以使用map方法
代码
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
案例
目标
将字符串转换成Integer
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "宋远桥", "b", "c", "d");
one.stream()
.map(t -> {
if (StrUtil.equals(t, "b")) {
return 1;
}
if (StrUtil.equals(t, "c")) {
return 2;
}
if (StrUtil.equals(t, "d")) {
return 3;
}
return 0;
})
.forEach(System.out::println);
}
sorted方法
介绍
如果需要将数据排序,可以使用sorted节法
代码
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
案例
目标
对集合升序排序
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "c", "d", "a", "b");
one.stream().sorted().forEach(System.out::println);
}
distinct方法
介绍
如果需要去除重复数据,可以使用distinct方法
代码
Stream<T> distinct();
案例
目标
对集合去重
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "c", "c", "c", "b");
one.stream().distinct().forEach(System.out::println);
}
注意
如果是对对象去重,需要重写euals和hashcode方法,否则不会生效
macth方法
介绍
如果需要判断数据是否匹配指定的条件,可以使用Match相关方法
代码
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
案例
目标
集合中元素全都是c返回true
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "c", "c", "c", "b");
boolean b = one.stream()
.allMatch(t -> {
if (StrUtil.equals(t, "c")) {
return true;
}
return false;
});
System.out.println(b);
}
find方法
介绍
如果需要找到某些数据,可以使用find相关方法
代码
Optional<T> findAny();
Optional<T> findFirst();;
案例
目标
查找集合中元素的第一个
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "c", "c", "c", "b");
System.out.println(one.stream().findFirst().get());
}
max和min方法
介绍
如果需要获取最大和最小值,可以使用max和min方法
代码
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
案例
目标
查找集合中元素中最小的
代码
@Test
public void test() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "c", "c", "c", "b");
String s = one.stream().min((t1, t2) -> t1.compareTo(t2)).get();
System.out.println(s);
}
reduce方法
介绍
如果需要将所有数据归纳得到一个数据,可以使用reduce方法
代码
Optional<T> reduce(BinaryOperator<T> accumulator);
案例
目标
对集合的求和
代码
@Test
public void test() {
List<Integer> one = new ArrayList<>();
Collections.addAll(one, 1, 2, 3);
// 第一个参数是默认值
// 第二个参数是如何对所有值进行归纳
Integer sum = one.stream().reduce(0, (x, y) -> {
return x + y;
});
System.out.println(sum);
}
以求和的案例看执行原理
mapToInt方法
介绍
如果需要将Stream<Integer>中的Integer类型数据转成int类型,可以使用mapToInt方法
当然也有ToLong和ToDouble啥的
为什么使用
有的时候可能有性能问题,比如如下场景
代码
IntStream mapToInt(ToIntFunction<? super T> mapper);
Stream类体系
案例
目标
转换成基本数据类型的int的Stream
代码
@Test
public void test() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
IntStream intStream = integerStream.mapToInt(Integer::intValue);
// 然后照常操作intStream即可,里面是基本数据类型
}
concat方法
介绍
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
代码
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
案例
目标
合并两个流
代码
@Test
public void test() {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4);
Stream<Integer> stream2 = Stream.of(5, 6, 7, 8);
Stream<Integer> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out::println);
}
收集Stream流中的结果
介绍
对流操作完成之后,如果需要将流的结果保存到数组或集合中,可以收集流中的数据
操作
- 收集Stream流中的元素到集合中
- 收集Stream流中的元素到数组中
案例1
目标
收集Stream流中的元素到集合中
代码
@Test
public void test() {
List<String> collect = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.filter(t -> {
if (StrUtil.equalsAny(t, "1", "2", "3", "4")) {
return true;
}
return false;
})
// 收集流中的数据到List中
.collect(Collectors.toList());
System.out.println(collect);
}
@Test
public void test() {
Set<String> collect = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.filter(t -> {
if (StrUtil.equalsAny(t, "1", "2", "3", "4")) {
return true;
}
return false;
})
// 收集流中的数据到集合中
.collect(Collectors.toSet());
System.out.println(collect);
}
@Test
public void test() {
ArrayList<String> collect = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.filter(t -> {
if (StrUtil.equalsAny(t, "1", "2", "3", "4")) {
return true;
}
return false;
})
// 收集流中的数据指定到集合中
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(collect);
}
案例2
目标
收集Stream流中的元素到数组中
代码
@Test
public void test() {
Object[] array = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.filter(t -> {
if (StrUtil.equalsAny(t, "1", "2", "3", "4")) {
return true;
}
return false;
})
.toArray();
}
@Test
public void test() {
String[] array = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.filter(t -> {
if (StrUtil.equalsAny(t, "1", "2", "3", "4")) {
return true;
}
return false;
})
.toArray(String[]::new);
}
聚合计算
介绍
当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值,获取最小值,求总和,平均值,统计数量
案例
@Test
public void test() {
Stream<Sutdent> studentStream = Stream.of(
new Student("赵丽颖",58,95),
new Student("杨颖",56,88),
new Student("迪丽热巴",56,99),
new Student("柳岩",52,77)
);
// 获取最大值
Optional<Student> max = studentStream.collect(Collectors.maxBy((s1,s2)->{
s1.getSocre()-s2.getSocre();
}));
// 获取最小值
Optional<Student> min = studentStream.collect(Collectors.minBy((s1,s2)->{
s1.getSocre()-s2.getSocre();
}));
// 求和
Integer sum = studentStream.collect(Collectors.summingInt(i->{
return i.getAge();
}));
// 平均值
Integer avg = studentStream.collect(Collectors.averagingInt(i->{
return i.getAge();
}));
// 数量
Integer count = studentStream.collect(Collectors.counting());
}
分组
介绍
当我们使用Stream流处理数据后,可以根据某个属性将数据分组
案例1
目标
学生对象按照年龄分组
代码
@Test
public void test() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖",52,95),
new Student("杨颖",56,88),
new Student("迪丽热巴",56,55),
new Student("柳岩",52,33)
);
Map<Integer,List<Student>> map = studentStream.collect(Collectors.groupingBy(t->t.getAge()));
}
案例2
目标
学生对象分数大于60的为一组,小于60的另一组
代码
@Test
public void test() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖",52,95),
new Student("杨颖",56,88),
new Student("迪丽热巴",56,55),
new Student("柳岩",52,33)
);
Map<String,List<Student>> map = studentStream.collect(Collectors.groupingBy(t->{
int socre = t.getSocre();
if(socre>60){
return "及格";
}else{
return "不及格";
}
}));
}
案例2
目标
先根据年龄分组,每一组中再根据成绩分组
代码
@Test
public void test() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖",52,95),
new Student("杨颖",56,88),
new Student("迪丽热巴",56,55),
new Student("柳岩",52,33)
);
Map<Integer,Map<String,List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge,Collectors.groupingBy((s)->{
int socre = s.getSocre();
if(socre>60){
return "及格";
}else{
return "不及格";
}
}));
}
分区
介绍
会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表
案例
目的
学生分数大于60进行分区
代码
@Test
public void test() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖",52,95),
new Student("杨颖",56,88),
new Student("迪丽热巴",56,55),
new Student("柳岩",52,33)
);
Map<Boolean,List<Student>> map = studentStream.collect(Collectors.partitioningBy(s->{
return s.getSocre>60;
}));
}
连接
介绍
根据指定的连接符,将所有元素连接成一个字符串
案例
目的
使用逗号连接
代码
@Test
public void test() {
Stream<String> stream = Stream.of("a","b","c");
String str = stream.collect(Collectors.joining(","));
}
9.并行Stream
介绍
默认的流是串行的,其实也有并行的
获取方式
线程安全问题
流里面操作的逻辑是多线程执行的,要注意线程安全问题,具体怎么解决参考多线程里面如何解决线程安全问题
9.Stream使用案例
对象的某个属性求和
/**
* @author sgy
* @description
* @date 2024/5/26
*/
public class TestA {
public static void main(String[] args) {
// 求出所有年龄的和
int totalAge = Stream.of(
new Person("a", 1),
new Person("b", 2),
new Person("c", 3),
new Person("d", 4)
)
.map(Person::getAge)
.reduce(0, Integer::sum);
System.out.println(totalAge);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class Person {
private String name;
private Integer age;
}
查找对象的某个属性的最大值
/**
* @author sgy
* @description
* @date 2024/5/26
*/
public class TestA {
public static void main(String[] args) {
// 求年龄的最大值
int maxAge = Stream.of(
new Person("a", 1),
new Person("b", 2),
new Person("c", 3),
new Person("d", 4)
)
.map(Person::getAge)
.reduce(0, (x, y) -> x > y ? x : y);
System.out.println(maxAge);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class Person {
private String name;
private Integer age;
}
统计集合中某个元素出现的数量
/**
* @author sgy
* @description
* @date 2024/5/26
*/
public class TestA {
public static void main(String[] args) {
// 查找a出现的次数
int count = Stream.of(
new Person("a", 1),
new Person("b", 2),
new Person("c", 3),
new Person("d", 4),
new Person("a", 5)
)
.map(s -> {
String name = s.getName();
if (StrUtil.equals(name, "a")) {
return 1;
} else {
return 0;
}
})
.reduce(0, Integer::sum);
System.out.println(count);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class Person {
private String name;
private Integer age;
}