与 flatMap 相比,为什么 mapMulti 需要类型信息
Why does mapMulti need type information in comparison to flatMap
我想使用 mapMulti
而不是 flatMap
并重构了以下代码:
// using flatMap (version 1) => returns Set<Item>
var items = users.stream()
.flatMap(u -> u.getItems().stream())
.collect(Collectors.toSet());
进入此(版本 2):
// using mapMulti (version 2) => returns Set<Item>
var items = users.stream()
.<Item>mapMulti((u, consumer) -> u.getItems().forEach(consumer))
.collect(Collectors.toSet());
两者return相同的元素。但是,我怀疑我是否真的应该用更冗长的 mapMulti
代码替换我的所有 flatMap
。为什么我需要在mapMuli(.<Item>mapMulti
)之前添加类型信息。如果我不包含类型信息,它将 return 一个 Set<Object>
。 (如何) 我可以简化 mapMulti
?
请注意,使用 flatMap
时推断结果流类型所需的类型推断与使用 mapMulti
时的类型推断有很大不同。
当您使用 flatMap
时,结果流的类型与 lambda 主体的 return 类型相同。这是一件特殊的事情,编译器被设计为从中推断类型变量(即编译器“知道”它)。
但是,在 mapMulti
的情况下,您可能想要的结果流的类型只能从您对 consumer
lambda 参数所做的事情中推断出来。 假设, 编译器可以这样设计,例如,如果您说 consumer.accept(1)
,那么它会查看您传递给 accept
的内容,并且看到你想要一个 Stream<Integer>
,在 getItems().forEach(consumer)
的情况下,类型 Item
可能来自的唯一地方是 return 类型的 getItems
, 所以它需要去看看那个。
你基本上是在要求编译器根据其中任意表达式的类型推断 lambda 的参数类型。编译器根本就没有被设计成这样做。
除了添加 <Item>
前缀之外,还有其他(更长)的方法可以让它将 Stream<Item>
推断为 mapMulti
的 return 类型:
显式输入 lambda:
var items = users.stream()
.mapMulti((User u, Consumer<Item> consumer) -> u.getItems().forEach(consumer))
.collect(Collectors.toSet());
添加临时流变量:
// By looking at the type of itemStream, the compiler can figure out that mapMulti should return a Stream<Item>
Stream<Item> itemStream = users.stream()
.mapMulti((u, consumer) -> u.getItems().forEach(consumer));
var items = itemStream.collect(Collectors.toSet());
我不知道这是否更“简化”,但我认为如果使用方法引用会更整洁:
var items = users.stream()
.map(User::getItems)
.<Item>mapMulti(Iterable::forEach)
.collect(Collectors.toSet());
我想使用 mapMulti
而不是 flatMap
并重构了以下代码:
// using flatMap (version 1) => returns Set<Item>
var items = users.stream()
.flatMap(u -> u.getItems().stream())
.collect(Collectors.toSet());
进入此(版本 2):
// using mapMulti (version 2) => returns Set<Item>
var items = users.stream()
.<Item>mapMulti((u, consumer) -> u.getItems().forEach(consumer))
.collect(Collectors.toSet());
两者return相同的元素。但是,我怀疑我是否真的应该用更冗长的 mapMulti
代码替换我的所有 flatMap
。为什么我需要在mapMuli(.<Item>mapMulti
)之前添加类型信息。如果我不包含类型信息,它将 return 一个 Set<Object>
。 (如何) 我可以简化 mapMulti
?
请注意,使用 flatMap
时推断结果流类型所需的类型推断与使用 mapMulti
时的类型推断有很大不同。
当您使用 flatMap
时,结果流的类型与 lambda 主体的 return 类型相同。这是一件特殊的事情,编译器被设计为从中推断类型变量(即编译器“知道”它)。
但是,在 mapMulti
的情况下,您可能想要的结果流的类型只能从您对 consumer
lambda 参数所做的事情中推断出来。 假设, 编译器可以这样设计,例如,如果您说 consumer.accept(1)
,那么它会查看您传递给 accept
的内容,并且看到你想要一个 Stream<Integer>
,在 getItems().forEach(consumer)
的情况下,类型 Item
可能来自的唯一地方是 return 类型的 getItems
, 所以它需要去看看那个。
你基本上是在要求编译器根据其中任意表达式的类型推断 lambda 的参数类型。编译器根本就没有被设计成这样做。
除了添加 <Item>
前缀之外,还有其他(更长)的方法可以让它将 Stream<Item>
推断为 mapMulti
的 return 类型:
显式输入 lambda:
var items = users.stream()
.mapMulti((User u, Consumer<Item> consumer) -> u.getItems().forEach(consumer))
.collect(Collectors.toSet());
添加临时流变量:
// By looking at the type of itemStream, the compiler can figure out that mapMulti should return a Stream<Item>
Stream<Item> itemStream = users.stream()
.mapMulti((u, consumer) -> u.getItems().forEach(consumer));
var items = itemStream.collect(Collectors.toSet());
我不知道这是否更“简化”,但我认为如果使用方法引用会更整洁:
var items = users.stream()
.map(User::getItems)
.<Item>mapMulti(Iterable::forEach)
.collect(Collectors.toSet());