一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Java Stream的基本概念以及创建方法代码解析

时间:2020-08-28 编辑:袖梨 来源:一聚教程网

本篇文章小编给大家分享一下Java Stream的基本概念以及创建方法代码解析,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看。

Stream 与 Collection 的区别

1.用途与关注点不同

Collection 主要关注于对象的存储方面,通过使用List、Map、Set等等数据结构,让数据被更好的组织起来,以便于使用。而 Stream 则关注于对象的操作方面,包含reduce、map、filter等等实用的操作。

2.流是懒搜索(Laziness-seeking)的

先看一个例子,考虑一下代码:

Random random = new Random(29);
random.ints()
   .filter(v -> v > 5 && v < 31)
   .limit(3)
   .forEach(System.out::println);

// output:
//  21
//  22
//  28

代码首先创建了一个随机整数流,然后过滤得到其中在(5, 31)范围内的数,最终得到其中的3个数并输出,这里创建的流就是3中所说的无限流,而流在执行的过程中一旦得到一个满足条件的整数就会加到结果序列中,并且开始进行下一轮的搜索,直到找到3个满足的整数为止。流只会完成所给任务(找到3个满足指定范围的整数并输出),不会有额外的操作。

3.流的大小可以是无限的

尽管 Collection 的数据量也可以动态扩展改变,但由于计算机内存是有限的,所以其数据量大小始终可以看成只能为有限的大小。但 Stream 则不同,由于流是懒加载的,所以当使用limit类似的短路操作时,就可以利用特性2的原因去接收一个无限流。

4.流操作不存在副作用

和 Collection 中的某些操作,例如remove会删除集合中的元素不同,流不会修改生成流的原有集合中的数据,例如使用filter时,会产生一个经过元素过滤后的新流,而不会修改原集合中的数据。

5.流属于消耗品(Consumable)

不同与 Collection 没有访问次数与使用的限制,一个流在其生命周期中只能被执行一次,当执行了终端操作(terminal operation,在之后的文章中会具体介绍)后,即使没有将流关闭,例如上述代码中的forEach,也无法再次访问了(类似迭代器),如下代码所示,想要再操作,必须重新创建一个流。

IntStream stream = new Random(29).ints();
stream.filter(v -> v > 5 && v < 31)
   .limit(3)
   .forEach(System.out::println);
// 当执行了终端操作后再使用,就会出现一下异常提示信息
// java.lang.IllegalStateException: stream has already been operated upon or closed
stream.forEach(System.out::println);

创建流

流可以通过很多种方式被创建,下面进行一一介绍:

1.Collection 家族创建的方式

对于实现了Collection 接口的类,都可以通过stream()和parallelStream()创建对应流,如下代码所示:

List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// 创建一个普通的流
Stream stream = list.stream();
// 创建一个并行流
Stream parallelStream = list.parallelStream();

2.数组家族创建的方式

对于数组类型的元素,都可以使用Arrays类的stream()创建对应的流,如果想获得并行流则需要使用parallel()方法,如下所示:

IntStream stream = Arrays.stream(new int[]{1, 2, 3});
// 生成流对应的并行流
IntStream parallelStream = stream.parallel();

3.Stream家族的工厂方法

通过工厂方法来创建流的方式比较多,可以通过empty、of、concat、generate、iterate、range、rangeClosed以及builder等方法创建流,下面就通过代码样例来一一介绍:

// 产生一个不包含任何元素的流
Stream stream1 = Stream.empty();

// 由给定元素所生成的流
Stream stream2 = Stream.of(1, 2, 3);

// 合并两个流产生一个新的流
Stream stream3 = Stream.concat(stream1, stream2);

// 创建一个<无限流>,流中的数据是通过调用所传函数产生的
Stream stream4 = Stream.generate(Math::random);

// 创建一个<无限流>,流中的数据由第一个参数、将
// 第一个参数作为函数参数调用产生的值以及不断将
// 函数调用得到的值作为参数继续调用所组成,
// 例如下面会生成1,2,3....的整数流
Stream stream5 = Stream.iterate(1, v -> v + 1);

// 创建范围为[1, 5)组成的整数流
IntStream stream6 = IntStream.range(1, 5);

// 创建范围为[1, 5]组成的整数流
IntStream stream7 = IntStream.rangeClosed(1, 5);

// 通过流的建造者模式创建流
Stream.Builder builder = Stream.builder();
for (int i = 0; i < 10; i++) {
  // add 与 accept 方法均可将元素添加到流中
  // 区别是 add 无返回值, accept 会返回当前 builder 的 this 对象
  // 底层 add 方法也是调用了 accept 然后返回 this
  // 因此对于 add 方法可以进行链式调用
  builder.add(i);
  builder.accept(i);
}
Stream stream8 = builder.build();

4.IO/NIO家族中的方法

除了两种获取lines生成的流外,其它几种方式都很少使用,这一部分了解即可。

try {
  String dir = System.getProperty("user.dir");
  // 以下两种方法均是获取文件中行数据组成的流
  Stream stream1 = new BufferedReader(new FileReader(dir + "\demo.txt")).lines();
  Stream stream2 = Files.lines(Paths.get(dir + "\demo.txt"));
  // 获取指定路径下所有文件/文件夹的路径组成的流
  Stream stream3 = Files.list(Paths.get("d:\temp"));
  // 获取指定路径下以及指定最深文件层级内(在这里为2)且满足函数条件的所有文件/文件夹的路径组成的流
  Stream stream4 = Files.find(
    Paths.get("d:\temp"), 1, (path, basicFileAttributes) -> path.isAbsolute());
  // 获取指定路径下以及指定最深文件层级内(在这里为2)所有文件/文件夹的路径组成的流
  Stream stream5 = Files.walk(Paths.get("d:\temp"), 2);
} catch (IOException e) {
  e.printStackTrace();
}

5.Random 获取流的方式

由于直接使用 Random 类生成随机数无限流,均为基本数据类型组成的流,因此通常还需要使用boxed方法进行装箱(以前凡是生成的为IntStream,DoubleStream,LongStream均同此),以便可以使用更加丰富的特性。

Random random = new Random();
// 以下三种方式得到的均是随机数组成的<无限流>
IntStream stream1 = random.ints();
DoubleStream stream2 = random.doubles();
LongStream stream3 = random.longs();
Stream boxedStream = stream1.boxed();

下面就先举一个具体的实用的例子,在之后的文章中会详细介绍一些实用操作,这里可以先做了解:

// 对数组元素进行倒序排序
// 如果不进行装箱(boxed)处理,则只能使用默认的升序排序方法
// 通过装箱,则可以通过自定义比较器,实现更加多样的排序
int[] arr = {1, 5, 4, 6, 3, 9, 4, 5, 6, 4};
int[] reverseArr = Arrays.stream(arr)
             .boxed()
             .sorted(Comparator.reverseOrder())
             .mapToInt(Integer::valueOf)
             .toArray();
// output: [9, 6, 6, 5, 5, 4, 4, 4, 3, 1]
System.out.println(Arrays.toString(reverseArr));

6.其它可以生成流的类

除了以上介绍的几个主要可以生成流的类之外,还有一些其它不太常见的可以流的类,下面是部分代码展示:

String s = "1,2,3,4,5,6,7";
// 由分割后的字符串组成的流
// 在这里就是"1", "2", "3", "4", "5", "6", "7"组成的流
Stream stream1 = Pattern.compile(",").splitAsStream(s);
BitSet bitSet = new BitSet();
for (int i = 0; i < 10; i++) {
  if (i % 2 == 0) {
    bitSet.set(i);
  }
}
// 由 bitset 中被设置为 true 的位下标所组成的流
// 在这里就是0, 2, 4, 6, 8
IntStream stream2 = bitSet.stream();
try {
  String dir = System.getProperty("user.dir");
  JarFile jarFile = new JarFile(dir + "\demo.jar");
  // 由指定 jar 包中所有文件及文件夹的 JarEntry 对象所组形成的流
  Stream stream3 = jarFile.stream();
} catch (IOException e) {
  e.printStackTrace();
}

此外还可以通过 StreamSupport工具类进行产生和操作流,由于本文包括之后的文章主要是为了入门和先简单上手,所以这里不做详细讨论,感兴趣的可以自己进行查阅资料。