04.JDK8-Stream流

23 阅读10分钟

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流的两种方式

方式总览

  1. 所有的Collection集合都可以通过stream默认方法获取流
  2. 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常识

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. 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;
}