JDK 8新特性-Stream流

  |   0 评论   |   0 浏览

JDK 8新特性-Stream流

1.Stream流介绍

目标

了解集合的处理数据的弊端

理解Stream流的思想和作用

1.集合处理数据的弊端

当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验 集合操作数据的弊端,需求如下:

一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰

需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据

代码如下:

public static void main(String[] args) {
    // 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
    // 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
    ArrayList<String> list = new ArrayList<>();
    Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
    // 1.拿到所有姓张的
    ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
    for (String name : list) {
        if (name.startsWith("张")) {
            zhangList.add(name);
        }
    }
    // 2.拿到名字长度为3个字的
    ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
    for (String name : zhangList) {
        if (name.length() == 3) {
            threeList.add(name);
        }
    }
    // 3.打印这些数据
    for (String name : threeList) {
        System.out.println(name);
    }
}

2.循环遍历的弊端

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?**不是。**循环 是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使 用另一个循环从头开始。

那Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

下面来看一下借助Java 8的Stream API,修改后的代码:

public class Demo03StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
    }
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。我们真 正要做的事情内容被更好地体现在代码中。

3.Stream流式思想概述

注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!

Stream流式思想类似于工厂车间的"生产流水线",Stream流不是一种数据结构,不保存数据,而是对数据进行加工 处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。

image-20220919224245591

image-20220919224258142

image-20220919224303840

Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。

4.小结

首先我们了解了集合操作数据的弊端,每次都需要循环遍历,还要创建新集合,很麻烦

Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理

2.获取Stream流的两种方式

目标

掌握根据Collection获取流

掌握Stream中的静态方法of获取流java.util.stream.Stream<T> 是JDK 8新加入的流接口。

获取一个流非常简单,有以下几种常用的方式:

1.所有的Collection 集合都可以通过stream 默认方法获取流;

2.Stream 接口的静态方法of 可以获取数组对应的流。

1.方式1 : 根据Collection获取流

首先, java.util.Collection 接口中加入了default方法stream 用来获取流,所以其所有实现类均可获取流。

public interface Collection {
    default Stream<E> stream() 
}
public class Demo04GetStream {
    public static void main(String[] args) {
        // 集合获取流
        // Collection接口中的方法: default Stream<E> stream() 获取流
        List<String> list = new ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
        // ... 
        Stream<String> stream2 = set.stream();
        Vector<String> vector = new Vector<>();
        // ...
        Stream<String> stream3 = vector.stream();
    }
}

java.util.Map 接口不是Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:

public class Demo05GetStream {
    public static void main(String[] args) {
        // Map获取流 
        Map<String, String> map = new HashMap<>();
        // ...
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}

2.方式2 : Stream中的静态方法of获取流

由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of ,使用很简单:

public class Demo06GetStream {
    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);
    }
}

备注: of 方法的参数其实是一个可变参数,所以支持数组。

3.小结

学习了两种获取流的方式:

  1. 通过Collection接口中的默认方法Stream stream()
  2. 通过Stream接口中的静态of方法

3.Stream常用方法和注意事项

1.目标

了解Stream常用方法的分类

掌握Stream注意事项

2.Stream常用方法

Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:


方法名 方法作用 返回值类型 方法种类

count 统计个数 long 终结

forEach 逐一处理 void 终结

filter 过滤 Stream 函数拼接

limit 取用前几个 Stream 函数拼接

skip 跳过前几个 Stream 函数拼接

map 映射 Stream 函数拼接

concat 组合 Stream 函数拼接


终结方法:返回值类型不再是Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括count 和

forEach 方法。

非终结方法:返回值类型仍然是Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

3.Stream注意事项(重要)

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不调用终结方法,中间的操作不会执行

4.小结

我们学习了Stream的常用方法,我们知道Stream这些常用方法可以分成两类,终结方法,函数拼接方法

Stream的3个注意事项:

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不调用终结方法,中间的操作不会执行

4.Stream流的forEach方法

forEach 用来遍历流中的数据

void forEach(Consumer<? super T> action);

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。例如:

@Test
public void testForEach() {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
    one.stream().forEach((String s)->{
        System.out.println(s);
    });

    //简写
    one.stream().forEach(s-> System.out.println(s));
}

5.Stream流的count方法

Stream流提供 count 方法来统计其中的元素个数:

long count();

该方法返回一个long值代表元素个数。基本使用:

@Test
public void testCount() {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
    System.out.println(one.stream().count());
}

6.Stream流的filter方法

filter用于过滤数据,返回符合过滤条件的数据

image-20220920085308002

可以通过filter 方法将一个流转换成另一个子集流。方法声明:

Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

Stream流中的 filter 方法基本使用的代码如:

@Test
public void testFilter() {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
    /**
     * 老子
     * 庄子
     * 孙子
     */
    one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}

在这里通过Lambda表达式来指定了筛选的条件:姓名长度为2个字。

7.Stream流的limit方法

image-20220920085418522

limit 方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxSize);

参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:

@Test
public void testLimit() {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
    one.stream().limit(3).forEach(System.out::println);
}

8.Stream流的skip方法

image-20220920085501694

如果希望跳过前几个元素,可以使用skip 方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

@Test
public void testSkip() {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
    one.stream().skip(2).forEach(System.out::println);
}

9.Stream流的map方法

image-20220920085853136

如果需要将流中的元素映射到另一个流中,可以使用map 方法。方法签名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

Stream流中的 map 方法基本使用的代码如:

@Test
public void testMap() {
    Stream<String> original = Stream.of("11", "22", "33");
    Stream<Integer> result = original.map(Integer::parseInt);
    result.forEach(s -> System.out.println(s + 10));
}

这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer 类对象)。

10.Stream流的sorted方法

如果需要将数据排序,可以使用sorted 方法。方法签名:

Stream<T> sorted(); 
Stream<T> sorted(Comparator<? super T> comparator);

基本使用

Stream流中的 sorted 方法基本使用的代码如:

@Test
public void testSorted() {
    // sorted(): 根据元素的自然顺序排序
    /**
     * 11
     * 22
     * 33
     * 55
     */
    Stream.of(33, 22, 11, 55).sorted().forEach(System.out::println);
    // sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
    /**
     * 55
     * 33
     * 22
     * 11
     */
    Stream.of(33, 22, 11, 55).sorted().sorted((o1, o2) -> o2 - o1).forEach(System.out::println);
}

这段代码中, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。

11.Stream流的distinct方法

image-20220920090150599

如果需要去除重复数据,可以使用distinct 方法。方法签名:

Stream<T> distinct();

基本使用

Stream流中的 distinct 方法基本使用的代码如:

@Test
public void testDistinct() {
    Stream.of(22, 33, 22, 11, 33).distinct().forEach(System.out::println);
}

如果是自定义类型如何是否也能去除重复的数据呢?

public class Person {
    private String name;
    private int age; // 省略其他 
}
@Test
public void testDistinct2() {
    Stream.of(new Person("刘德华", 58),
            new Person("张学友", 56),
            new Person("张学友", 56),
            new Person("黎明", 52)).distinct().forEach(System.out::println);
}

自定义类型是根据对象的hashCode和equals来去除重复元素的。

12.Stream流的match方法

如果需要判断数据是否匹配指定的条件,可以使用Match 相关方法。方法签名:

boolean allMatch(Predicate<? super T> predicate);

boolean anyMatch(Predicate<? super T> predicate);

boolean noneMatch(Predicate<? super T> predicate);

基本使用

Stream流中的 Match 相关方法基本使用的代码如:

@Test
public void testMatch() {
    boolean b = Stream.of(5, 3, 6, 1)
            // allMatch: 元素是否全部满足条件
            // .allMatch(e -> e > 0);
            // anyMatch: 元素是否任意有一个满足条件
            // .anyMatch(e -> e > 5);
            // noneMatch: 元素是否全部不满足条件
            .noneMatch(e -> e < 0);
    System.out.println("b = " + b);
}

13.Stream流的find方法

image-20220920090634450

如果需要找到某些数据,可以使用find 相关方法。方法签名:

1 .findFirst() 方法根据命名可以大致知道是获取Optional流中的第一个元素。
2 .findAny() 方法是获取Optional 流中任意一个,存在随机性,其实里面也是获取元素中的第一个。

3.使用findAny() 方法时候需要注意是否是使用异步流处理方式,如果不是findAny() 等于findFirst()

Optional<T> findFirst(); 
Optional<T> findAny();

基本使用

Stream流中的 find 相关方法基本使用的代码如:

@Test
public void testFind() {
    Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
    System.out.println("first = " + first.get());
    Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
    System.out.println("any = " + any.get());
}

14.Stream流的max和min方法

image-20220920091038922

如果需要获取最大和最小值,可以使用max 和min 方法。方法签名:

Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);

基本使用

Stream流中的 max 和min 相关方法基本使用的代码如:

@Test
public void testMax_Min() {
    Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
    //6
    System.out.println("first = " + max.get());
    Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
    //1
    System.out.println("any = " + min.get());
}

15.Stream流的reduce方法

image-20220920091133131

如果需要将所有数据归纳得到一个数据,可以使用reduce 方法。方法签名:

基本使用

Stream流中的 reduce 相关方法基本使用的代码如:

@Test
public void testReduce() {
    /***
     * a = 0, b = 4
     * a = 4, b = 5
     * a = 9, b = 3
     * a = 12, b = 9
     */
    int reduce = Stream.of(4, 5, 3, 9).reduce(0, (a, b) -> {
        System.out.println("a = " + a + ", b = " + b);
        return a + b;
    });
    // reduce:
    // 第一次将默认做赋值给x, 取出第一个元素赋值给y,进行操作
    // 第二次,将第一次的结果赋值给x, 取出二个元素赋值给y,进行操作
    // 第三次,将第二次的结果赋值给x, 取出三个元素赋值给y,进行操作
    // 第四次,将第三次的结果赋值给x, 取出四个元素赋值给y,进行操作
    //reduce = 21
    System.out.println("reduce = " + reduce);
    int reduce2 = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
        return Integer.sum(x, y);
    });
    int reduce3 = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
    int max = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
        return x > y ? x : y;
    });
    //max = 9
    System.out.println("max = " + max);
}

image-20220920091314326

16.Stream流的map和reduce组合使用

@Test
public void testMapReduce() {
    // 求出所有年龄的总和
    int totalAge = Stream.of(
            new Person("刘德华", 58),
            new Person("张学友", 56),
            new Person("郭富城", 54),
            new Person("黎明", 52)).map((p) -> p.getAge()).reduce(0, (x, y) -> x + y);
    //totalAge = 220
    System.out.println("totalAge = " + totalAge);
    // 找出最大年龄
    int maxAge = Stream.of(
            new Person("刘德华", 58),
            new Person("张学友", 56), new Person("郭富城", 54),
            new Person("黎明", 52)).map((p) -> p.getAge()).reduce(0, (x, y) -> x > y ? x : y);
    //maxAge = 58
    System.out.println("maxAge = " + maxAge);
    // 统计 数字2 出现的次数
    int count = Stream.of(1, 2, 2, 1, 3, 2).map(i -> {
        if (i == 2) {
            return 1;
        } else {
            return 0;
        }
    }).reduce(0, Integer::sum);
    //count = 3
    System.out.println("count = " + count);
}

17.Stream流的mapToInt

如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:

IntStream mapToInt(ToIntFunction<? super T> mapper);

image-20220920091809375

基本使用

Stream流中的 mapToInt 相关方法基本使用的代码如:

@Test
public void test1() {
    // Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
    Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
    // 把大于3的和打印出来
    //Integer result = stream
    // .filter(i -> i.intValue() > 3)
    // .reduce(0, Integer::sum);
    // System.out.println(result);
    // 先将流中的Integer数据转成int,后续都是操作int类型
    IntStream intStream = stream.mapToInt(Integer::intValue);
    //将集合元素大于3的进行求和
    int reduce = intStream.filter(i -> i > 3).reduce(0, Integer::sum);
    //9
    System.out.println(reduce);
    // 将IntStream转化为Stream<Integer>
    IntStream intStream1 = IntStream.rangeClosed(1, 10);
    Stream<Integer> boxed = intStream1.boxed();
    /**
     * class java.lang.Integer, 1
     * class java.lang.Integer, 2
     * class java.lang.Integer, 3
     * class java.lang.Integer, 4
     * class java.lang.Integer, 5
     * class java.lang.Integer, 6
     * class java.lang.Integer, 7
     * class java.lang.Integer, 8
     * class java.lang.Integer, 9
     * class java.lang.Integer, 10
     */
    boxed.forEach(s -> System.out.println(s.getClass() + ", " + s));
}

17.Stream流的concat方法

如果有两个流,希望合并成为一个流,那么可以使用Stream 接口的静态方法concat :

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

备注:这是一个静态方法,与java.lang.String 当中的concat 方法是不同的。该方法的基本使用代码如:

@Test
public void testContact() {
    Stream<String> streamA = Stream.of("张三");
    Stream<String> streamB = Stream.of("李四");
    Stream<String> result = Stream.concat(streamA, streamB);
    result.forEach(System.out::println);
}

18.Stream综合案例

现在有两个ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建Person 对象;
  7. 打印整个队伍的Person对象信息。

两个队伍(集合)的代码如下:

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = List.of("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七 公");
        List<String> two = List.of("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
        //....
    }
}

而Person 类的代码为:

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

传统方式

使用for循环 , 示例代码:

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = Stream.of("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公").collect(Collectors.toList());
        List<String> two = Stream.of("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三").collect(Collectors.toList());
        // 第一个队伍只要名字为3个字的成员姓名;
        List<String> oneA = new ArrayList<>();
        for (String name : one) {
            if (name.length() == 3) {
                oneA.add(name);
            }
        }
        // 第一个队伍筛选之后只要前3个人;
        List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            oneB.add(oneA.get(i));
        }
        // 第二个队伍只要姓张的成员姓名;
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("张")) {
                twoA.add(name);
            }
        }
        // 第二个队伍筛选之后不要前2个人;
        List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }
        // 将两个队伍合并为一个队伍;
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);
        // 根据姓名创建Person对象;
        List<Person> totalPersonList = new ArrayList<>();
        for (String name : totalNames) {
            totalPersonList.add(new Person(name));
        }
        // 打印整个队伍的Person对象信息。
        for (Person person : totalPersonList) {
            System.out.println(person);
        }
    }
}

运行结果为:

Person(name=宋远桥, age=0, height=0)
Person(name=苏星河, age=0, height=0)
Person(name=洪七公, age=0, height=0)
Person(name=张二狗, age=0, height=0)
Person(name=张天爱, age=0, height=0)
Person(name=张三, age=0, height=0)

Stream方式

等效的Stream流式处理代码为:

public class DemoArrayListNames2 {
    public static void main(String[] args) {
        List<String> one = Stream.of("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公").collect(Collectors.toList());
        List<String> two = Stream.of("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三").collect(Collectors.toList());
        // 第一个队伍只要名字为3个字的成员姓名;
        // 第一个队伍筛选之后只要前3个人;
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // 第二个队伍只要姓张的成员姓名;
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("张")) {
                twoA.add(name);
            }
        }
        
        // 第二个队伍筛选之后不要前2个人;
        // 将两个队伍合并为一个队伍;
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);

        // 根据姓名创建Person对象;
        // 打印整个队伍的Person对象信息。
        Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
    }
}

运行效果完全一样:

Person(name=宋远桥, age=0, height=0)
Person(name=苏星河, age=0, height=0)
Person(name=洪七公, age=0, height=0)
Person(name=张二狗, age=0, height=0)
Person(name=张天爱, age=0, height=0)
Person(name=张三, age=0, height=0)

19.收集Stream流中的结果

IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue); 
intStream.filter(n -> n > 3).forEach(System.out::println);
intStream.filter(n -> n > 3).count; 
intStream.filter(n -> n > 3).reduce(0, Integer::sum);

对流操作完成之后,如果需要将流的结果保存到数组或集合中,可以收集流中的数据

目标

掌握Stream流中的结果到集合中

掌握Stream流中的结果到数组中

20.Stream流中的结果到集合中

Stream流提供 collect 方法,其参数需要一个java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:

public static <T> Collector<T, ?, List<T>> toList():转换为 List 集合。

public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。

下面是这两个方法的基本使用代码:

// 将流中数据收集到集合中 
@Test
public void testStreamToCollection() {
    Stream<String> stream = Stream.of("aa", "bb", "cc");
    List<String> list = stream.collect(Collectors.toList());
    Set<String> set = stream.collect(Collectors.toSet());
    ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
    HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
}

21.Stream流中的结果到数组中

Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:

Object[] toArray();

其使用场景如:

@Test
public void testStreamToArray() {
    Stream<String> stream = Stream.of("aa", "bb", "cc");
    Object[] objects = stream.toArray();
    for (Object obj : objects) {
         System.out.println(obj);
    }
    String[] strings = stream.toArray(String[]::new);
    for (String str : strings) {
        System.out.println(str);
    }
}

22.对流中数据进行聚合计算

当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值,获取最小 值,求总和,平均值,统计数量。

@Test
public void testStreamToOther() {
    Stream<Student> studentStream = Stream.of(
            new Student("赵丽颖", 58, 95),
            new Student("杨颖", 56, 88),
            new Student("迪丽热巴", 56, 99), new Student("柳岩", 52, 77));
    // 获取最大值
    Optional<Student> collect = studentStream.collect(Collectors.maxBy((o1, o2) -> o1.getSocre() - o2.getSocre()));
    // 获取最小值
    Optional<Student> collect = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getSocre() - o2.getSocre()));
    System.out.println(collect.get());
    // 求总和
    int sumAge = studentStream.collect(Collectors.summingInt(s -> s.getAge()));
    System.out.println("sumAge = " + sumAge);
    // 平均值
    double avgScore = studentStream.collect(Collectors.averagingInt(s -> s.getSocre()));
    System.out.println("avgScore = " + avgScore);
    // 统计数量
    Long count = studentStream.collect(Collectors.counting());
    System.out.println("count = " + count);
}

23.对流中数据进行分组

当我们使用Stream流处理数据后,可以根据某个属性将数据分组:

// 分组
@Test
public void testGroup() {
    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(Student::getAge));
    // 将分数大于60的分为一组,小于60分成另一组
    Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy((s) -> {
        if (s.getSocre() > 60) {
            return "及格";
        } else {
            return "不及格";
        }
    }));
  
    map.forEach((k, v) -> {
        System.out.println(k + "::" + v);
    });
}

效果:

不及格::[Student(name=迪丽热巴, age=56, socre=55), Student(name=柳岩, age=52, socre=33)]
及格::[Student(name=赵丽颖, age=52, socre=95), Student(name=杨颖, age=56, socre=88)]

24.对流中数据进行多级分组

还可以对数据进行多级分组:

// 多级分组
@Test
public void testCustomGroup() {
    Stream<Student> studentStream = Stream.of(
            new Student("赵丽颖", 52, 95),
            new Student("杨颖", 56, 88),
            new Student("迪丽热巴", 56, 99),
            new Student("柳岩", 52, 77));
    Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(s -> s.getAge(), Collectors.groupingBy(s -> {
        if (s.getSocre() >= 90) {
            return "优秀";
        } else if (s.getSocre() >= 80 && s.getSocre() < 90) {
            return "良好";
        } else if (s.getSocre() >= 80 && s.getSocre() < 80) {
            return "及格";
        } else {
            return "不及格";
        }
    })));
    map.forEach((k, v) -> {
        System.out.println(k + " == " + v);
    });
}

效果:

52 == {不及格=[Student(name=柳岩, age=52, socre=77)], 优秀=[Student(name=赵丽颖, age=52, socre=95)]}
56 == {优秀=[Student(name=迪丽热巴, age=56, socre=99)], 良好=[Student(name=杨颖, age=56, socre=88)]}

25.对流中数据进行分区

image-20220920095223112

Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。

// 分区
@Test
public void testPartition() {
    Stream<Student> studentStream = Stream.of(
            new Student("赵丽颖", 52, 95),
            new Student("杨颖", 56, 88),
            new Student("迪丽热巴", 56, 99),
            new Student("柳岩", 52, 77));
    // partitioningBy会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。
    Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s -> s.getSocre() > 90));
    map.forEach((k, v) -> {
        System.out.println(k + " == " + v);
    });
}

效果:

false == [Student(name=杨颖, age=56, socre=88), Student(name=柳岩, age=52, socre=77)]
true == [Student(name=赵丽颖, age=52, socre=95), Student(name=迪丽热巴, age=56, socre=99)]

26.对流中数据进行拼接

Collectors.joining 会根据指定的连接符,将所有元素连接成一个字符串。

// 拼接
@Test
public void testJoining() {
    Stream<Student> studentStream = Stream.of(
            new Student("赵丽颖", 52, 95),
            new Student("杨颖", 56, 88),
            new Student("迪丽热巴", 56, 99),
            new Student("柳岩", 52, 77));
    String collect = studentStream.map(Student::getName).collect(Collectors.joining(">_<", "^_^", "^v^"));
    System.out.println(collect);
}

效果:

^_^赵丽颖>_<杨颖>_<迪丽热巴>_<柳岩^v^

27.小结

收集Stream流中的结果

到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()

到数组中: toArray()/toArray(int[]::new)

聚合计算 : Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt

分组: Collectors.groupingBy

分区: Collectors.partitionBy

拼接: Collectors.joinging


标题:JDK 8新特性-Stream流
作者:llp
地址:https://llinp.cn/articles/2022/09/20/1663639295819.html