Java8实战-总结23

Java8实战-总结23

  • 使用流
    • 构建流
      • 由值创建流
      • 由数组创建流
      • 由文件生成流
      • 由函数生成流:创建无限流
    • 小结

使用流

构建流

流对于表达数据处理查询是非常强大而有用的。到目前为止,已经能够使用stream方法从集合生成流了。此外,还介绍了如何根据数值范围创建数值流。下面会介绍如何从值序列、数组、文件来创建流,甚至由生成函数来创建无限流。

由值创建流

可以使用静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流。然后,可以将字符串转换为大写,再一个个打印出来:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

可以使用empty得到一个空流,如下所示:

Stream<String> emptyStream = Stream.empty();

由数组创建流

可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。例如,可以将一个原始类型int的数组转换成一个IntStream,如下所示:

int[] numbers ={2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();//总和是41

由文件生成流

Java中用于处理文件等I/O操作的NIO API(非阻塞I/O)已更新,以便利用Stream APIjava.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。可以用这个方法看看一个文件中有多少各不相同的词:

long uniqueWords = 0;
try(Stream<String> lines =
			Files.lines(Paths.get("data.txt"), Charset.defaultCharset ())) {//流会自动关闭
			uniqueWords = lines.flatMap(line -> Arrays.stream(line.split("")))//生成单词流
				.distinct()//删除重复项
				.count();//数一数有多少各不相同的单词
}
catch(IOException e) {

	//如果打开文件时出现异常则加以处理
}

可以使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,可以对line调用split方法将行拆分成单词。应该注意的是,该如何使用flatMap产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把distinctcount方法链接起来,数数流中有多少各不相同的单词。

由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iteratestream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterategenerate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去。一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

  1. 迭代

一个iterate的简单例子:

Stream.iterate(0, n -> n + 2)
			.limit(10)
			.forEach(System.out::println);

iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(Unaryoperator类型)。这里,使用Lambda n -> n +2,返回的是前一个元素加上2。因此,iterate方法生成了一个所有正偶数的流:流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。这种iterate操作基本上是顺序的,因为结果取决于前一次应用。此操作将生成一个无限流——这个流没有结尾,因为值是按需计算的,可以永远计算下去。这个流是无界的。这是流和集合之间的一个关键区别。使用limit方法来显式限制流的大小。这里只选择了前10个偶数。然后可以调用forEach终端操作来消费流,并分别打印每个元素。

一般来说,在需要依次生成一系列值的时候应该使用iterate,比如一系列日期:1月31日,2月1日,依此类推。

  1. 生成

iterate方法类似,generate方法也可按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个supplier类型的Lambda提供新的值。先来看一个简单的用法:

Stream.generate(Math::random)
		.limit(5)
		.forEach(System.out::println);

这段代码将生成一个流,其中有五个01之间的随机双精度数。例如,运行一次得到了下面的结果:

0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241

Math.Random静态方法被用作新值生成器。同样,可以用limit方法显式限制流的大小,否则流将会无限长。

使用的供应源(指向Math.random的方法引用)是无状态的:它不会在任何地方记录任何值,以备以后计算使用。但供应源不一定是无状态的。可以创建存储状态的供应源,它可以修改状态,并在为流生成下一个值时使用。但很重要的一点是,在并行代码中使用有状态的供应源是不安全的。因此下面的代码仅仅是为了内容完整,应尽量避免使用。

在这个例子中会使用IntStream说明避免装箱操作的代码。IntStreamgenerate方法会接受一个IntSupplier,而不是Supplier。例如,可以这样来生成一个全是1的无限流:

IntStream ones = IntStream.generate(() -> 1);

Lambda允许创建函数式接口的实例,只要直接内联提供方法的实现就可以。也可以像下面这样,通过实现IntSupplier接口中定义的getAsInt方法显式传递一个对象:

IntStream twos = IntStream.generate(new IntSupplier() {
	public int getAsInt()	{
		return 2;
	}
});

generate方法将使用给定的供应源,并反复调用getAsInt方法,而这个方法总是返回2。但这里使用的匿名类和Lambda的区别在于,匿名类可以通过字段定义状态,而状态又可以用getAsInt方法来修改。这是一个副作用的例子。迄今见过的所有Lambda都是没有副作用的;它们没有改变任何状态。

回到斐波纳契数列的任务上,现在需要做的是建立一个IntSupplier,它要把前一项的值保存在状态中,以便getAsInt用它来计算下一项。此外,在下一次调用它的时候,还要更新IntSupplier的状态。下面的代码就是如何创建一个在调用时返回下一个斐波纳契项的IntSupplier:

IntSupplier fib = new IntSupplier() {
	private int previous = 0;
	private int current = 1;
	public int getAsInt () {
		int oldPrevious = this.previous;
		int nextValue = this.previous + this.current;
		this.previous = this.current;
		this.current = nextValue;
		return oldPrevious;
	}
};
IntStream.generate(fib).limit(10).forEach(System.out::println);

前面的代码创建了一个IntSupplier的实例。此对象有可变的状态:它在两个实例变量中记录了前一个斐波纳契项和当前的斐波纳契项。getAsInt在调用时会改变对象的状态,由此在每次调用时产生新的值。相比之下,使用iterate的方法则是纯粹不变的:它没有修改现有状态,但在每次迭代时会创建新的元组。

小结

现在可以更高效地处理集合了。流让你可以简洁地表达复杂的数据处理查询。此外,流可以透明地并行化。以下是关键概念。

  • Streams API可以表达复杂的数据处理查询。
  • 可以使用filterdistinctskiplimit对流做筛选和切片。
  • 可以使用mapflatMap提取或转换流中的元素。
  • 可以使用findFirstfindAny方法查找流中的元素。
  • 可以用allMatchnoneMatchanyMatch方法让流匹配给定的谓词。
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  • 可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
  • filtermap等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorteddistinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  • 流有三种基本的原始类型特化:IntStreamDoubleStreamLongStream。它们的操作也有相应的特化。
  • 流不仅可以从集合创建,也可从值、数组、文件以及iterategenerate等特定方法创建。
  • 无限流是没有固定大小的流。
    156

你可能感兴趣的:(开发语言,java)