首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

java.util.stream 库简介(1)

java.util.stream 库简介(1)

Java SE 8                    中主要的新语言特性是拉姆达表达式。可以将拉姆达表达式想作一种匿名方法;像方法一样,拉姆达表达式具有带类型的参数、主体和返回类型。但真正的亮点不是拉姆达表达式本身,而是它们所实现的功能。拉姆达表达式使得将行为表达为数据变得很容易,从而使开发具有更强表达能力、更强大的库成为可能。
Java SE 8 中引入的一个这样的库是 java.util.stream 包                (Streams),它有助于为各种数据来源上的可能的并行批量操作建立简明的、声明性的表达式。较早的 Java 版本中也编写过像 Streams                这样的库,但没有紧凑的行为即数据语言特性,而且它们的使用很麻烦,以至于没有人愿意使用它们。您可以将 Streams 视为 Java                中第一个充分利用了拉姆达表达式的强大功能的库,但它没有什么特别奇妙的地方(尽管它被紧密集成到核心 JDK 库中)。Streams 不是该语言的一部分                — 它是一个精心设计的库,充分利用了一些较新的语言特性。
关于本系列借助 java.util.stream 包,您可以简明地、声明性地表达集合、数组和其他数据源上可能的并行批量操作。在                    Java 语言架构师 Brian Goetz 编写的这个  中,全面了解 Streams 库,并了解如何最充分地使用它。

本文是一个深入探索 java.util.stream                库的系列的第一部分。本期介绍该库,并概述它的优势和设计原理。在后续几期中,您将学习如何使用流来聚合和汇总数据,了解该库的内部原理和性能优化。
使用流的查询流的最常见用法之一是表示对集合中的数据的查询。清单 1                给出了一个简单的流管道示例。该管道获取一个在买家和卖家之间模拟购买的交易集合,并计算生活在纽约的卖家的交易总价值。
清单 1.                一个简单的流管道
1
2
3
4
5
int totalSalesFromNY
    = txns.stream()
          .filter(t -> t.getSeller().getAddr().getState().equals("NY"))
          .mapToInt(t -> t.getAmount())
          .sum();




“流利用了这种最强大的计算原理:组合。”

filter() 操作仅选择与来自纽约的卖家进行的交易。mapToInt()                操作选择所关注交易的交易金额。最终的 sum() 操作将对这些金额求和。
这个例子非常容易理解,即使比较挑剔的人也会发现这个查询的命令版本(for                循环)非常简单,而且需要更少的代码行即可表达。为了体现流方法的好处,示例问题没有必要变得过于复杂。流利用了这种最强大的计算原理:组合。通过使用简单的构建块(过滤、映射、排序、聚合)来组合复杂的操作,在问题变得比相同数据源上更加临时的计算更复杂时,流查询更可能保留写入和读取的简单性。
作为来自清单 1 中的相同领域的更复杂查询,考虑 “打印与年龄超过 65                岁的买家进行交易的卖家姓名,并按姓名排序。”以旧式的(命令)方式编写此查询可能会得到类似清单 2 的结果。
清单 2.                    对一个集合的临时查询
1
2
3
4
5
6
7
8
9
10
11
12
13
Set<Seller> sellers = new HashSet<>();
for (Txn t : txns) {
    if (t.getBuyer().getAge() >= 65)
        sellers.add(t.getSeller());
}
List<Seller> sorted = new ArrayList<>(sellers);
Collections.sort(sorted, new Comparator<Seller>() {
    public int compare(Seller a, Seller b) {
        return a.getName().compareTo(b.getName());
    }
});
for (Seller s : sorted)
    System.out.println(s.getName());




尽管此查询比第一个查询稍微复杂一点,但很明显采用命令方法的结果代码的组织结构和可读性已开始下降。读者首先看到的不是计算的起点和终点;而是一个一次性中间结果的声明。要阅读此代码,您需要在头脑中缓存大量上下文,然后才能明白代码的实际用途。清单                3 展示了可以如何使用 Streams 重写此查询。
清单 3. 使用 Streams 表达的清单 2                中的查询
1
2
3
4
5
6
7
txns.stream()
    .filter(t -> t.getBuyer().getAge() >= 65)
    .map(Txn::getSeller)
    .distinct()
    .sorted(comparing(Seller::getName))
    .map(Seller::getName)
    .forEach(System.out::println);




清单 3 中的代码更容易阅读,因为用户既没有被 “垃圾” 变量(比如 sellers 和                sorted)分心,也不需要在阅读代码的同时跟踪记录大量上下文;而且代码看起来几乎就像问题陈述一样。可读性更强的代码也更不容易出错,因为维护者更容易一眼就看出代码在做什么。
Streams 登录所采用的设计方法实现了实际的关注点分离。客户端负责指定计算的是 “什么”,而库负责控制                “如何做”。这种分离倾向于与专家经验的分发平行进行;客户端编写者通常能够更好地了解问题领域,而库编写者通常拥有所执行的算法属性的更多专业技能。编写允许这种关注点分离的库的主要推动力是,能够像传递数据一样轻松地传递行为,从而使调用方可在                API 中描述复杂计算的结构,然后离开,让库来选择执行战略。
返回列表