从Java Set获取*任何*值的好方法是什么?

96

有一个简单的 Set<T>,如何以一种好的方式(快速、代码行数少)获取Set中的任何值?

对于List来说,很容易:

List<T> things = ...;
return things.get(0);

然而,在Set中,没有.get(...)方法,因为Set不是有序的。

3个回答

135

Set<T> 是一个 Iterable<T>,因此迭代到第一个元素可行:

Set<T> things = ...;
return things.iterator().next();

Guava有一种方法可以做到这一点,不过上面的代码片段可能更好


1
实际上,当您不想要默认值时,我们希望您继续使用 iterator().next()。这就是为什么我们没有 getFirst(Iterable<E>),只有 getFirst(Iterable<E>, E default) 的原因。 - Louis Wasserman
@LouisWasserman: 不一定。如果集合非常大,并且其大部分项目已被删除,则需要循环遍历直到找到非空存储桶。https://dev59.com/aE7Sa4cB1Zd3GeqP3nNt#3046838 - SLaks
1
真实情况下很少见,但在实践中确实存在。 (此外,可以通过使用“LinkedHashSet”轻松解决。) - Louis Wasserman
2
(另外,我声明没有更快的解决方案存在。) - Louis Wasserman

22

有了流(Stream)的存在,你也可以这样操作,但是需要使用 java.util.Optional 类。 Optional 是一个包装类,用于表示一个元素或明确地没有元素(避免空指针异常)。

//returns an Optional.
Optional <T> optT = set.stream().findAny();

//Optional.isPresent() yields false, if set was empty, avoiding NullpointerException
if(optT.isPresent()){
    //Optional.get() returns the actual element
    return optT.get();
}

编辑: 由于我自己经常使用Optional:有一种方法可以访问元素或获取默认值,如果它不存在:
optT.orElse(other) 返回元素或者,如果不存在,则返回otherother 可以是null,顺便说一下。


2
我想知道这个比 set.iterator().next(); 快还是慢。 - Gustavo
如果集合为空,set.iterator().next()会抛出NoSuchElementException异常,使用Stream的findAny更安全,因为它会在集合为空时给你一个空的Optional。除非性能真的很重要,否则我会选择更安全的方式。这取决于你需要什么。 - lost_trekkie

4
从Set或Collection中获取任何元素可能看起来像是一个不常见的需求 - 如果不是随意或折衷的话 - 但是,当需要在Map中计算键或值对象的统计信息并必须初始化最小/最大值时,这是非常常见的。从Set/Collection(由Map.keySet()或Map.values()返回)中获取任何元素将用于在更新每个元素之前初始化最小/最大值。
那么,当面临这个问题并且同时试图保持内存和执行时间的小和代码清晰时,有哪些选择呢?
通常你会得到以下建议:“将Set转换为ArrayList并获取第一个元素”。太好了!又多了一个包含数百万项的数组,并且需要额外的处理周期来从Set中检索对象,分配数组并填充它:
HashMap<K,V> map;
List<K> list = new ArrayList<V>(map.keySet()); // min/max of keys
min = max = list.get(0).some_property(); // initialisation step
for(i=list.size();i-->1;){
 if( min > list.get(i).some_property() ){ ... }
 ...
}

或者可以使用迭代器进行循环,使用一个标志来表示min/max需要初始化,并使用条件语句来检查该标志是否在循环的所有迭代中设置。这意味着需要进行大量的条件检查。

boolean flag = true;
Iterator it = map.keySet().iterator();
while( it.hasNext() ){
  if( flag ){
    // initialisation step
    min = max = it.next().some_property();
    flag = false;
  } else {
    if( min > list.get(i).some_property() ){ min = list.get(i).some_property() }
  ...
  }
}

或者在循环外进行初始化:

HashMap<K,V> map;
Iterator it = map.keySet().iterator();
K akey;
if( it.hasNext() ){
  // initialisation step:
  akey = it.next();
  min = max = akey.value();
  do {
    if( min > list.get(i).some_property() ){ min = akey.some_property() }
  } while( it.hasNext() && ((akey=it.next())!=null) );
}

但是,每次需要min/max时,程序员(以及JVM代表的迭代器设置)是否真的值得进行所有这些操作呢?
一个符合Java标准的老手可能会建议:“将您的Map包装在一个类中,在put或删除时跟踪最小和最大值!”
还有另一种情况,根据我的经验,需要从Map中获取任何项。当映射包含具有共同属性的对象 - 对于该映射中的所有对象都相同 - 并且您需要读取该属性时,就会出现这种情况。例如,假设有一个保持具有相同维数的同一直方图的Map。给定这样一个Map,您可能需要知道Map中任何一个Histobin的维数,以便创建具有相同维数的另一个Histobin。我将跳过符合Java标准的人对此情况的建议。
如果获取任何元素所需的所有麻烦都导致内存和CPU周期增加不明显,那么获取难以获取的任何元素所需编写的所有代码又如何呢?
我们需要任何元素。给我们吧!

1
这不是对问题的回答,但它确实阐述了为什么问题是有意义的。 - Jim Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接