β

跟上Java8 - Stream API快速入门

王爵的技术博客 18 阅读

在前面我们简单介绍了 lambda 表达式,Java8旨在帮助程序员写出更好的代码, 其对核心类库的改进也是关键的一部分, Stream 是Java8种处理集合的抽象概念, 它可以指定你希望对集合的操作,但是执行操作的时间交给具体实现来决定。

为什么需要Stream?

Java语言中集合是使用最多的API,几乎每个Java程序都会用到集合操作, 这里的Stream和IO中的Stream不同,它提供了对集合操作的增强,极大的提高了操作集合对象的便利性。

集合对于大多数编程任务而言都是基本的,为了解释集合是怎么工作,我们想象一下当下最火的外卖APP, 当我们点菜的时候需要按照 距离 价格 销量 等进行排序后筛选出自己满意的菜品。 你可能想选择距离自己最近的一家店铺点菜,尽管用集合可以完成这件事,但集合的操作远远算不上完美。

假如让你编写上面示例中的代码,你可能会写出如下:

public static void main(String[] args) {
    Property p1 = new Property("叫了个鸡", 1000, 500, 2);
    Property p2 = new Property("张三丰饺子馆", 2300, 1500, 3);
    Property p3 = new Property("永和大王", 580, 3000, 1);
    Property p4 = new Property("肯德基", 6000, 200, 4);

    List<Property> properties = Arrays.asList(p1, p2, p3, p4);

    Collections.sort(properties, (x, y) -> x.distance.compareTo(y.distance));

    String name = properties.get(0).name;
    System.out.println("距离我最近的店铺是:" + name);
}

这里也使用了部分 lambda 表达式,在Java8之前你可能写的更痛苦一些。 要是要处理大量元素又该怎么办呢?为了提高性能,你需要并行处理,并利用多核架构。 但写并行代码比用迭代器还要复杂,而且调试起来也够受的!

Stream 中操作这些东西当然是非常简单的,小试牛刀:

int count = 0;
for (Property property : properties) {
    if(property.sales > 1000){
        count++;
    }
}

上面的操作是可行的,但是当每次迭代的时候你需要些很多重复的代码。将 for 循环修改为并行执行也非常困难, 需要修改每个 for 的实现。

从集合背后的原理来看, for 循环封装了迭代的语法糖,首先调用 iterator 方法,产生一个 Iterator 对象, 然后控制整个迭代,这就是 外部迭代 。迭代的过程通过调用 Iterator 对象的 hasNext next 方法完成。

使用迭代器进行计算

long count2 = properties.stream()
                .filter(p -> p.sales > 1000)
                .count();

上述代码是通过 Stream API 完成的,我们可以把它理解为2个步骤:

  1. 找出所有销量大于1000的店铺
  2. 计算出店铺个数

为了找出销量大于1000的店铺,需要先做一次过滤: filter ,你可以看看这个方法的入参就是前面讲到的 Predicate 断言型函数式接口, 测试一个函数完成后,返回值为 boolean 。 由于 Stream API 的风格,我们没有改变集合的内容,而是描述了 Stream 的内容,最终调用 count() 方法计算出 Stream 里包含了多少个过滤之后的对象,返回值为 long

创建Stream

你已经知道Java8种在 Collection 接口添加了 Stream 方法,可以将任何集合转换成一个 Stream 。 如果你操作的是一个数组可以使用 Stream.of(1, 2, 3) 方法将它转换为一个流。

也许有人知道JDK7中添加了一些类库如 Files.readAllLines(Paths.get("/home/biezhi/a.txt")) 这样的读取文件行方法。 List 作为 Collection 的子类拥有转换流的方法,那么我们读取这个文本文件到一个字符串变量中将变得更简洁:

properties.stream()
            .filter(p -> p.distance < 1000)

筛选出名称大于5个字的店铺

properties.stream()
            .map(p -> p.name);

传给 map lambda 表达式接收一个 Property 类型的参数,返回一个 String 。 参数和返回值不必属于同一种类型,但是 lambda 表达式必须是 Function 接口的一个实例。

flatMap

有时候我们会遇到提取子流的操作,这种情况用的不多但是遇到 flatMap 将变得更容易处理。

例如我们有一个 List<List<String>> 结构的数据:

lists.stream()
        .flatMap(Collection::stream)
        .filter(str -> str.length() > 2)
        .count();

在不使用 flatMap 前你可能需要做2次 for 循环。这里调用了 List stream 方法将每个列表转换成 Stream 对象, 其他的就和之前的操作一样。

max和min

Stream 中常用的操作之一是求最大值和最小值, Stream API 中的 max min 操作足以解决这一问题。

我们需要筛选出价格最低的店铺:

List<Property> properties = properties.stream()
            .sorted(Comparator.comparingInt(x -> x.distance))
            .limit(2)
            .collect(Collectors.toList());

获取所有店铺的名称

Map<String, Integer> map = properties.stream()
        .collect(Collectors.toMap(Property::getName, Property::getPriceLevel));

所有价格等级的店铺列表

properties.stream()
            .filter(p -> p.priceLevel < 4)
            .sorted(Comparator.comparingInt(Property::getDistance))
            .map(Property::getName)
            .limit(2)
            .collect(Collectors.toList());

调用 parallelStream 方法即能并行处理

properties.parallelStream()
.filter(p -> p.priceLevel < 4)
.sorted(Comparator.comparingInt(Property::getDistance))
.map(Property::getName)
.limit(2)
.collect(Collectors.toList());

读到这里,大家的第一反应可能是立即将手头代码中的 stream 方法替换为 parallelStream 方法, 因为这样做简直太简单了!先别忙,为了将硬件物尽其用,利用好并行化非常重要,但流类库提供的数据并行化只是其中的一种形式。

我们先要问自己一个问题: 并行化运行基于流的代码是否比串行化运行更快? 这不是一个简单的问题。 回到前面的例子,哪种方式花的时间更多取决于串行或并行化运行时的环境。

在前面我们简单介绍了 lambda 表达式,Java8旨在帮助程序员写出更好的代码, 其对核心类库的改进也是关键的一部分, Stream 是Java8种处理集合的抽象概念, 它可以指定你希望对集合的操作,但是执行操作的时间交给具体实现来决定。

作者:王爵的技术博客
javaer
原文地址:跟上Java8 - Stream API快速入门, 感谢原作者分享。

发表评论