在Stream和Collections API之间进行选择

时间:2015-06-10 18:08:10

标签: java collections java-8 java-stream

考虑以下示例,在List中打印最大元素:

List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);           
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println);

使用Collections.max方法也可以实现相同的目标:

System.out.println(Collections.max(list));

以上代码不仅更短,而且阅读更清晰(在我看来)。我想到了类似的例子,例如binarySearch vs filterfindAny一起使用。

我理解Stream可以是无限管道,而不是受{JVM可用内存限制的Collection。这将是我决定是使用Stream还是Collections API的标准。是否有任何其他原因可以选择Stream Collections API(例如效果)。更一般地说,这是选择Stream而不是旧API的唯一原因,它可以更清洁,更短的方式完成工作吗?

2 个答案:

答案 0 :(得分:7)

Stream API就像瑞士军刀:它可以让您通过有效地组合工具来完成相当复杂的操作。另一方面,如果你只需要一把螺丝刀,那么独立螺丝刀可能会更方便。 Stream API包含许多内容(如distinctsorted,基本操作等),否则需要您编写多行并引入中间变量/数据结构和无聊循环,从实际中吸引程序员的注意力算法。有时使用Stream API即使对于顺序代码也可以提高性能。例如,考虑一些旧的API:

class Group {
    private Map<String, User> users;

    public List<User> getUsers() {
        return new ArrayList<>(users.values());
    }
}

这里我们要返回该组的所有用户。 API设计者决定返回List。但它可以通过各种方式在室外使用:

List<User> users = group.getUsers();
Collections.sort(users);
someOtherMethod(users.toArray(new User[users.size]));

这里它被排序并转换为数组以传递给碰巧接受数组的其他方法。在另一个地方getUsers()可以像这样使用:

List<User> users = group.getUsers();
for(User user : users) {
    if(user.getAge() < 18) {
        throw new IllegalStateException("Underage user in selected group!");
    }
}

这里我们只想找到符合某些条件的用户。在这两种情况下,实际上都不需要复制到中间ArrayList。当我们迁移到Java 8时,我们可以将getUsers()方法替换为users()

public Stream<User> users() {
    return users.values().stream();
}

修改来电者代码。第一个:

someOtherMethod(group.users().sorted().toArray(User[]::new));

第二个:

if(group.users().anyMatch(user -> user.getAge() < 18)) {
    throw new IllegalStateException("Underage user in selected group!");
}

这样它不仅更短,而且可以更快地工作,因为我们跳过了中间复制。

Stream API中的另一个概念点是,只需添加parallel()步骤即可并行化根据指南编写的任何流代码。当然,这并不总能提升性能,但它比我预期的更有帮助。通常,如果对0.1ms or longer顺序执行操作,则可以从并行化中受益。无论如何,我们还没有看到过如此简单的方法在Java中进行并行编程。

答案 1 :(得分:3)

当然,它总是取决于具体情况。以您为例:

List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);           
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println);

如果您想有效地执行相同的事情,可以使用

IntStream.of(1,4,3,9,7,4,8).max().ifPresent(System.out::println);

不涉及任何自动装箱。但是,如果您的假设是预先设置List<Integer>,那么这可能不是一个选项,因此如果您只对max值感兴趣,Collections.max可能是更简单的选择。

但这会导致你事先得到List<Integer>的问题。也许,这是旧代码(或使用旧思维编写的新代码)的结果,除了使用装箱和Collection之外别无选择,因为过去没有其他选择?

所以也许你应该考虑产生这个集合的来源,然后再费心去消费它(或者好吧,同时考虑两者)。

如果您只拥有Collection并且您需要的只是一个基于Collection的简单实现的单一终端操作,您可以直接使用它而不必担心Stream API。 API设计人员承认了这一想法,因为他们向forEach(…) API添加了Collection等方法,而不是坚持使用stream().forEach(…)的每个人。 Collection.forEach(…)并不是Collection.stream().forEach(…)的简单简写,事实上,它已经在更抽象的Iterable接口上定义,甚至没有stream()方法。< / p>

顺便说一下,您应该了解Collections.binarySearchStream.filter/findAny之间的区别。前者要求集合排序,如果满足该先决条件,可能是更好的选择。但是如果集合没有排序,简单的线性搜索比单独使用二进制搜索排序更有效,更不用说二进制搜索仅在过滤/ findAny时与List一起使用的事实。适用于支持各种源集合的任何流。