Java 8 流 – Java 流

欢迎来到Java 8 Stream API教程。在过去的几篇Java 8文章中,我们学习了Java 8接口的变化、函数式接口和Lambda表达式。今天我们将介绍Java 8中引入的一个重要API – Java Stream。

Java 8 流

    Java 8 流
    集合和 Java 流
    Java 8 流中的函数式接口

    函数和双函数
    断言和双断言
    消费者和双消费者
    供应商

    java.util.Optional
    java.util.Spliterator
    Java 流的中间和终端操作
    Java 流的短路操作
    Java 流示例

    创建 Java 流
    将 Java 流转换为集合或数组
    Java 流的中间操作
    Java 流的终端操作

    Java 8 流 API 的限制

Java Stream (Java流)

在我们进一步研究Java Stream API示例之前,让我们看看为什么需要它。假设我们想要迭代整数列表,并找出大于10的所有整数的总和。在Java 8之前,完成这个任务的方法是:

private static int sumIterator(List<Integer> list) {
	Iterator<Integer> it = list.iterator();
	int sum = 0;
	while (it.hasNext()) {
		int num = it.next();
		if (num > 10) {
			sum += num;
		}
	}
	return sum;
}

以上方法存在三个主要问题:

    我们只想知道整数的总和,但我们还必须提供迭代的方式,这也被称为外部迭代,因为客户程序负责迭代列表的算法。
    这个程序的性质是顺序的,我们很难并行地完成它。
    即使是简单的任务,也需要很多代码。

为了克服以上所有缺点,引入了Java 8 Stream API。我们可以使用Java Stream API来实现内部迭代,这更好,因为Java框架对迭代有控制权。内部迭代提供了几个功能,例如按照给定的条件进行顺序和并行执行、基于给定条件进行过滤、映射等。大多数Java 8 Stream API的方法参数都是函数式接口,因此lambda表达式与它们非常兼容。让我们看看如何使用Java Streams在一条单行语句中编写上述逻辑。

private static int sumStream(List<Integer> list) {
	return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}

请注意,上述程序利用了Java框架的迭代策略、过滤和映射方法,从而提高了效率。首先,我们将了解Java 8 Stream API的核心概念,然后通过一些例子来理解最常用的方法。

集合和Java流

集合是一种内存中的数据结构,用来存储值,在我们开始使用集合之前,所有的值都应该已经被填充。而Java Stream是一种按需计算的数据结构。Java Stream不存储数据,它作用于源数据结构(集合和数组),并产生我们可以使用和执行特定操作的流式数据。例如,我们可以从列表中创建一个流,并根据条件对其进行过滤。Java Stream操作使用函数接口,这使得它非常适合使用lambda表达式进行函数式编程。正如您在上面的示例中所看到的,使用lambda表达式可以使我们的代码更易读、更简洁。Java 8 Stream的内部迭代原理有助于实现某些流操作的延迟求值。例如,过滤、映射或去重可以进行惰性实现,从而实现更高的性能和优化空间。Java Streams是可消耗的,因此无法为将来的使用创建流的引用。由于数据是按需获取的,因此无法多次重用同一流。Java 8 Stream支持顺序处理和并行处理,对于大型集合,使用并行处理可以帮助实现高性能。所有Java Stream API的接口和类都在java.util.stream包中。由于我们可以使用原始数据类型(例如int、long)在集合中使用自动装箱,而这些操作可能需要很长时间,因此针对原始类型有特定的类-IntStream、LongStream和DoubleStream。

Java 8流中的函数式接口

Java 8 Stream API方法中常用的函数式接口包括:

    功能和BiFunction:Function表示接受一种类型的参数并返回另一种类型的参数的函数。Function是通用形式,其中T是函数的输入类型,R是函数结果的类型。对于处理基本类型,有特定的Function接口-ToIntFunction、ToLongFunction、ToDoubleFunction、ToIntBiFunction、ToLongBiFunction、ToDoubleBiFunction、LongToIntFunction、LongToDoubleFunction、IntToLongFunction、IntToDoubleFunction等。一些使用Function或其基本类型特化的Stream方法有:

    Stream map(Function mapper)
    IntStream mapToInt(ToIntFunction mapper)-类似的对于long和double返回基本类型的流。
    IntStream flatMapToInt(Function mapper)-类似的对于long和double
    A[] toArray(IntFunction generator)
    U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)

    Predicate和BiPredicate:它表示针对流元素进行测试的谓词。这用于从java流中筛选元素。就像Function一样,对于int、long和double也有特定的原始接口。一些使用Predicate或BiPredicate特化的Stream方法有:

    Stream filter(Predicate predicate)
    boolean anyMatch(Predicate predicate)
    boolean allMatch(Predicate predicate)
    boolean noneMatch(Predicate predicate)

    Consumer和BiConsumer:它表示接受一个输入参数并返回没有结果的操作。它可以用于对java流的所有元素执行某些操作。一些Java 8 Stream方法使用Consumer、BiConsumer或其原始特化接口的情况有:

    Stream peek(Consumer action)
    void forEach(Consumer action)
    void forEachOrdered(Consumer action)

    Supplier:Supplier表示通过它可以生成流中的新值的操作。一些Stream中采用Supplier参数的方法有:

    public static Stream generate(Supplier s)
    R collect(Supplier supplier,BiConsumer accumulator,BiConsumer combiner)

Java中的java.util.Optional。

Java Optional是一个容器对象,它可以包含或不包含非空值。如果存在一个值,isPresent()方法将返回true,而get()方法将返回该值。流的终端操作返回Optional对象。其中一些方法包括:

  • Optional reduce(BinaryOperator accumulator)
  • Optional min(Comparator comparator)
  • Optional max(Comparator comparator)
  • Optional findFirst()
  • Optional findAny()

java.util.Spliterator 可以被翻译为 “Java中的Spliterator工具类”。

为了支持Java 8 Stream API中的并行执行,使用了Spliterator接口。Spliterator的trySplit方法返回一个管理原始Spliterator元素子集的新Spliterator。

Java流的中间和终端操作

Java Stream API操作中返回新Stream的操作被称为中间操作。大多数情况下,这些操作是惰性的,所以它们开始生成新的流元素并将其发送到下一个操作。中间操作永远不是最终的结果生成操作。常用的中间操作包括filter和map。Java 8 Stream API操作返回一个结果或产生副作用。一旦在流上调用了终端方法,它就会消耗该流,之后我们无法再使用该流。终端操作是急性的,即在返回结果之前处理流中的所有元素。常用的终端方法包括forEach、toArray、min、max、findFirst、anyMatch、allMatch等。您可以根据返回类型识别终端方法,它们永远不会返回一个Stream。

Java Stream 短路操作

如果一个中间操作可能为无限流生成有限流,则称其为“短路”。例如,limit()和skip()是两个短路的中间操作。如果一个终端操作可能在有限时间内终止无限流,则称其为“短路”。例如,anyMatch、allMatch、noneMatch、findFirst和findAny都是短路的终端操作。

Java 流示例

我已经涵盖了Java 8 Stream API的几乎所有重要部分。使用这个新API的特性真是令人兴奋,让我们通过一些Java流示例来看它的实际效果。

创建Java流。

我们可以通过几种方式从数组和集合中创建Java流。让我们通过简单的例子来了解这些方式。

    我们可以使用Stream.of()从相似类型的数据中创建流。例如,我们可以从一组int或Integer对象中创建Java Stream的整数流。
    Stream stream = Stream.of(1,2,3,4);

    我们可以使用Stream.of()和一个对象数组来返回流。请注意,它不支持自动装箱,所以我们不能传递基本类型的数组。
    Stream stream = Stream.of(new Integer[]{1,2,3,4}); //运行正常

    Stream stream1 = Stream.of(new int[]{1,2,3,4}); //编译时错误,类型不匹配:无法将Stream转换为Stream

    我们可以使用Collection的stream()方法创建顺序流,用parallelStream()方法创建并行流。
    List myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i); //顺序流 Stream sequentialStream = myList.stream();

    //并行流
    Stream parallelStream = myList.parallelStream();

    我们可以使用Stream.generate()和Stream.iterate()方法创建流。
    Stream stream1 = Stream.generate(() -> {return “abc”;});
    Stream stream2 = Stream.iterate(“abc”, (i) -> i);

    使用Arrays.stream()和String.chars()方法。
    LongStream is = Arrays.stream(new long[]{1,2,3,4});
    IntStream is2 = “abc”.chars();

将Java流转换为集合或数组

我们可以通过几种方式从Java Stream中获取Collection或Array。

    我们可以使用Java Stream的collect()方法从流中获取List、Map或Set。
    Stream intStream = Stream.of(1,2,3,4);
    List intList = intStream.collect(Collectors.toList());
    System.out.println(intList); //打印结果为 [1, 2, 3, 4]

    intStream = Stream.of(1,2,3,4); //流已关闭,所以需要重新创建
    Map intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
    System.out.println(intMap); //打印结果为 {1=11, 2=12, 3=13, 4=14}

    我们可以使用stream的toArray()方法从流中创建一个数组。
    Stream intStream = Stream.of(1,2,3,4);
    Integer[] intArray = intStream.toArray(Integer[]::new);
    System.out.println(Arrays.toString(intArray)); //打印结果为 [1, 2, 3, 4]

Java 流的中间操作

让我们来看一下常用的Java流中间操作示例。

    stream filter()示例:我们可以使用filter()方法来测试流元素是否满足条件,并生成一个过滤后的列表。
    List myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i); Stream sequentialStream = myList.stream();

    Stream highNums = sequentialStream.filter(p -> p > 90); //过滤大于90的数字
    System.out.print(“高于90的数字=”);
    highNums.forEach(p -> System.out.print(p+” “));
    //输出 “高于90的数字=91 92 93 94 95 96 97 98 99 ”

    Stream map()示例:我们可以使用map()方法将函数应用于流。让我们看看如何将其应用于一个字符串列表来进行大写转换。
    Stream names = Stream.of(“aBc”, “d”, “ef”);
    System.out.println(names.map(s -> {
    return s.toUpperCase();
    }).collect(Collectors.toList()));
    //输出 [ABC, D, EF]

    Stream sorted()示例:我们可以使用sorted()方法通过传递Comparator参数来对流元素进行排序。
    Stream names2 = Stream.of(“aBc”, “d”, “ef”, “123456”);
    List reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    System.out.println(reverseSorted); // 输出 [ef, d, aBc, 123456]

    Stream names3 = Stream.of(“aBc”, “d”, “ef”, “123456”);
    List naturalSorted = names3.sorted().collect(Collectors.toList());
    System.out.println(naturalSorted); //输出 [123456, aBc, d, ef]

    Stream flatMap()示例:我们可以使用flatMap()方法从列表流中创建一个流。让我们看一个简单的例子来消除这种疑惑。
    Stream> namesOriginalList = Stream.of(
    Arrays.asList(“Pankaj”),
    Arrays.asList(“David”, “Lisa”),
    Arrays.asList(“Amit”));
    //将流从List转换为String流
    Stream flatStream = namesOriginalList
    .flatMap(strList -> strList.stream());

    flatStream.forEach(System.out::println);

Java Stream 终端操作

让我们来看一些Java流终端操作的示例。

    流reduce()示例:我们可以使用reduce()对流的元素执行约简操作,使用可关联的累积函数,并返回一个Optional。让我们看看如何使用它来对流中的整数进行乘法运算。
    Stream numbers = Stream.of(1,2,3,4,5);
    Optional intOptional = numbers.reduce((i,j) – > {return i * j;});
    if(intOptional.isPresent())System.out.println(”乘法 =” + intOptional.get()); //120

    流计数()示例:我们可以使用这个终端操作来计算流中的项目数。
    Stream numbers1 = Stream.of(1,2,3,4,5);
    System.out.println(”流中的元素数=” + numbers1.count()); //5

    流forEach()示例:这可以用于迭代流。我们可以将其用于打印流的所有元素。

    Stream numbers2 = Stream.of(1,2,3,4,5);
    numbers2.forEach(i – > System.out.print(i +“,”)); //1,2,3,4,5,

    流匹配()示例:让我们看一些Stream API中匹配方法的示例。

    Stream numbers3 = Stream.of(1,2,3,4,5);
    System.out.println(”流中包含4吗?”+ numbers3.anyMatch(i – > i == 4));
    //流中包含4吗? 真的

    Stream numbers4 = Stream.of(1,2,3,4,5);
    System.out.println(”流中包含所有小于10的元素吗?”+ numbers4.allMatch(i – > i <10)); //流中包含所有小于10的元素吗?真的 Stream numbers5 = Stream.of(1,2,3,4,5);
    System.out.println(”流中不包含10吗?”+ numbers5.noneMatch(i – > i == 10));
    //流不包含10吗?真的

    findFirst()示例:这是一个短路终端操作,让我们看看如何使用它从以D开头的流中找到第一个字符串。

    Stream names4 = Stream.of(”Pankaj”,“Amit”,“David”,“Lisa”);
    Optional firstNameWithD = names4.filter(i – > i.startsWith(“D”))。findFirst();
    if(firstNameWithD.isPresent()){
    System.out.println(”以D开头的名字=”+ firstNameWithD.get()); //大卫
    }

Java 8 流API的限制

Java 8 Stream API 带来了许多新的功能,可用于处理列表和数组,但它也有一些限制。

    无状态的lambda表达式:如果你正在使用并行流,而lambda表达式是有状态的,可能会导致随机的响应。我们来看一个简单的程序。StatefulParallelStream.java

    “`
    package com.Olivia.java8.stream;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;

    public class StatefulParallelStream {

    public static void main(String[] args) {

    List ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
    List result = new ArrayList();

    Stream stream = ss.parallelStream();

    stream.map(s -> {
    synchronized (result) {
    if (result.size() < 10) { result.add(s); } } return s; }).forEach( e -> {});
    System.out.println(result);
    }
    }

    “`

    如果我们运行上述程序,会得到不同的结果,因为它取决于流的迭代方式,并且对于并行处理,我们没有定义任何顺序。如果我们使用顺序流,则不会出现这个问题。

    一旦流被消耗,就不能再次使用。如您在上述示例中看到的,每次我都在创建一个流。

    在Stream API中有很多方法,最令人困惑的部分是方法的重载。这使得学习曲线变得很漫长。

这就是关于Java 8 Stream示例教程的全部内容。我期待着使用这个特性,并通过并行处理使代码变得更易读且性能更好。参考资料:Java Stream API文档。