【问题标题】:Getting an element from a Set从 Set 中获取元素
【发布时间】:2011-11-09 03:30:32
【问题描述】:

为什么Set 不提供一个操作来获取一个等于另一个元素的元素?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

我可以问Set 是否包含等于bar 的元素,那为什么我不能得到那个元素呢? :(

为了澄清,equals 方法被覆盖,但它只检查其中一个字段,而不是全部。所以两个被认为相等的Foo 对象实际上可以有不同的值,这就是为什么我不能只使用foo

【问题讨论】:

  • 这篇文章已经被广泛讨论,并且已经提出了好的答案。但是,如果您只是在寻找有序集合,只需使用 SortedSet 及其基于映射的实现(例如,TreeSet 允许访问 first())。
  • 我也想念那个方法,对于你上面描述的完全相同的情况。 Objective-C (NSSet) 有这样的方法。它被称为member,它返回集合中将“等于”与member 方法的参数进行比较的对象(当然,它可能是一个不同的对象,也有不同的属性,equal 可能无法检查)。

标签: java collections set equals


【解决方案1】:

要回答精确的问题“为什么Set 不提供一个操作来获取一个等于另一个元素的元素?”,答案是:因为集合框架的设计者是不是很有前瞻性。他们没有预料到您非常合理的用例,天真地尝试“建模数学集合抽象”(来自 javadoc),只是忘记添加有用的 get() 方法。

现在到隐含的问题“how do you get the element then”:我认为最好的解决方案是使用Map&lt;E,E&gt;而不是Set&lt;E&gt;,将元素映射到它们自己.这样,您可以高效地从“集合”中检索元素,因为Map 的 get() 方法将使用高效的哈希表或树算法来查找元素。如果您愿意,您可以编写自己的 Set 实现,提供额外的 get() 方法,封装 Map

以下答案在我看来是坏的或错误的:

“您不需要获取元素,因为您已经有一个相等的对象”:断言是错误的,正如您在问题中已经表明的那样。相等的两个对象仍然可以具有与对象相等性无关的不同状态。目标是访问 Set 中包含的元素的这种状态,而不是用作“查询”的对象的状态。

“你别无选择,只能使用迭代器”:这是对集合的线性搜索,这对于大型集合来说是完全低效的(具有讽刺意味的是,在内部 Set 被组织为可以查询的哈希映射或树有效率的)。不要这样做!通过使用这种方法,我在实际系统中看到了严重的性能问题。在我看来,缺少的get() 方法的可怕之处并不在于解决它有点麻烦,而是大多数程序员会使用线性搜索方法而不考虑其含义。

【讨论】:

  • 嗯。重写 equals 的实现以使不相等的对象“相等”是这里的问题。要求一个方法说“让我得到与这个对象相同的对象”,然后期望返回一个不同的对象,这似乎很疯狂,而且很容易引起维护问题。正如其他人所建议的那样,使用地图可以解决所有这些问题:它使您所做的事情不言自明。很容易理解,两个不相等的对象在一个 map 中可能有相同的 key,拥有相同的 key 会显示它们之间的关系。
  • 强词,@David Ogren。嗯?疯狂的?但是在您的评论中,您使用“相同”和“相等”这两个词,就好像它们的意思相同。他们不。具体来说,在 Java 中,标识由“==”运算符表示,相等由 equals() 方法表示。如果它们的意思相同,则根本不需要 equals() 方法。在其他语言中,这当然可以不同。例如,在 Groovy 中,标识是 is() 方法,相等是“==”。有趣,不是吗?
  • 您对我在本应使用等效一词时使用相同一词的批评非常有效。但是在一个对象上定义 equals 以便 Foo 和 Bar “相等”但不足以让他等效地使用它们,这将在功能和可读性/可维护性方面产生各种问题。这个问题与 Set 只是潜在问题的冰山一角。例如,相等的对象必须具有相等的哈希码。所以他会有潜在的哈希冲突。对象调用 .get(foo) 来获取 foo 以外的东西是不是很疯狂?
  • 可能值得注意的是,例如,HashSet 被实现为 HashMap 的包装器(将键映射到虚拟值)。因此,显式使用 HashMap 而不是 HashSet 不会导致内存使用开销。
  • @user686249 我觉得这已经演变成一场学术辩论。我承认我在反对压倒性平等方面可能有些过火了。特别是在像你这样的用途中。但是,对于调用这个方法get()的想法,我还是有异议的。在您的示例中,我会对 customerSet.get(thisCustomer) 感到非常困惑。 (然而,正如许多答案所建议的那样,使用 canonicalCustomerMap.get(此客户)就可以了。我也可以使用更明确命名的方法(例如 NSSet 上的 Objective-C 的成员方法)。
【解决方案2】:

如果元素相等,那么获取元素就没有意义了。 Map 更适合这个用例。


如果你仍然想找到元素,你别无选择,只能使用迭代器:

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() { 
        return string.hashCode(); 
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}

【讨论】:

  • 获取元素绝对是有意义的。如果您希望在元素已添加到 Set 后更新某些元素的值怎么办?例如,当 .equals() 不使用 OP 指定的所有字段时。一个效率较低的解决方案是删除元素并重新添加它并更新其值。
  • 我仍然认为Map 更适合(在这种情况下为Map&lt;Foo, Foo&gt;。)
  • @dacwe,我来到这里是因为我开始寻找一种方法来避免这种情况!一个同时作为键和对应值的对象正是集合的全部意义所在。就我而言,我想从一组键(字符串)中获取一些复杂的对象。这个字符串被封装(并且唯一)到被映射的对象。事实上,整个对象围绕着这个键“旋转”。此外,调用者知道所说的字符串,但不知道对象本身;这正是它想要通过密钥检索它的原因。当然,我现在正在使用地图,但它仍然是奇怪的行为。
  • @KyleM 我理解用例,但我想强调不触及作为 hashCode/equals 一部分的属性的重要性。来自 Set Javadoc:“注意:如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是集合中的元素。” -- 我建议这些对象是不可变的,或者至少具有不可变的关键属性。
  • 我同意您可以使用 Map&lt;Foo, Foo&gt; 作为替代品,缺点是映射始终必须至少存储一个键和一个值(并且为了性能,它还应该存储散列),而一个集合可以仅仅存储值(可能还有哈希以提高性能)。因此,一个好的集合实现可以与Map&lt;Foo, Foo&gt; 一样快,但使用的内存最多可减少 50%。在 Java 的情况下,这无关紧要,因为 HashSet 无论如何在内部都是基于 HashMap 的。
【解决方案3】:

如果你有一个相等的对象,为什么你需要集合中的那个?如果它仅通过一个键“相等”,Map 将是更好的选择。

不管怎样,下面的就可以了:

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

使用 Java 8,这可以成为一个单行:

return all.stream().filter(sample::equals).findAny().orElse(null);

【讨论】:

  • 我更喜欢这个答案,我只是避免使用两个 return 语句,因为这不利于 OOP 并且它使循环复杂度值更高。
  • @Leo 谢谢,但单出口范式不反对 OOP,并且对于比 Fortran 或 COBOL 更现代的语言大多无效,另请参阅 softwareengineering.stackexchange.com/questions/118703/…
  • 使用 Map 而不是 Set 似乎是一个更好的选择:迭代 Set 的元素比从 Map 中获取单个值要多。 (O(N) 与 O(1))
  • @JamieFlournoy 是的,如果您必须多次检查同一组的不同元素,那就更好了。不适合单次使用,因为它需要更多的努力首先构建地图。
【解决方案4】:

遗憾的是,Java 中的 Default Set 并非旨在提供“获取”操作,正如 jschreiner 准确解释的那样。

使用迭代器查找感兴趣的元素(dacwe 建议)或删除元素并重新添加并更新其值(KyleM 建议)的解决方案可以工作,但可以非常低效。

正如David Ogren 正确指出的那样,重写equals 的实现以使不相等的对象是“相等的”,这很容易导致维护问题。

恕我直言,使用 Map 作为显式替换(正如许多人所建议的那样)会使代码不那么优雅。

如果目标是访问集合中包含的元素的原始实例(希望我正确理解了您的用例),这是另一种可能的解决方案。


在使用 Java 开发客户端-服务器视频游戏时,我个人也有同样的需求。在我的例子中,每个客户端都有存储在服务器中的组件副本,问题是每当客户端需要修改服务器的对象时。

通过互联网传递一个对象意味着客户端拥有该对象的不同实例。为了将这个“复制”的实例与原始实例相匹配,我决定使用 Java UUID。

所以我创建了一个抽象类 UniqueItem,它会自动为其子类的每个实例提供一个随机的唯一 ID。

此 UUID 在客户端和服务器实例之间共享,因此只需使用 Map 即可轻松匹配它们。

但是,在类似的用例中直接使用 Map 仍然不优雅。有人可能会争辩说,使用 Map 可能更难以维护和处理。

出于这些原因,我实现了一个名为 MagicSet 的库,它使地图的使用对开发人员“透明”。

https://github.com/ricpacca/magicset


与原始的 Java HashSet 一样,MagicHashSet(它是库中提供的 MagicSet 的实现之一)使用支持 HashMap,但它不是将元素作为键,将虚拟值作为值,而是使用元素作为键,元素本身作为值。与普通的 HashSet 相比,这不会导致内存使用的开销。

此外,MagicSet 可以完全像 Set 一样使用,但可以使用更多方法提供额外的功能,例如 getFromId()、popFromId()、removeFromId() 等。

使用它的唯一要求是您想要存储在 MagicSet 中的任何元素都需要扩展抽象类 UniqueItem。


这是一个代码示例,假设从 MagicSet 中检索城市的原始实例,假设该城市的另一个实例具有相同的 UUID(甚至只是其 UUID)。

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}

【讨论】:

    【解决方案5】:

    如果您的集合实际上是NavigableSet&lt;Foo&gt;(例如TreeSet)和Foo implements Comparable&lt;Foo&gt;,则可以使用

    Foo bar = set.floor(foo); // or .ceiling
    if (foo.equals(bar)) {
        // use bar…
    }
    

    (感谢@eliran-malka 的评论提示。)

    【讨论】:

    • 如果我不介意有人在阅读我的代码时最初认为我完全疯了,这将是一个很好的解决方案。
    • 不幸的是,在使用 TreeSets(引擎盖下的 TreeMaps)时,所有基本操作都有 log(N) 时间复杂度:c
    【解决方案6】:

    使用 Java 8,您可以:

    Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();
    

    但要小心,.get() 会抛出 NoSuchElementException,或者您可以操作 Optional 项。

    【讨论】:

    • item-&gt;item.equals(theItemYouAreLookingFor)可以简写为theItemYouAreLookingFor::equals
    【解决方案7】:

    将set转换为list,然后使用list的get方法

    Set<Foo> set = ...;
    List<Foo> list = new ArrayList<Foo>(set);
    Foo obj = list.get(0);
    

    【讨论】:

    • 我不明白。这将检索集合的 任意 对象。不是 对象。
    • 为什么会得到这么多赞?在这个答案中,您将集合转换为列表并检索第一个对象,而不是“foo”
    • 过度复杂化并使其可能非常低效(消耗大量内存,速度慢)。正确的做法是使用地图。
    【解决方案8】:

    为什么:

    似乎 Set 在提供比较方法方面发挥了有用的作用。它旨在不存储重复元素。

    由于这种意图/设计,如果要 get() 对存储对象的引用,然后对其进行变异,则 Set 的设计意图可能会受阻并可能导致意外行为。

    来自JavaDocs

    如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响相等比较的方式更改,而对象是集合中的一个元素,则不指定集合的​​行为。

    如何:

    现在已经引入了 Streams,可以执行以下操作

    mySet.stream()
    .filter(object -> object.property.equals(myProperty))
    .findFirst().get();
    

    【讨论】:

    • 支持 Stream API。不过,不推荐使用get(),请改用orElse()ifPresent()
    【解决方案9】:
    Object objectToGet = ...
    Map<Object, Object> map = new HashMap<Object, Object>(set.size());
    for (Object o : set) {
        map.put(o, o);
    }
    Object objectFromSet = map.get(objectToGet);
    

    如果你只做一次获取,这将不会很好执行,因为你会遍历所有元素,但是当在一个大集合上执行多次检索时,你会注意到差异。

    【讨论】:

      【解决方案10】:

      你可以使用迭代器类

      import java.util.Iterator;
      import java.util.HashSet;
      
      public class MyClass {
       public static void main(String[ ] args) {
       HashSet<String> animals = new HashSet<String>();
      animals.add("fox");
      animals.add("cat");
      animals.add("dog");
      animals.add("rabbit");
      
      Iterator<String> it = animals.iterator();
      while(it.hasNext()) {
        String value = it.next();
        System.out.println(value);   
       }
       }
      }
      

      【讨论】:

        【解决方案11】:

        我知道,这个问题很久以前就有人问过了,但是如果有人感兴趣,这里是我的解决方案 - 由 HashMap 支持的自定义集类:

        http://pastebin.com/Qv6S91n9

        您可以轻松实现所有其他 Set 方法。

        【讨论】:

        • 最好包含示例而不是仅仅链接到一个。
        • @Lukas Z. 您的add() 不能确保元素是否存在于“集合”中因此根据您的实现允许重复!这明显违反了集合原则。
        【解决方案12】:

        去过那里吗!如果您使用 Guava 将其转换为地图的快速方法是:

        Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
        

        【讨论】:

          【解决方案13】:

          如果您想要 HashSet 中的第 n 个元素,您可以使用以下解决方案, 这里我在 HashSet 中添加了 ModelClass 的对象。

          ModelClass m1 = null;
          int nth=scanner.nextInt();
          for(int index=0;index<hashset1.size();index++){
              m1 = (ModelClass) itr.next();
              if(nth == index) {
                  System.out.println(m1);
                  break;
              }
          }
          

          【讨论】:

            【解决方案14】:

            如果您查看java.util.HashSet 实现的前几行,您会看到:

            public class HashSet<E>
                ....
                private transient HashMap<E,Object> map;
            

            所以HashSet 无论如何都使用HashMap,这意味着如果您直接使用HashMap 并使用与键和值相同的值,您将获得您想要的效果并节省一些内存。

            【讨论】:

              【解决方案15】:

              看来要使用的正确对象是来自 guava 的 Interner

              为其他不可变对象提供与 String.intern() 等效的行为 类型。常见的实现可从Interners 获得 类。

              它还有一些非常有趣的杠杆,例如 concurrencyLevel 或使用的引用类型(可能值得注意的是,它没有提供我认为比 WeakInterner 更有用的 SoftInterner)。

              【讨论】:

                【解决方案16】:

                因为 Set 的任何特定实现可能是也可能不是random access

                你总是可以得到一个iterator 并单步执行Set,一旦你找到相等的元素,使用迭代器的next() 方法返回你想要的结果。无论实施如何,这都有效。如果实现不是随机访问(想象一个链表支持的 Set),接口中的 get(E element) 方法将具有欺骗性,因为它必须迭代集合以找到要返回的元素,而 get(E element) 将似乎暗示这是必要的,即 Set 可以直接跳转到要获取的元素。

                contains() 可能需要也可能不需要做同样的事情,当然,这取决于实现,但这个名字似乎不会引起同样的误解。

                【讨论】:

                • get() 方法会做的任何事情都已经由 contains() 方法完成了。如果不获取包含的对象并调用其 .equals() 方法,则无法实现 contains() 。 API 设计者似乎对将 get() 添加到 java.util.List 没有任何疑虑,尽管它在某些实现中会很慢。
                • 我认为这不是真的。两个对象可以通过 equals 相等,但不能通过 == 相等。如果你有对象 A 和一个包含对象 B 的集合 S 和 A.equals(B) 但 A != B 并且你想获得对 B 的引用,你可以调用 S.get(A) 来获得对B,假设您有一个具有 List 的 get 方法语义的 get 方法,这与检查是否 S.contains(A) (它会)是不同的用例。这甚至不是一个罕见的集合用例。
                【解决方案17】:

                是的,使用HashMap ...但以一种特殊的方式:我预见到尝试将HashMap用作伪Set的陷阱是@987654324的“实际”元素之间可能存在混淆@ 和“候选”元素,即用于测试equal 元素是否已经存在的元素。这远非万无一失,但可以让你远离陷阱:

                class SelfMappingHashMap<V> extends HashMap<V, V>{
                    @Override
                    public String toString(){
                        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
                        return keySet().toString();
                    }
                
                    @Override
                    public V get( Object key ){
                        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
                    }
                
                    @Override
                    public V put( V key, V value ){
                       // thorny issue here: if you were indavertently to `put`
                       // a "candidate instance" with the element already in the `Map/Set`: 
                       // these will obviously be considered equivalent 
                       assert key.equals( value );
                       return super.put( key, value );
                    }
                
                    public V tryToGetRealFromCandidate( V key ){
                        return super.get(key);
                    }
                }
                

                然后这样做:

                SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
                ...
                SomeClass candidate = new SomeClass();
                if( selfMap.contains( candidate ) ){
                    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
                    ...
                    realThing.useInSomeWay()...
                }
                

                但是...您现在希望candidate 以某种方式自毁,除非程序员实际上立即将其放入Map/Set...您希望contains“污染”@987654331 @ 所以除非它加入Map,否则任何使用它都会使其成为“诅咒”。也许你可以让SomeClass 实现一个新的Taintable 接口。

                更令人满意的解决方案是 GettableSet,如下所示。但是,要使其工作,您必须负责 SomeClass 的设计,以使所有构造函数不可见(或者......能够并愿意为其设计和使用包装类):

                public interface NoVisibleConstructor {
                    // again, this is a "nudge" technique, in the sense that there is no known method of 
                    // making an interface enforce "no visible constructor" in its implementing classes 
                    // - of course when Java finally implements full multiple inheritance some reflection 
                    // technique might be used...
                    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
                };
                
                public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
                    V getGenuineFromImpostor( V impostor ); // see below for naming
                }
                

                实施:

                public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
                    private Map<V, V> map = new HashMap<V, V>();
                
                    @Override
                    public V getGenuineFromImpostor(V impostor ) {
                        return map.get( impostor );
                    }
                
                    @Override
                    public int size() {
                        return map.size();
                    }
                
                    @Override
                    public boolean contains(Object o) {
                        return map.containsKey( o );
                    }
                
                    @Override
                    public boolean add(V e) {
                        assert e != null;
                        V result = map.put( e,  e );
                        return result != null;
                    }
                
                    @Override
                    public boolean remove(Object o) {
                        V result = map.remove( o );
                        return result != null;
                    }
                
                    @Override
                    public boolean addAll(Collection<? extends V> c) {
                        // for example:
                        throw new UnsupportedOperationException();
                    }
                
                    @Override
                    public void clear() {
                        map.clear();
                    }
                
                    // implement the other methods from Set ...
                }
                

                您的NoVisibleConstructor 类如下所示:

                class SomeClass implements NoVisibleConstructor {
                
                    private SomeClass( Object param1, Object param2 ){
                        // ...
                    }
                
                    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
                        SomeClass candidate = new SomeClass( param1, param2 );
                        if (gettableSet.contains(candidate)) {
                            // obviously this then means that the candidate "fails" (or is revealed
                            // to be an "impostor" if you will).  Return the existing element:
                            return gettableSet.getGenuineFromImpostor(candidate);
                        }
                        gettableSet.add( candidate );
                        return candidate;
                    }
                
                    @Override
                    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
                       // more elegant implementation-hiding: see below
                    }
                }
                

                PS 一个与 NoVisibleConstructor 类有关的技术问题:可能会反对这样一个类本质上是 final,这可能是不可取的。实际上,您总是可以添加一个虚拟的无参数 protected 构造函数:

                protected SomeClass(){
                    throw new UnsupportedOperationException();
                }
                

                ... 这至少可以让子类编译。然后您必须考虑是否需要在子类中包含另一个 getOrCreate() 工厂方法。

                最后一步是一个抽象基类(NB“元素”用于列表,“成员”用于集合)对于您的集合成员(如果可能 - 同样,使用 包装类,其中该类不在您的控制之下,或者已经有一个基类等),以最大限度地隐藏实现:

                public abstract class AbstractSetMember implements NoVisibleConstructor {
                    @Override
                    public NoVisibleConstructor
                            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
                        AbstractSetMember member = this;
                        @SuppressWarnings("unchecked") // unavoidable!
                        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
                        if (gettableSet.contains( member )) {
                            member = set.getGenuineFromImpostor( member );
                            cleanUpAfterFindingGenuine( set );
                        } else {
                            addNewToSet( set );
                        }
                        return member;
                    }
                
                    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
                    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
                }
                

                ...用法相当明显(在您的SomeClassstatic 工厂方法中):

                SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
                

                【讨论】:

                • 不。如果你真的想要一个新的数据结构来管理它,你应该扩展 Set 接口,例如,接口 RetrievableElementsSet 扩展 Set,它应该有一个新方法 E get ( E e ),然后你可以实现它通过在内部使用 HashMap 作为 RetrievableElementHasSet。这个SelfMappingHashMap不够抽象和干净,而不是这种乱七八糟的,直接使用Map/HashMap很快。
                • @zakmck 首先,感谢您对我的这个答案感兴趣。重新阅读它,我得出结论,您将其定性为“一团糟”是正确的。我真的不明白你的推理或你的最后一点(“快速”?),但再次感谢您考虑此事。
                • 我的意思是“更快”。直接使用 Map 是执行 OP 要求的快速方法。定义新的 Set 扩展是一种更纯粹的方式,它使“可从集合中检索的对象”的抽象明确化,但实现时间更长。我最初的意思是,如果必须定义更长、更抽象的方式,那么只有做得正确才有意义。
                【解决方案18】:

                哈希码合约明确:

                “如果根据 Object 方法,两个对象相等,那么对两个对象中的每一个调用 hashCode 方法都必须产生相同的整数结果。”

                所以你的假设:

                “为了澄清,equals方法被覆盖,但它只检查一个 字段,不是全部。所以两个被认为相等的 Foo 对象可以 实际上有不同的值,这就是为什么我不能只使用 foo。"

                错了,你违反了合同。如果我们看一下 Set 接口的“contains”方法,我们会发现:

                boolean contains(Object o);
                如果此集合包含指定的元素,则返回 true。更多的 正式地,当且仅当此集合包含一个元素时才返回 true "e" 这样 o==null ? e==null : o.equals(e)

                要完成您想要的,您可以使用定义键的 Map 并使用定义对象彼此不同或相等的键存储元素。

                【讨论】:

                  【解决方案19】:

                  如果您有 NavigableSet(例如 TreeSet),您可以执行以下操作:

                  public static <E> E get(NavigableSet<E> set, E key) {
                      return set.tailSet(key, true).floor(key);
                  }
                  

                  对于HashSet 及其后代,如LinkedHashSet,事情会稍微复杂一些:

                  import java.util.*;
                  import java.lang.reflect.Field;
                  import java.lang.reflect.Method;
                  
                  public class Test {
                      private static final Field mapField;
                      private static final Method hashMethod;
                      private static final Method getNodeMethod;
                      private static final Field keyField;
                      static {
                          try {
                              mapField = HashSet.class.getDeclaredField("map");
                              mapField.setAccessible(true);
                              hashMethod = HashMap.class.getDeclaredMethod("hash", Object.class);
                              hashMethod.setAccessible(true);
                              getNodeMethod = HashMap.class.getDeclaredMethod("getNode",
                                      Integer.TYPE, Object.class);
                              getNodeMethod.setAccessible(true);
                              keyField = Class.forName("java.util.HashMap$Node").getDeclaredField("key");
                              keyField.setAccessible(true);
                          } catch (ReflectiveOperationException e) {
                              throw new RuntimeException(e);
                          }
                      }
                  
                      public static <E> E get(HashSet<E> set, E key) {
                          try {
                              Object map = mapField.get(set);
                              Object hash = hashMethod.invoke(null, key);
                              Object node = getNodeMethod.invoke(map, hash, key);
                              if (node == null)
                                  return null;
                              @SuppressWarnings("unchecked")
                              E result = (E)keyField.get(node);
                              return result;
                          } catch (ReflectiveOperationException e) {
                              throw new RuntimeException(e);
                          }
                      }
                  
                      public static <E> E get(NavigableSet<E> set, E key) {
                          return set.tailSet(key, true).floor(key);
                      }
                  
                      public static void main(String[] args) {
                          HashSet<Integer> s = new HashSet<>();
                  //      HashSet<Integer> s = new LinkedHashSet<>();
                  //      TreeSet<Integer> s = new TreeSet<>();
                          for (int i = 0; i < 100_000; i++)
                              s.add(i);
                          Integer key = java.awt.event.KeyEvent.VK_FIND;
                          Integer hidden = get(s, key);
                          System.out.println(key);
                          System.out.println(hidden);
                          System.out.println(key.equals(hidden));
                          System.out.println(key == hidden);
                      }
                  }
                  

                  【讨论】:

                    【解决方案20】:

                    可能解决这种情况的快速帮助方法:

                    <T> T onlyItem(Collection<T> items) {
                        if (items.size() != 1)
                            throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());
                    
                        return items.iterator().next();
                    }
                    

                    【讨论】:

                    • 很奇怪,这个答案得到了如此多的支持,因为它没有回答这个问题,甚至没有尝试以任何方式解决这个问题。
                    【解决方案21】:

                    尝试使用数组:

                    ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
                    

                    【讨论】:

                      【解决方案22】:

                      以下可以是一种方法

                         SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
                         Set<String> main = se_get.getStringSet("mydata",null);
                         for(int jk = 0 ; jk < main.size();jk++)
                         {
                            Log.i("data",String.valueOf(main.toArray()[jk]));
                         }
                      

                      【讨论】:

                        猜你喜欢
                        • 2017-04-22
                        • 1970-01-01
                        • 2014-09-23
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2014-04-15
                        相关资源
                        最近更新 更多