【问题标题】:Java - how to get a key object (or entry) stored in HashMap by key?Java - 如何按键获取存储在HashMap中的关键对象(或条目)?
【发布时间】:2017-11-24 23:41:37
【问题描述】:

我想为每个可用于查询地图的键获取“规范”键对象。见这里:

Map<UUID, String> map = new HashMap();
UUID a = new UUID("ABC...");
map.put(a, "Tu nejde o zamykání.");

UUID b = new UUID("ABC...");
String string = map.get(b); // This gives that string.

// This is what I am looking for:
UUID againA = map.getEntry(b).key();
boolean thisIsTrue = a == againA;

HashMap 使用 equals(),对于多个唯一对象也是如此。所以我想从映射中获取实际的键,无论使用什么对象来查询映射,它总是相同的。

有没有办法从地图中获取实际的关键对象?我在界面中看不到任何内容,但也许是我忽略了一些巧妙的技巧?

(迭代所有条目或键不计算在内。)

【问题讨论】:

  • 出于什么目的?您关心实际对象身份的唯一时间是您是否正在同步或滥用==。注意哈希映射同时使用hashCode()equals()
  • 回复:“A HashMap 使用 hashCode(),这对于多个唯一对象是相同的”:更相关的是,它使用 equals() 而不是 ==hashCode() 只是为了快速缩小使用equals() 所需的键集。
  • 这次和上一次你问这个问题的不清楚的是你问这个问题的原因。至少上次你有一个原因,虽然你发布的代码实际上毫无意义。这一次,什么都没有,甚至在 23 分钟前和几次编辑之前被特别要求解释时也没有。
  • 如果您的设计依赖于获取对特定实例的引用,那么最初传递给put(K,V) 的关键实例和等效项(通过hashCode()equals())是不够的,那么你的设计坏了。除非你能提供一个令人信服的理由,否则我们必须假设它是XY Problem
  • 你为什么问重要:当问题显得毫无意义时;当你被问到时;当你问的问题看起来像 XY 问题时;当你之前提出这个问题的动机被证明是虚假的。您是否正在寻找IdentityHashMap

标签: java dictionary key


【解决方案1】:

有没有办法从地图中获取实际的关键对象?

好的,所以我将对你的意思做一些假设。毕竟,您说您的问题不需要澄清,所以我能看到的明显含义一定是正确的。对? :-)

答案是否定的。没有办法。

示例场景(不可编译!)

UUID uuid = UUID.fromString("xxxx-yyy-zzz");
UUID uuid2 = UUID.fromString("xxxx-yyy-zzz");  // same string
println(uuid == uuid2);  // prints false
println(uuid.equals(true));  // prints true

Map<UUID, String> map = new ...
map.put(uuid, "fred");
println(map.get(uuid)); // prints fred
println(map.get(uuid2)); // prints fred (because uuid.equals(uuid2) is true)

...但是,Map API 除了迭代键或条目集。而且我不知道有任何现有的 Map 类(标准或第 3 方)提供此功能1

但是,您可以实现您自己的Map 类,并使用额外的方法返回实际的键对象。没有技术上的原因您不能,尽管您需要编写、测试、维护等更多代码。


但我要补充一点,我同意吉姆·加里森的观点。如果您有一个场景,其中您有 UUID 对象(具有按值相等的语义)并且您还想通过标识语义实现相等,那么您的应用程序的设计可能有问题。正确的方法是将UUID.fromString(...) 实现更改为始终为相同的输入字符串返回相同的UUID 对象。


1 - 这并不是说这样的地图实现不存在。但如果确实如此,如果你足够努力的话应该能够找到它。请注意,要求我们查找或推荐图书馆的问题是题外话!

【讨论】:

    【解决方案2】:

    有一种(相对)简单的方法可以做到这一点。我不时在我的应用程序中这样做,当需要时......不是为了==测试的目的,而是为了减少存在数万个对象时存储的相同对象的数量,并且是交叉的互相引用。这显着减少了我的内存使用量,并提高了性能......同时仍然使用equals() 进行相等测试。

    只需维护一个用于实习密钥的并行映射。

    Map<UUID, UUID> interned_keys = ...
    
    UUID key = ...
    if (interned_keys.contains(key))
        key = interned_keys.get(key)
    

    当然,当被存储的对象知道它自己的身份是什么时会好得多。然后你基本上免费得到实习。

    class Item {
        UUID key;
        // ...
    }
    
    Map<UUID, Item> map = ...
    map.put(item.key, item);
    
    UUID key = ...
    key = map.get(key).key;  // get interned key
    

    【讨论】:

    • 但是额外的地图会占用内存。请参阅下面的答案。
    【解决方案3】:

    我认为需要实际密钥是有正当理由的。例如,为了节省内存。另请记住,实际密钥可能存储其他对象。例如,假设您有一个图的顶点。顶点可以存储实际数据(例如,说一个字符串)以及事件顶点。顶点散列值只能依赖于数据。因此,要查找带有一些数据的顶点, D,查找具有数据 D 且没有事件值的顶点。现在,如果您可以返回地图中的实际顶点,您将能够获得顶点的实际事件。

    在我看来,许多地图实现都可以轻松提供 getEntry 方法。比如get的HashMap实现是:

      public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
      }
    
      final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
     }
    

    可以使用 getNode 方法返回一个条目:

     public getEntry(Object key){
       Node<K,V> e = getNode(hash(key),key);
       if(e == null) return null;
       return new Entry<>(e.key,e.value);
     }
    

    【讨论】:

      【解决方案4】:

      最简单的方法是使用泛型 Pair 类型复制对值中键的引用,如下所示:

      HashMap<UUID,Pair<UUID,String>> myMap = new HashMap<>();
      

      当您将它们放入映射时,您提供了对密钥对的引用。费用是每个条目一个参考。

      void add(UUID uuid, String str)
      {
          myMap.put(uuid,Pair.of(uuid,str));
      }
      
      Pair<UUID,String> get(UUID uuid)
      {
          return myMap.get(uuid);
      }
      

      那么对的getFirst()是你的关键。 getSecond() 是值。

      无论你做什么,都会耗费你的时间或空间。

      您的 Pair 类将类似于:

      public class Pair<A,B>
      {
          private final A a;
          private final B b;
      
          
          public Pair(A a, B b)
          {
              this.a = a;
              this.b = b;
          }
      
          /**
           * @return      the first argument of the Pair
           */
          public A getFirst()
          {
              return this.a;
          }
      
          /**
           * @return      the second argument of the Pair
           */
          public B getSecond()
          {
              return this.b;
          }
      
          /**
           * Create a Pair.
           *
           * @param a         The first argument (of type A)
           * @param b         The second argument (of type B)
           *
           * @return          A Pair of A and B
           */
          public static <A,B> Pair<A,B> of(A a, B b)
          {
              return new Pair<>(a,b);
          }
      
      
          // Don't forget to get your IDE to produce a hashcode() 
          // and equals() method for you, depending
          // on if you allow nulls or not, or DIY.
      
      }
      

      【讨论】:

        【解决方案5】:

        它可以提供帮助。您可以使用 a for each 如下所示。

        Map<String,Object> map = new HashMap<>();
                map.put("hello1", new String("Hello"));
                map.put("hello2", new String("World"));
                map.put("hello3", new String("How"));
                map.put("hello4", new String("Are u"));
        
                for(Map.Entry<String,Object> e: map.entrySet()){
                     System.out.println(e.getKey());
                }
        

        【讨论】:

        • 问题指定“迭代所有条目或键不计算在内。”
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-25
        • 2012-03-25
        • 2021-10-08
        • 2021-06-05
        相关资源
        最近更新 更多