在 Java 8 中添加了 Stream API,以简化顺序或并行执行批量操作的任务。 该 API 提供了两个关键的抽象:流(Stream),表示有限或无限的数据元素序列,以及流管道 (stream pipeline),表示对这些元素的多级计算。 Stream 中的元素可以来自任何地方。 常见的源包括集合,数组,文件,正则表达式模式匹配器,伪随机数生成器和其他流。流中的数据元素可以是对象引用或基本类型。 支持三种基本类型:int,long 和 double。

流管道由源流(source stream)的零或多个中间操作和一个终结操作组成。每个中间操作都以某种方式转换流,例如将每个元素映射到该元素的函数或过滤掉所有不满足某些条件的元素。中间操作都将一个流转换为另一个流,其元素类型可能与输入流相同或不同。终结操作对流执行最后一次中间操作产生的最终计算,例如将其元素存储到集合中、返回某个元素或打印其所有元素。

管道延迟(lazily)计算求值:计算直到终结操作被调用后才开始,而为了完成终结操作而不需要的数据元素永远不会被计算出来。 这种延迟计算求值的方式使得可以使用无限流。 请注意,没有终结操作的流管道是静默无操作的,所以不要忘记包含一个。

Stream API 流式的(fluent)::它设计允许所有组成管道的调用被链接到一个表达式中。事实上,多个管道可以链接在一起形成一个表达式。

默认情况下,流管道按顺序 (sequentially) 运行。 使管道并行执行就像在管道中的任何流上调用并行方法一样简单,但很少这样做(第 48 个条目)。

Stream API 具有足够的通用性,实际上任何计算都可以使用 Stream 执行,但仅仅因为可以,并不意味着应该这样做。如果使用得当,流可以使程序更短更清晰;如果使用不当,它们会使程序难以阅读和维护。对于何时使用流没有硬性的规则,但是有一些启发。

考虑以下程序,该程序从字典文件中读取单词并打印其大小符合用户指定的最小值的所有变位词(anagram)组。如果两个单词由长度相通,不同顺序的相同字母组成,则它们是变位词。程序从用户指定的字典文件中读取每个单词并将单词放入 map 对象中。map 对象的键是按照字母排序的单词,因此『staple』的键是『aelpst』,『petals』的键也是『aelpst』:这两个单词就是同位词,所有的同位词共享相同的依字母顺序排列的形式(或称之为alphagram)。map 对象的值是包含共享字母顺序形式的所有单词的列表。 处理完字典文件后,每个列表都是一个完整的同位词组。然后程序遍历 map 对象的 values() 的视图并打印每个大小符合阈值的列表:

// Prints all large anagram groups in a dictionary iteratively
public class Anagrams {public static void main(String[] args) throws IOException {File dictionary = new File(args[0]);int minGroupSize = Integer.parseInt(args[1]);Map<String, Set<String>> groups = new HashMap<>();try (Scanner s = new Scanner(dictionary)) {while (s.hasNext()) {String word = s.next();groups.computeIfAbsent(alphabetize(word), (unused) -> new TreeSet<>()).add(word);}}for (Set<String> group : groups.values())if (group.size() >= minGroupSize)System.out.println(group.size() + ": " + group);}private static String alphabetize(String s) {char[] a = s.toCharArray();Arrays.sort(a);return new String(a);}
}

现在考虑以下程序,它解决了同样的问题,但大量过度使用了流。 请注意,整个程序(打开字典文件的代码除外)包含在单个表达式中。 在单独的表达式中打开字典文件的唯一原因是允许使用 try-with-resources 语句,该语句确保关闭字典文件:

// Overuse of streams - don't do this!
public class Anagrams {public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> word.chars().sorted().collect(StringBuilder::new,(sb, c) -> sb.append((char) c),StringBuilder::append).toString())).values().stream().filter(group -> group.size() >= minGroupSize).map(group -> group.size() + ": " + group).forEach(System.out::println);}}
}

如果你发现这段代码难以阅读,不要担心;你不是一个人。它更短,但是可读性也更差,尤其是对于那些不擅长使用流的程序员来说。过度使用流使程序难于阅读和维护

幸运的是,有一个折中的办法。下面的程序解决了同样的问题,使用流而不过度使用它们。其结果是一个比原来更短更清晰的程序:

// Tasteful use of streams enhances clarity and conciseness
public class Anagrams {public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> alphabetize(word))).values().stream().filter(group -> group.size() >= minGroupSize).forEach(g -> System.out.println(g.size() + ": " + g));}}// alphabetize method is the same as in original version
}

请注意,仔细选择 lambda 参数名称。 上面程序中参数 g 应该真正命名为 group,但是生成的代码行对于本书来说太宽了。 在没有显式类型的情况下,仔细命名 lambda 参数对于流管道的可读性至关重要。

另请注意,单词字母化是在单独的 alphabetize 方法中完成的。 这通过提供操作名称并将实现细节保留在主程序之外来增强可读性。 使用辅助方法对于流管道中的可读性比在迭代代码中更为重要,因为管道缺少显式类型信息和命名临时变量

流可以很容易地做一些事情:

· 统一转换元素序列
· 过滤元素序列
· 使用单个操作组合元素序列 (例如添加、连接或计算最小值)
· 将元素序列累积到一个集合中,可能通过一些公共属性将它们分组
· 在元素序列中搜索满足某些条件的元素

总之,有些任务最好使用流来完成,有些任务最好使用迭代来完成。将这两种方法结合起来,可以最好地完成许多任务。对于选择使用哪种方法进行任务,没有硬性规定,但是有一些有用的启发式方法。在许多情况下,使用哪种方法将是清楚的;在某些情况下,则不会很清楚。如果不确定一个任务是通过流还是迭代更好地完成,那么尝试这两种方法,看看哪一种效果更好

Effective Java~45. 谨慎使用Stream相关推荐

  1. Effective Java之谨慎地使用本地方法(五十四)

    本地方法,是指本地程序设计语言(c,或者c++)来编写的特殊方法. 本地方法在本地语言中可移植性任意的计算任务,并且返回到java程序语言. 为什么说谨慎使用本地方法? 1.如果调用了一个本地方法,那 ...

  2. Effective Java之谨慎地实现Serializable(七十四)

    1.序列化的含义和作用 序列化用来将对象编码成字节流,反序列化就使将字节流编码重新构建对象. 序列化实现了对象传输和对象持久化,所以它能够为远程通信提供对象表示法,为JavaBean组件提供持久化数据 ...

  3. Effective Java~46. 优先选择Stream 中无副作用的函数

    纯函数(pure function)的结果仅取决于其输入:它不依赖于任何可变状态,也不更新任何状态. 坏味道 // Uses the streams API but not the paradigm- ...

  4. Effective Java之谨慎地覆盖clone(十一)

    Clone提供一种语言之外的机制:无需调用构造器就可以创建对象. 它的通用约定非常弱: 创建和返回该对象的一个拷贝.这个拷贝的精确含义取决于该对象的类.一般含义是,对于任何对象x,表达式x.clone ...

  5. Effective Java(第3版) 90条经验法则

    目录 第2章 创建和销毁对象 第1条:用静态工厂方法替代构造器 第2条:遇到多个构造器参数时要考虑使用构建器 例子 使用 第3条:用私有构造器或者枚举类型强化Singleton属性 例子 使用 第4条 ...

  6. 5天带你读完《Effective Java》(三)

    <Effective Java>是Java开发领域无可争议的经典之作,连Java之父James Gosling都说: "如果说我需要一本Java编程的书,那就是它了". ...

  7. 《Effective Java》真的是一本值得一直去钻研的好书

    文章目录 <Effective Java>读书笔记 第一章 引言 第二章 创建和销毁对象 1 考虑使用静态工厂方法替代构造方法** 2 当构造方法参数过多时使用builder(建造者) 模 ...

  8. 第 3 次读 Effective Java,这 58 个技巧最值!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:Dong GuoChao <Effective ...

  9. 读完《Effective Java》后,总结了 50 条开发技巧

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Dong GuoChao 来源 | https ...

最新文章

  1. 绩点1.8成功逆袭!复旦博士林田成为华为第20位「天才少年」
  2. 10分钟理解CSS3 FlexBox
  3. getheaderfields java_java – HttpsURLConnection getHeaderFields没有返回set-cookie
  4. auto static 的区别
  5. matlab爱心函数代码画图
  6. 机器人操作系统ROS是什么?
  7. Android Jetpack 之 DataStore 初探
  8. php对照表,编码对照表的使用(1)-PHP教程,PHP应用
  9. SpringBoot项目实现多数据源的三种方式
  10. 十五数码难题 A*算法及深度优先算法实现
  11. FedNCF:Federated Neural Collaborative Filtering | 联邦神经协同过滤
  12. RecyclerView的使用(二):添加头部和尾部
  13. 从头开始写STM32F103C8T6驱动库(一)——STM32CubeMX创建并调整工程结构
  14. Vulnhub_hacksudo_fog
  15. 信息资源管理3500字超详细,全网最全笔记!!(第一章 1)
  16. 面试题之如何用Java设计一个自动售货机
  17. STM32Cube配置等精度测频和测相位差
  18. Centos7 SSH远程登录十分缓慢的解决方案
  19. Java练手项目3:基于Java的经典扫雷游戏(入门必备)!
  20. 【MD】高等数学常用符号

热门文章

  1. 论文浅尝 | KGQR: 用于交互式推荐的知识图谱增强Q-learning框架
  2. 领域应用 | 从本体论开始说起——运营商关系图谱的构建及应用
  3. 一篇文章学习Python中的多进程
  4. error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“MD_DynamicRelease”...
  5. Intel格式与Motorola格式的区别
  6. 网络版ATM项目的实现——客户端
  7. UI控件Telerik UI for WinForms发布R1 2019|附下载
  8. 修改maven打包名字
  9. ParserError: Error tokenizing data. C error: Expected 1 fields in line 122, saw 2
  10. 读写Excel2003文档