likes
comments
collection
share

深入了解Java 8 新特性:Stream流的实践应用(一)

作者站长头像
站长
· 阅读数 4

深入了解Java 8 新特性:Stream流的实践应用(一)

前言

早就打算写一篇文章来梳理一下Java8的新特性Stream流的相关应用,终于得空来完成这件事了。原先以为这块内容应该不会太多,当我梳理完成以后,发现居然有两万多字。当然,在这两万字中,有一部分是示例代码。考虑到一口读完这两字,对于我的读者小伙伴来说,太不友好了,于是乎我打算,把这些内容拆成两部分:

第一部分,主要是梳理Stream的核心方法;

第二部分,主要是梳理Collectors类的核心方法;Collectors类是Stream实践应用中非常重要的一个工具类,读完这两篇文章相信肯定能意识到这一点。

Stream是什么

Java的Stream是Java 8 引入的一个新特性,它提供了一种简洁、优雅的方式来处理集合数据。Stream允许你将集合中的元素进行过滤、映射、排序等操作,并将结果转换为一个新的集合。并且使用Stream,你可以将一个集合转换为一个流,然后对这个流进行各种操作,最后将结果收集到一个新的集合中。这种处理方式非常适合处理大量数据,因为它允许你在内存中一次只处理一个或一小批数据,而不是一次性加载整个数据集到内存中。因此Stream是java8新增特性中一个非常有用且强大的。它的核心特性:

  1. 声明性:Stream的操作以声明的方式进行,这使得代码更易读、易懂。声明性编程强调的是“做什么”,而不是“如何做”,这使得代码更具可维护性和可扩展性。
  2. 可复合:Stream的操作可以链式地进行,即可以将多个操作链接起来运行。这种可复合的特性使得对数据的处理更简洁、更易于理解。
  3. 可并行:Stream可以并行处理数据,这是为了适应多核机器的时代,提高系统CPU、内存的利用率。并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

这些特性的能够给你带来的好处是:

  1. 提高代码的可读性和可维护性:通过使用声明性和链式操作,可以让代码更加简洁、易于理解和维护。
  2. 提高系统的性能:通过并行流的处理方式,可以充分利用多核CPU的性能,提高系统的处理速度和性能。
  3. 适应大数据时代的需求:随着大数据时代的到来,对大量数据的处理成为一项重要的任务。Stream的并行流处理方式可以满足这种需求,提高数据处理的速度和效率。

Stream的核心方法

Stream#filter

Stream#filter 方法用于根据指定的条件筛选出 Stream 中的元素。filter 方法接收一个 Predicate 参数,Predicate接口在Java中是一个函数式接口,它只有一个抽象方法,即test(T t),用于接受一个参数并返回一个布尔值。这个接口通常用于定义一个断言(即条件),在编程中可以用于对集合进行过滤或者在函数式编程中作为参数使用

示例:

使用Stream#filter()过滤出年龄等于18的学生信息

@Test
public void test() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    List<Student> targetList = list.stream().filter(item -> item.getAge() == 18).collect(Collectors.toList());
    for (Student student : targetList) {
        Assert.isTrue(student.getAge() == 18, "单元测试结果与预期不匹配");
    }
}

Stream#map

Stream#map方法,它用于将 Stream 中的每个元素映射到另一个元素,Stream#map()方法接受一个Function类型的参数,而Function接口是Java 8引入的函数式接口,主要方法是apply(T t),它接受一个参数并返回一个结果。这个方法可以被看作是一个操作或者函数,它对输入进行某种处理并产生输出。

示例:

先使用Stream#filter()过滤出年龄大于18的学生信息,再使用Stream#map()方法把过滤到的结果信息映射成为另外一个类型的

@Test
public void test2() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    List<Person> targetList = list.stream().filter(item -> item.getAge() > 18).map(item -> new Person(item.getName(), item.getAge())).collect(Collectors.toList());
    Assert.notNull(targetList, "单元测试结果与预期不匹配");
}

Stream#mapToInt

Stream.mapToInt()是Java8中Stream API 的一部分,mapToInt() 方法接受一个函数接口作为参数,可以用于将 Stream 中的元素映射为 int 类型,当调用 mapToInt() 方法时,Java 会遍历原始 Stream 中的每个元素,并将每个元素传递给指定的函数。然后,Java 会收集这些结果,最后返回返回一个 IntStream,在IntStream 上可以执行一些特殊的操作,如求和、统计、排序、最大值、最小值等

示例:

  • 把学生信息中的年龄数据过滤到一个数组中
  • 统计所有学生信息的总年龄
  • 统计所有学生信息中的最大年龄
@Test
public void test3() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    //把学生信息中的年龄数据过滤到一个数组中
    int[] ageArr = list.stream().mapToInt(item -> item.getAge()).toArray();
    for (int age : ageArr) {
        System.out.println(age);
    }
    //统计所有学生信息的总年龄
    int sum = list.stream().mapToInt(item -> item.getAge()).sum();
    System.out.println(sum);
    //统计所有学生信息中的最大年龄
    Integer maxAge = list.stream().map(item -> item.getAge()).max((v1, v2) -> {
        if (v1 > v2) {
            return 1;
        } else if (v1 < v2) {
            return -1;
        }
        return 0;
    }).get();
    System.out.println(maxAge);
}

Stream#distinct

Stream#distinct 方法用于去除 Stream 中的重复元素,它会返回由不同元素组成的新 Stream。这个方法实际上调用了 Object.equals(Object o) 方法,默认的行为是比较两个对象的引用是否相等。如果两个对象的引用相等,则认为它们相等;反之则认为它们不相等。因此,在使用 distinct 方法时,如果需要比较的元素是自定义对象,需要在自定义类中重写 equals() 和 hashCode() 方法。

示例:

给存在重复学生信息的集合进行去重操作并回收结果

@Test
public void test4() {
    //wangwu的信息存在重复
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17), new Student("wangwu", 17));
    List<Student> targetList = list.stream().distinct().collect(Collectors.toList());
    Assert.isTrue(list.size() > targetList.size(), "单元测试结果与预期不匹配");
}

Stream#sorted

Stream#sorted 方法用于对 Stream 中的元素进行排序。它返回一个包含按指定排序规则排序后元素的新的 Stream,默认情况下,使用自然顺序排序(对于实现了 Comparable 的元素类型)。

示例

  • 自然排序,默认是升序,这里是首字母升序
  • 自定义排序,lambda表达式定义排序逻辑,这里是降序
public void test5() {
    //自然排序,默认是升序,这里是首字母升序
    List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    List<String> sortedCopy = names.stream().sorted().collect(Collectors.toList());
    String str = sortedCopy.stream().collect(Collectors.joining(","));
    System.out.println(str);
    //自定义排序,lambda表达式定义排序逻辑,这里是降序
    List<Integer> ages = Arrays.asList(18, 17, 19, 20);
    List<Integer> ages2 = ages.stream().sorted((v1, v2) -> {
        if (v1 > v2) {
            return -1;
        } else if (v1 < v2) {
            return 1;
        } else {
            return 0;
        }
    }).collect(Collectors.toList());
    System.out.println(ages2.toString());
}

Stream#peek

Stream.peek() ,它为每个元素提供了一个消费函数。该方法返回由该流的元素组成的流,并对每个元素执行所提供的 Consumer 操作方法,Consumer接口是java8的一个函数式接口,它定义了一个名为 accept 的抽象方法,该方法接受一个参数并且不返回任何结果。peek() 方法主要用于调试,以便在元素流过管道中的某个点时查看它们。peek() 是一个中间操作方法,将在调用终端方法时执行。

示例

打印输出以z开头的姓名;


@Test
public void test6() {
    List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    Stream<String> stream = names.stream()
            .peek(item -> {
                if (item.startsWith("z")) {
                    System.out.println(item);
                }
            });
    //在执行collect方法前,peek方法内的lambda表达式不会被执行;只有在终端方法collect()被调用时才会执行
    List<String> names2 = stream.collect(Collectors.toList());
    System.out.println(names2);
}

Stream#limit

Stream#limit 方法用于限制 Stream 中元素的数量。它返回一个包含指定数量元素的新的 Stream,而不会改变原始 Stream 中的元素顺序。

示例

获取集合内的前两个元素

@Test
public void test7() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    List<String> result = list.stream().limit(2).collect(Collectors.toList());
    System.out.println(result.toString());
}

Stream#skip

Stream#skip 方法,用于跳过 Stream 中的前 n 个元素。它返回一个新的 Stream,该 Stream 不包含原始 Stream 中的前 n 个元素。

示例:

跳过集合内的前3个元素,而获取剩余其他元素

@Test
public void test8() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    List<String> result = list.stream().skip(3).collect(Collectors.toList());
    System.out.println(result.toString());
}

Stream#forEach

Stream#forEach 方法,用于对 Stream 中的每个元素执行遍历操作。该方法没有返回值,它只会对 Stream 中的每个元素进行遍历操作,而不会改变 Stream 中的元素或产生新的结果。

示例

@Test
public void test9() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    list.stream().forEach(item-> System.out.println(item));
}

Stream#forEachOrdered

Stream#forEachOrdered方法 ,用于对 Stream 中的每个元素按照它们在 Stream 中的顺序执行一个提供的操作。与 Stream#forEach 方法不同的是,forEachOrdered方法 会保证操作的顺序与 Stream 中元素的顺序一致,而Stream#forEach方法则不保证操作的顺序。

示例

@Test
public void test10() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    list.stream().forEachOrdered(item-> System.out.println(item));
}

Stream#reduce

Stream#reduce 方法,用于将 Stream 中的元素进行某种聚合操作,并返回一个单一的结果。它接受一个 BinaryOperator 作为参数,该操作接受两个参数并返回一个结果。在聚合过程中,每个元素都会与前一个元素进行操作,并将结果传递给下一个元素,直到处理完所有元素并返回最终结果。使用 Stream#reduce 方法可以对数据进行求和、求积、求最大值、求最小值等各种聚合操作

示例:

求集合内所有学生信息中的学生年龄之和


@Test
public void test11() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    Integer totalAge = list.stream().reduce((val1, val2) -> new Student(val1.getAge() + val2.getAge())).get().getAge();
    System.out.println(totalAge);
}

Stream#collect

Stream#collect 方法,用于将 Stream 中的元素收集到集合或其他对象中,如 List、Set、Map 等。它接受一个 Collector 作为参数,该 Collector 是一个函数式接口,用于定义将元素收集到目标集合或其他对象中的操作。

关于Collector,可以参考Collectors类,该类内置很多静态方法,用于获取常见的Collector;

Stream#min

Stream#min方法,用于找到 Stream 中元素的最小值。它返回 Stream 中元素的最小值,如果 Stream 为空,则返回 Optional.empty。Stream#min方法接受一个比较器作为参数,可以根据特定的业务逻辑或数据类型自定义自己的比较逻辑。

示例

获取集合内年龄最大的学生信息

    @Test
    public void test12() {
        List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
        Student student = list.stream().min((v1, v2) -> {
            if (v1.getAge() > v2.getAge()) {
                return 1;
            } else if (v1.getAge() < v2.getAge()) {
                return -1;
            } else {
                return 0;
            }
        }).get();
        System.out.println(student.toString());
    }
}

Stream#max

与Stream#min方法类似,Stream#max用于找到 Stream 中元素的最大值,Stream#max方法接受一个比较器作为参数,可以根据特定的业务逻辑或数据类型自定义自己的比较逻辑。

@Test
public void test13() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    Student student = list.stream().max((v1, v2) -> {
        if (v1.getAge() > v2.getAge()) {
            return -1;
        } else if (v1.getAge() < v2.getAge()) {
            return 1;
        } else {
            return 0;
        }
    }).get();
    System.out.println(student.toString());
}

Stream#count

Stream#count方法的功能是计算Stream中元素的数量,并返回一个long类型的数值。这个方法不会将Stream中的所有元素收集到一个集合中,而是直接在流上执行计数操作,因此可以高效地计算出Stream中元素的数量。单纯使用count(),与size()效果是一样的,实际使用过程中,一般会结合其他中间方法使用,如filter();

示例

统计姓名集合中以z开头的元素有几个

@Test
public void test14() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    long num = list.stream().filter(item -> item.startsWith("z")).count();
    System.out.println(num);
}

Stream#anyMatch

Stream#anyMatch 方法,用于判断 Stream 中的元素是否满足指定的匹配条件。如果 Stream 中至少有一个元素满足匹配条件,则返回 true;否则返回 false。

示例

统计学生信息集合中,是否存在有年龄小于18的学生信息

@Test
public void test15() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    boolean flag = list.stream().anyMatch(item -> item.getAge() < 18);
    System.out.println(flag);//结果是true
}

Stream#allMatch

Stream#allMatch 方法用于检查 Stream 中的所有元素是否满足给定的条件函数。如果 Stream 中的所有元素都满足条件函数,则返回 true,否则返回 false,可以用在需要检查 Stream 中所有元素是否满足某个条件的场景中。

示例

检查学生集合集合中的学生年龄是否全部大于18

@Test
public void test16() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    boolean flag = list.stream().allMatch(item -> item.getAge() < 18);
    System.out.println(flag);//结果是false
}

Stream#noneMatch

Stream#noneMatch方法,它用于检查Stream中的元素是否都不满足给定的断言条件。Stream#noneMatch方法接受一个Predicate(断言)作为参数,该断言用于测试每个元素是否满足某个条件。如果Stream中没有任何一个元素满足断言条件,则noneMatch方法返回true;否则返回false。

示例

检查学生信息集合中的学生姓名首字母是否都不是以a开头

@Test
public void test17() {
    List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19), new Student("wangwu", 17));
    boolean flag = list.stream().noneMatch(item -> item.getName().startsWith("a"));
    System.out.println(flag);//结果是
}

Stream#findFirst

Stream#findFirst 方法,用于查找并返回 Stream 中的第一个元素。使用 findFirst 方法可以方便地查找符合特定条件的第一个元素,而无需遍历整个 Stream。findFirst 方法不会在 Stream 为空时抛出异常,而是返回一个空的 Optional 对象。可以结合着filter方法一起使用。

示例

检索出学生姓名集合中学生姓名以z开头的第一个姓名

@Test
public void test18() {
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    String name = list.stream().filter(item->item.startsWith("w")).findFirst().get();
    System.out.println(name);//结果是wangwu
}

Stream#findAny

在 Java 11 中,Stream.findAny() 方法用于在流中查找满足特定条件的第一个元素。这个方法返回一个 Optional 对象,表示可能的元素。如果流为空,则返回一个空的 Optional。

示例

@Test
public void test19(){
    List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
    String name = list.stream().findAny().get();
    System.out.println(name); //结果为zhangsan
    List<String> list2=new ArrayList<>();
    boolean flag = list2.stream().findAny().isEmpty();
    System.out.println(flag);//结果为true
}

Stream#of

Stream#of() 方法,用于创建一个包含指定元素的 Stream。这个方法接收一个可迭代的数据源,如数组或集合,并返回一个包含该数据源中所有元素的 Stream。

示例

@Test
public void test20(){
    long count = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu").count();
    System.out.println(count);
}

Stream#iterator

Stream#iterator 方法,用于获取一个迭代器,用于遍历 Stream 中的元素。这个方法返回一个 Iterator 对象,可以使用 next() 方法依次获取 Stream 中的每个元素。

@Test
public void test21(){
    Iterator<String> iterator = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu").iterator();
    while (iterator.hasNext()){
        String next = iterator.next();
        System.out.println(next);
    }
}