我的情况是我在开发项目中有Player
个对象,而任务只是测量距离并返回低于某个阈值的结果。当然,我想以最简洁的方式使用流。
目前,我有一个映射流的解决方案,然后通过迭代器进行过滤:
Stream<Player> str = /* source of my player stream I'm filtering */;
Map<Player, Double> dists = str.collect(Collectors.toMap(...)); //mapping function
Iterator<Map.Entry<Player, Double>> itr = map.entrySet().iterator();
while (itr.hasNext()) {
if (itr.next().getValue() <= radiusSquared) {
itr.remove();
}
}
然而,我想要实现的是在对流进行操作时执行此过滤的事情,其中说“如果此谓词失败,则不收集&#34;”以尝试和保存第二次迭代。另外,我不想两次计算距离,所以通过映射函数进行过滤,然后重新映射不是一个合理的解决方案。
我想到的唯一真正可行的解决方案是映射到Pair<A, B>
,但如果对某种形式的二进制流有原生支持,那就更好了。< / p>
java的流API中是否有本机支持?
答案 0 :(得分:5)
之后过滤Map
并不像看起来那么糟糕,请记住,迭代Map
并不意味着执行查找(例如散列)的成本相同。
但不是
Iterator<Map.Entry<Player, Double>> itr = map.entrySet().iterator();
while (itr.hasNext()) {
if (itr.next().getValue() <= radiusSquared) {
itr.remove();
}
}
你可以简单地使用
map.values().removeIf(value -> value <= radiusSquared);
即使您坚持将其作为collect
操作的一部分,也可以将其作为后缀操作:
Map<Player, Double> dists = str.collect(
Collectors.collectingAndThen(Collectors.toMap(p->p, p->calculate(p)),
map -> { map.values().removeIf(value -> value <= radiusSquared); return map; }));
可以首先避免put
个不需要的条目,但这意味着手动回溯现有toMap
收集器所做的事情:
Map<Player, Double> dists = str.collect(
HashMap::new,
(m, p) -> { double value=calculate(p); if(value > radiusSquared) m.put(p, value); },
Map::putAll);
答案 1 :(得分:2)
请注意,旧式迭代器循环可以使用Collection.removeIf
在Java-8中重写:
map.values().removeIf(dist -> dist <= radiusSquared);
所以它实际上并不那么糟糕。不要忘记keySet()
和values()
是可修改的。
如果你想使用单一管道解决这个问题(例如,大部分条目都要删除),那么对你来说就是坏消息。似乎当前的Stream API不允许您在没有明确使用具有对语义的类的情况下执行此操作。创建Map.Entry
实例非常自然,尽管现有选项是AbstractMap.SimpleEntry
,其名称相当长且令人不快:
str.map(player -> new AbstractMap.SimpleEntry(player, getDistance(player)))
.filter(entry -> entry.getValue() > radiusSquared)
.toMap(Entry::getKey, Entry::getValue);
请注意,在Java-9中可能会有Map.entry()
静态方法,因此您可以使用Map.entry(player, getDistance(player))
。有关详细信息,请参阅JEP-269。
像往常一样,我的StreamEx库有一些语法糖来以更清洁的方式解决这个问题:
StreamEx.of(str).mapToEntry(player -> getDistance(player))
.filterValues(dist -> dist > radiusSquared)
.toMap();
关于评论:是的,toMap()
收集器使用逐个插入,但不要担心:要映射的批量插入很少提高速度。您甚至无法预先确定哈希表的大小(如果您的地图是基于哈希的),因为您不太了解要插入的元素。您可能希望使用相同的密钥插入数百万个对象:为百万条目分配哈希表只是为了发现插入后只有一个条目太浪费。
答案 2 :(得分:0)
如果您的目标只是进行一次迭代并且只计算一次距离,那么您可以这样做:
Stream<Player> str = /* source of my player stream I'm filtering */;
Map<Player, Double> dists = new HashMap<>();
str.forEach(p -> {
double distance = /* calculate distance */;
if (distance <= radiusSquared) {
dists.put(p, distance);
}
});
不再有收藏家,但这是否重要?