Java Stream的基本概念以及创建方法

前言

相信很多人(包括我自己),在很长一段时间内虽然使用了 JDK 1.8 ,却从来没有使用过自1.8开始增加的 Stream 这一强大使用的新特性,本文则将先从如何创建 Stream 开始,逐步去学会 Stream 的使用。本文不会涉及对流中数据的操作,而只讨论创建流的几种方法,以及一些基础概念,关于流的实用操作将会在后续文章中一一介绍。

Stream 与 Collection 的区别

1.用途与关注点不同

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

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

2.数组家族创建的方式

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

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

3.Stream家族的工厂方法

通过工厂方法来创建流的方式比较多,可以通过emptyofconcatgenerateiteraterangerangeClosed以及builder等方法创建流,下面就通过代码样例来一一介绍:

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

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

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

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

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

4.IO/NIO家族中的方法

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

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

5.Random 获取流的方式

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

Random random = new Random();
// 以下三种方式得到的均是随机数组成的<无限流>
IntStream stream1 = random.ints();
DoubleStream stream2 = random.doubles();
LongStream stream3 = random.longs();
Stream<Integer> 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<String> 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<JarEntry> stream3 = jarFile.stream();
} catch (IOException e) {
  e.printStackTrace();
}

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

总结

本文简单介绍了 Stream 这个自1.8开始引入的新特性,然后简单介绍了一些基本概念和流的创建方式,在接下来的文章中还会介绍流的一些实用操作,希望能和大家一起学会使用 Stream 这个实用的特性,当然本文也难免有错误之处,希望得到各位的指正。

以上就是Java Stream的基本概念以及创建方法的详细内容,更多关于JAVA Stream的资料请关注我们其它相关文章!

(0)

相关推荐

  • java8中Stream的使用以及分割list案例

    一.Steam的优势 java8中Stream配合Lambda表达式极大提高了编程效率,代码简洁易懂(可能刚接触的人会觉得晦涩难懂),不需要写传统的多线程代码就能写出高性能的并发程序 二.项目中遇到的问题 由于微信接口限制,每次导入code只能100个,所以需要分割list.但是由于code数量可能很大,这样执行效率就会很低. 1.首先想到是用多线程写传统并行程序,但是博主不是很熟练,写出代码可能会出现不可预料的结果,容易出错也难以维护. 2.然后就想到Steam中的parallel,能提高性能

  • java8 Stream流逐行处理文本文件

    本文中为大家介绍使用java8 Stream API逐行读取文件,以及根据某些条件过滤文件内容 1. Java 8逐行读取文件 在此示例中,我将按行读取文件内容并在控制台打印输出. Path filePath = Paths.get("c:/temp", "data.txt"); //try-with-resources语法,不用手动的编码关闭流 try (Stream<String> lines = Files.lines( filePath )) {

  • Java8新特性Stream的完全使用指南

    什么是Stream Stream是Java 1.8版本开始提供的一个接口,主要提供对数据集合使用流的方式进行操作,流中的元素不可变且只会被消费一次,所有方法都设计成支持链式调用.使用Stream API可以极大生产力,写出高效率.干净.简洁的代码. 如何获得Stream实例 Stream提供了静态构建方法,可以基于不同的参数创建返回Stream实例 使用Collection的子类实例调用stream()或者parallelStream()方法也可以得到Stream实例,两个方法的区别在于后续执行

  • Java8 stream 中利用 groupingBy 进行多字段分组求和案例

    Java8的groupingBy实现集合的分组,类似Mysql的group by分组功能,注意得到的是一个map 对集合按照单个属性分组.分组计数.排序 List<String> items = Arrays.asList("apple", "apple", "banana", "apple", "orange", "banana", "papaya");

  • Java8 Stream对两个 List 遍历匹配数据的优化处理操作

    使用场景,有两个List<Map<String,Object>>集合,第一个集合的所有元素都是需要保留的. 第一个集合的值为: {name=张三丰1, id=1} {name=张三丰2, id=2} {name=张三丰3, id=3} {name=张三丰4, id=4} {name=张三丰5, id=5} {name=张三丰6, id=6} {name=张三丰7, id=7} {name=张三丰8, id=8} 第二个集合的值为: {grade=61, id=1} {grade=6

  • Java使用FileInputStream流读取文件示例详解

    一.File流概念 JAVA中针对文件的读写操作设置了一系列的流,其中主要有FileInputStream,FileOutputStream,FileReader,FileWriter四种最为常用的流 二.FileInputStream 1)FileInputStream概念  FileInputStream流被称为文件字节输入流,意思指对文件数据以字节的形式进行读取操作如读取图片视频等 2)构造方法 2.1)通过打开与File类对象代表的实际文件的链接来创建FileInputStream流对象

  • Java8之Stream流代替For循环操作

    Stream流代替For循环进行输出可以使代码更简洁. 需求:根据姓名获取员工信息 1.建立实体类:Emp public class Emp { private String id; private String name; public Emp(String id, String name) { this.id=id; this.name=name; } public String getId() { return id; } public void setId(String id) { th

  • java8 stream 由一个list转化成另一个list案例

    我就废话不多说了,大家还是直接看代码吧~ // 利用stream进行类型转化 List<String> stringList = new ArrayList<>(); stringList.add("a11"); stringList.add("b11"); stringList.add("c11"); stringList.add("d11"); stringList.add("e11&qu

  • JAVA8 stream中三个参数的reduce方法对List进行分组统计操作

    背景 平时在编写前端代码时,习惯使用lodash来编写'野生'的JavaScript; lodash提供来一套完整的API对JS对象(Array,Object,Collection等)进行操作,这其中就包括_.groupBy 和 _.reduce,即分组和'聚合'(reduce不知道该怎么翻译合适). 使用这些'野生'的API能够极大的提高我本人编写JS代码的效率.而JAVA8开始支持stream和lambda表达式,这些和lodash的API有很多类似的功能.因此我在熟悉lodash的前提下尝

  • Java 8系列之Stream中万能的reduce用法说明

    reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型.比如,之前提到count.min和max方法,因为常用而被纳入标准库中.事实上,这些方法都是reduce操作. reduce方法有三个override的方法: Optional<T> reduce(BinaryOperator<T> accumulator); T reduce(T identity, BinaryOperator<T> accumulator); <

  • java8 streamList转换使用详解

    一.java8 stream 操作 List<Map<String, Object>> maps 转 Map<String, Object>的两种方法 第一种,实用于数据查询返回的是List<Map<String, Object>> maps 方法一. Map<String, Object>; resultMap = lists .stream() .flatMap(map ->map.entrySet().stream())

随机推荐