我有一种方法必须从某些(可能很大)Set<K> keysToRemove
中删除(小)Map<K,V> from
中列出的任何元素。但是removeAll()
不能这样做,因为我需要返回实际上已删除的所有键,因为映射可能包含或可能不包含需要删除的键。
老式代码很简单:
public Set<K> removeEntries(Map<K, V> from) {
Set<K> fromKeys = from.keySet();
Set<K> removedKeys = new HashSet<>();
for (K keyToRemove : keysToRemove) {
if (fromKeys.contains(keyToRemove)) {
fromKeys.remove(keyToRemove);
removedKeys.add(keyToRemove);
}
}
return removedKeys;
}
相同,使用流编写:
Set<K> fromKeys = from.keySet();
return keysToRemove.stream()
.filter(fromKeys::contains)
.map(k -> {
fromKeys.remove(k);
return k;
})
.collect(Collectors.toSet());
我发现这更加简洁,但是我也发现lambda太笨拙了。
有人建议如何以较少笨拙的方式获得相同的结果吗?
答案 0 :(得分:23)
“老式代码”应该是
UIImagePickerControllerDelegate
由于您说public Set<K> removeEntries(Map<K, ?> from) {
Set<K> fromKeys = from.keySet(), removedKeys = new HashSet<>(keysToRemove);
removedKeys.retainAll(fromKeys);
fromKeys.removeAll(removedKeys);
return removedKeys;
}
很小,因此复制开销可能无关紧要。否则,请使用循环,但不要进行两次哈希查找:
keysToRemove
您可以表达与流相同的逻辑
public Set<K> removeEntries(Map<K, ?> from) {
Set<K> fromKeys = from.keySet();
Set<K> removedKeys = new HashSet<>();
for(K keyToRemove : keysToRemove)
if(fromKeys.remove(keyToRemove)) removedKeys.add(keyToRemove);
return removedKeys;
}
但是由于这是一个有状态的过滤器,因此不建议使用。一个更干净的变体是
public Set<K> removeEntries(Map<K, ?> from) {
return keysToRemove.stream()
.filter(from.keySet()::remove)
.collect(Collectors.toSet());
}
,如果要最大程度地使用“流式”方法,可以用public Set<K> removeEntries(Map<K, ?> from) {
Set<K> result = keysToRemove.stream()
.filter(from.keySet()::contains)
.collect(Collectors.toSet());
from.keySet().removeAll(result);
return result;
}
代替from.keySet().removeAll(result);
,因为在较大的地图上迭代时,from.keySet().removeIf(result::contains)
昂贵,或者{{1} },虽然没有这个缺点,但仍然比result.forEach(from.keySet()::remove)
更具可读性。
总而言之,“老式代码”比这要好得多。
答案 1 :(得分:13)
更简洁的解决方案,但在filter
调用中仍然具有不必要的副作用:
Set<K> removedKeys =
keysToRemove.stream()
.filter(fromKeys::remove)
.collect(Collectors.toSet());
如果true
包含指定的元素, Set.remove
已经返回set
。
最后,我可能会坚持使用“老式代码”。
答案 2 :(得分:5)
我不会为此使用Streams。我会利用retainAll:
public Set<K> removeEntries(Map<K, V> from) {
Set<K> matchingKeys = new HashSet<>(from.keySet());
matchingKeys.retainAll(keysToRemove);
from.keySet().removeAll(matchingKeys);
return matchingKeys;
}
答案 3 :(得分:4)
您可以使用流和removeAll
Set<K> fromKeys = from.keySet();
Set<K> removedKeys = keysToRemove.stream()
.filter(fromKeys::contains)
.collect(Collectors.toSet());
fromKeys.removeAll(removedKeys);
return removedKeys;
答案 4 :(得分:4)
您可以使用此:
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.collect(Collectors.toSet());
removedKeys.forEach(from::remove);
类似于 Oleksandr 的回答,但避免了副作用。但是,如果您要寻找性能,我会坚持使用该答案。
或者,您可以使用Stream.peek()
进行去除,但要注意其他副作用(请参见注释)。所以我不建议这样做。
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.peek(from::remove)
.collect(Collectors.toSet());
答案 5 :(得分:3)
要为这些方法添加另一种变体,还可以对键进行分区,并将所需的Set
返回为:
public Set<K> removeEntries(Map<K, ?> from) {
Map<Boolean, Set<K>> partitioned = keysToRemove.stream()
.collect(Collectors.partitioningBy(k -> from.keySet().remove(k),
Collectors.toSet()));
return partitioned.get(Boolean.TRUE);
}