在 Stream 和 Collections 之间选择 API
Choosing between Stream and Collections API
考虑以下打印 List
中最大元素的示例:
List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println);
同样的objective也可以使用Collections.max
方法实现:
System.out.println(Collections.max(list));
上面的代码不仅更短而且更清晰易读(在我看来)。有类似的例子浮现在脑海中,例如 binarySearch
与 filter
与 findAny
.
结合使用
我知道 Stream
可以是无限管道,而不是受 JVM 可用内存限制的 Collection
。这将是我决定使用 Stream
还是 Collections
API 的标准。选择 Stream
而不是 Collections
API 是否还有其他原因(例如性能)。更一般地说,这是选择 Stream
而不是可以以更简洁、更短的方式完成工作的旧 API 的唯一原因吗?
Stream API 就像一把瑞士军刀:它允许您通过有效地组合工具来执行相当复杂的操作。另一方面,如果您只需要一把螺丝刀,独立的螺丝刀可能会更方便。 Stream API 包括许多东西(如 distinct
、sorted
、原始操作等),否则将需要您编写几行代码并引入中间 variables/data 结构和无聊的循环绘图程序员的注意力来自于实际的算法。有时使用 Stream API 甚至可以提高顺序代码的性能。例如,考虑一些旧的 API:
class Group {
private Map<String, User> users;
public List<User> getUsers() {
return new ArrayList<>(users.values());
}
}
这里我们要return该组的所有用户。 API 设计师决定 return 一个 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 中从未见过如此简单的并行编程方法。
当然,这总是要视情况而定。举个例子:
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(…)
之类的方法添加到 Collection
API 而不是坚持每个人都使用 stream().forEach(…)
。 Collection.forEach(…)
并不是 Collection.stream().forEach(…)
的简单简写,事实上,它已经定义在更抽象的 Iterable
接口上,甚至没有 stream()
方法。
顺便说一句,你应该明白Collections.binarySearch
和Stream.filter/findAny
之间的区别。前者要求集合 sorted 如果满足该先决条件,可能是更好的选择。但是如果集合没有排序,一个简单的线性搜索比只使用二分搜索排序更有效,更不用说二分搜索只在 List
s 时工作,而 [=48] =] 适用于支持各种源集合的任何流。
考虑以下打印 List
中最大元素的示例:
List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println);
同样的objective也可以使用Collections.max
方法实现:
System.out.println(Collections.max(list));
上面的代码不仅更短而且更清晰易读(在我看来)。有类似的例子浮现在脑海中,例如 binarySearch
与 filter
与 findAny
.
我知道 Stream
可以是无限管道,而不是受 JVM 可用内存限制的 Collection
。这将是我决定使用 Stream
还是 Collections
API 的标准。选择 Stream
而不是 Collections
API 是否还有其他原因(例如性能)。更一般地说,这是选择 Stream
而不是可以以更简洁、更短的方式完成工作的旧 API 的唯一原因吗?
Stream API 就像一把瑞士军刀:它允许您通过有效地组合工具来执行相当复杂的操作。另一方面,如果您只需要一把螺丝刀,独立的螺丝刀可能会更方便。 Stream API 包括许多东西(如 distinct
、sorted
、原始操作等),否则将需要您编写几行代码并引入中间 variables/data 结构和无聊的循环绘图程序员的注意力来自于实际的算法。有时使用 Stream API 甚至可以提高顺序代码的性能。例如,考虑一些旧的 API:
class Group {
private Map<String, User> users;
public List<User> getUsers() {
return new ArrayList<>(users.values());
}
}
这里我们要return该组的所有用户。 API 设计师决定 return 一个 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 中从未见过如此简单的并行编程方法。
当然,这总是要视情况而定。举个例子:
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(…)
之类的方法添加到 Collection
API 而不是坚持每个人都使用 stream().forEach(…)
。 Collection.forEach(…)
并不是 Collection.stream().forEach(…)
的简单简写,事实上,它已经定义在更抽象的 Iterable
接口上,甚至没有 stream()
方法。
顺便说一句,你应该明白Collections.binarySearch
和Stream.filter/findAny
之间的区别。前者要求集合 sorted 如果满足该先决条件,可能是更好的选择。但是如果集合没有排序,一个简单的线性搜索比只使用二分搜索排序更有效,更不用说二分搜索只在 List
s 时工作,而 [=48] =] 适用于支持各种源集合的任何流。