从Set中删除“第一个”对象
在某些情况下,我需要逐出Java Set
最旧的元素。 该集合使用LinkedHashSet
实现,这使得这很简单:只需删除set的迭代器返回的第一个元素:
Set mySet = new LinkedHashSet(); // do stuff... if (mySet.size() >= MAX_SET_SIZE) { Iterator iter = mySet.iterator(); iter.next(); iter.remove(); }
这很难看:如果我使用的是SortedSet
我可以用1行做3行(由于其他原因,这里不能选择SortedSet
):
if (/*stuff*/) { mySet.remove(mySet.first()); }
那么有没有更简洁的方法, 没有:
- 更改
Set
实现,或 - 写一个静态的实用方法?
利用番石榴的任何解决方案都很好。
我完全清楚套装没有固有的顺序。 我要求删除迭代顺序定义的第一个条目。
LinkedHashSet是LinkedHashMap的包装器,它支持简单的“删除最旧”策略。 要将它用作Set,你可以做到
Set set = Collections.newSetFromMap(new LinkedHashMap(){ protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; } });
if (!mySet.isEmpty()) mySet.remove(mySet.iterator.next());
似乎少于3行。
如果您的集合由多个线程共享,则必须同步它。
如果您确实需要在代码中的多个位置执行此操作,请编写静态方法。
提出的其他解决方案通常较慢,因为它们暗示调用Set.remove(Object)
方法而不是Iterator.remove()
方法。
@Nullable public static T removeFirst(Collection extends T> c) { Iterator extends T> it = c.iterator(); if (!it.hasNext()) { return null; } T removed = it.next(); it.remove(); return removed; }
用番石榴:
if (!set.isEmpty() && set.size() >= MAX_SET_SIZE) { set.remove(Iterables.get(set, 0)); }
我还建议一种替代方法。 是的,它改变了实现,但没有大幅改变:扩展LinkedHashSet
并在add
方法中具有该条件:
public LimitedLinkedHashSet extends LinkedHashSet { public void add(E element) { super.add(element); // your 5-line logic from above or my solution with guava } }
它仍然是5行,但它使用它的代码是不可见的。 由于这实际上是集合的特定行为,因此将其置于集合中是合乎逻辑的。
我认为你这样做的方式很好。 这是你经常做的事情,值得找一个更短的方法吗? 你可以像这样用Guava做同样的事情:
Iterables.removeIf(Iterables.limit(mySet, 1), Predicates.alwaysTrue());
这增加了包装集合及其迭代器以限制然后再调用alwaysTrue()
谓词的小开销……但对我来说似乎并不值得。
编辑:要在答案中添加我在评论中所说的内容,您可以创建一个SetMultimap
,自动限制每个键可以拥有的值的数量,如下所示:
SetMultimap multimap = Multimaps.newSetMultimap(map, new Supplier>() { public Set get() { return Sets.newSetFromMap(new LinkedHashMap() { @Override protected boolean removeEldestEntry(Entry eldestEntry) { return size() > MAX_SIZE; } }); } });
快速而肮脏的单行解决方案: mySet.remove(mySet.toArray(new Foo[mySet.size()])[0])
;)
但是,我仍然会选择迭代器解决方案,因为这样会更具可读性,也应该更快。
编辑:我会选择Mike Samuel的解决方案。 🙂