3.1 流的创建

问题

用户希望从数据源创建流。

方案

使用 java.util.stream.Stream 接口定义的各种静态工厂方法,以及 java.lang.Iterable 接口或 java.util.Arrays 类定义的 stream 方法。

讨论

Java 8 引入的 Stream 接口定义了多种用于创建流的静态方法。具体而言,可以采用 Stream.ofStream.iterateStream.generate 等静态方法创建流。

Stream.of 方法传入元素的可变参数列表:

  1. static <T> Stream<T> of(T... values)

在 Java 标准库中,of 方法的实现实际上被委托给 java.util.Arrays 类定义的 stream 方法,如例 3-1 所示。

例 3-1 Stream.of 方法的引用实现

  1. @SafeVarargs
  2. public static<T> Stream<T> of(T values) {
  3. return Arrays.stream(values);
  4. }

3.1 流的创建 - 图1 @SafeVarargs 注解属于 Java 泛型的一部分,它在使用数组作为参数时出现。因为用户有可能将一个类型化数组(typed array)赋给一个 Object 数组,导致添加的元素引发类型安全问题。换言之,@SafeVarargs 注解构成了开发人员对类型安全的承诺。详见附录 A。

Stream.of 方法的简单应用如例 3-2 所示。

3.1 流的创建 - 图2 由于流在遇到终止表达式之前不会处理任何数据,本范例中的所有示例都会在末尾添加一个终止方法,如 collectforEach

 

例 3-2 利用 Stream.of 方法创建流

  1. String names = Stream.of("Gomez", "Morticia", "Wednesday", "Pugsley")
  2. .collect(Collectors.joining(","));
  3. System.out.println(names);
  4. // 打印Gomez,Morticia,Wednesday,Pugsley

Java API 还定义了 of 方法的重载形式,它传入单个元素 T t,返回只包含一个元素的单例顺序流(singleton sequential stream)。

Arrays.stream 方法的应用如例 3-3 所示。

例 3-3 利用 Arrays.stream 方法创建流

  1. String[] munsters = { "Herman", "Lily", "Eddie", "Marilyn", "Grandpa" };
  2. names = Arrays.stream(munsters)
  3. .collect(Collectors.joining(","));
  4. System.out.println(names);
  5. // 打印Herman,Lily,Eddie,Marilyn,Grandpa

由于需要提前创建数组,上述方案略有不便,但足以满足可变参数列表的需要。Java API 定义了 Arrays.stream 方法的多种重载形式,用于处理 intlongdouble 型数组,还定义了本例使用的泛型类型。

Stream 接口定义的另一种静态工厂方法是 iterate,其签名如下:

  1. static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

根据 Javadoc 的描述,iterate 方法“返回一个无限顺序的有序流(infinite sequential ordered stream),它由迭代应用到初始元素种子的函数 f 产生”。回顾一下范例 2.4 讨论的 UnaryOperator,它是一种函数式接口,其输入参数和输出类型相同。如果有办法根据当前值生成流的下一个值,iterate 方法将相当有用,如例 3-4 所示。

例 3-4 利用 Stream.iterate 方法创建流

  1. List<BigDecimal> nums =
  2. Stream.iterate(BigDecimal.ONE, n -> n.add(BigDecimal.ONE) )
  3. .limit(10)
  4. .collect(Collectors.toList());
  5. System.out.println(nums);
  6. // 打印[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  7. Stream.iterate(LocalDate.now(), ld -> ld.plusDays(1L))
  8. .limit(10)
  9. .forEach(System.out::println)
  10. // 打印从当日开始之后的10天

第一段代码采用 BigDecimal 实例,从 1 开始递增。第二段代码采用 java.time 包新增的 LocalDate 类,从当日开始按天递增。由于生成的两个流都是无界的,需要通过中间操作 limit 加以限制。

Stream 接口还定义了静态工厂方法 generate,其签名为:

  1. static <T> Stream<T> generate(Supplier<T> s)

generate 方法通过多次调用 Supplier 产生一个顺序的无序流(sequential, unordered stream)。在 Java 标准库中,Supplier 的一种简单应用是 Math.random 方法,它不传入参数而返回 double 型数据,如例 3-5 所示。

例 3-5 利用 Math.random 创建随机流(double 型)

  1. long count = Stream.generate(Math::random)
  2. .limit(10)
  3. .forEach(System.out::println)

如果已有集合,可以利用 Collection 接口新增的默认方法 stream,如例 3-6 所示。1

1希望承认以下事实不会让我的声誉毁于一旦:我随口就能叫出《脱线家族》中六个孩子的姓名。真的,我也没想到自己的记忆力会这么好。

例 3-6 从集合创建流

  1. List<String> bradyBunch = Arrays.asList("Greg", "Marcia", "Peter", "Jan",
  2. "Bobby", "Cindy");
  3. names = bradyBunch.stream()
  4. .collect(Collectors.joining(","));
  5. System.out.println(names);
  6. // 打印Greg,Marcia,Peter,Jan,Bobby,Cindy

Stream 接口定义了三种专门用于处理基本数据类型的子接口,它们是 IntStreamLongStreamDoubleStreamIntStreamLongStream 还包括另外两种创建流所用的工厂方法 rangerangeClosed,二者的方法签名如下:

  1. static IntStream range(int startInclusive, int endExclusive)
  2. static IntStream rangeClosed(int startInclusive, int endInclusive)
  3. static LongStream range(long startInclusive, long endExclusive)
  4. static LongStream rangeClosed(long startInclusive, long endInclusive)

注意这几个语句中的参数有所不同:rangeClosed 包含终值(end value),而 range 不包含终值。两种方法都返回一个顺序的有序流,从第一个参数开始逐一递增。例 3-7 展示了 rangerangeClosed 方法的应用。

例 3-7 rangerangeClosed 方法

  1. List<Integer> ints = IntStream.range(10, 15)
  2. .boxed()
  3. .collect(Collectors.toList());
  4. System.out.println(ints);
  5. // 打印[10, 11, 12, 13, 14]
  6. List<Long> longs = LongStream.rangeClosed(10, 15)
  7. .boxed()
  8. .collect(Collectors.toList());
  9. System.out.println(longs);
  10. // 打印[10, 11, 12, 13, 14, 15]

Collectors 需要将基本数据类型转换为 List

本例唯一的奇怪之处在于使用了 boxed 方法将 int 值转换为 Integer 实例。有关 boxed 方法的详细讨论请参见范例 3.2。

创建流所用的方法总结如下。

  • Stream.of(T… values)Stream.of(T t)
  • Arrays.stream(T[] array) 以及用于处理 int[]double[]long[] 型数组的重载形式
  • Stream.iterate(T seed, UnaryOperator f)
  • Stream.generate(Supplier s)
  • Collection.stream()
  • 使用 rangerangeClosed 方法:
    • IntStream.range(int startInclusive, int endExclusive)
    • IntStream.rangeClosed(int startInclusive, int endInclusive)
    • LongStream.range(long startInclusive, long endExclusive)
    • LongStream.rangeClosed(long startInclusive, long endInclusive)

另见

流的应用贯穿全书。有关将基本类型流转换为包装器实例(wrapper instance)的讨论请参见范例 3.2。