3

我正在使用哈希映射。

下面是示例代码。

MyKey 和 MyValue 类以简单的方式从 Object 继承。

Java 文档说对象和方法 hashCode() 和 equals():

“在合理可行的情况下,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。(这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 不需要这种实现技术编程语言。)”

“Object 类的 equals 方法实现了对象上最有区别的可能等价关系;也就是说,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象(x = = y 的值为真)。”

我的问题是:

我可以相信 HashMap 在我的示例中有效吗?如果不是,在不重写方法 hashCode() 和 equals() 的情况下,将简单对象放入 map 中的正确方法是什么?

我不确定,但我听说 Java 可能会在程序执行期间更改用户对象的位置和地址(是 GC 可能会这样做吗?)

如果 key2 的地址和哈希码在该行之前发生了变化

    MyValue v = m.get(key2);

那么调用 m.get(key2) 会返回错误的值,null?

如果这是真的,那么我相信 IdentityHashMap() 出于同样的原因也是无用的。

class MyKey
{
  Integer v;
  //<Perhaps more fields>
  MyKey(Integer v) {this.v=v;}
}

class MyValue
{
  String s;
  //<Perhaps more fields>
  MyValue(String s) {this.s = s;}
}


Then some code:

Map<MyKey,MyValue> m = new HashMap<MyKey,MyValue>();

MyKey key1 = new MyKey(5);
MyKey key2 = new MyKey(6);
MyKey key3 = new MyKey(7);


m.put(key1, new MyValue("AAA"));
m.put(key2, new MyValue("BBB"));
m.put(key3, new MyValue("CCC"));

.
.

//Is it sure that I will get value "AAA" here
//if entry with key2 has not been removed from map m?
MyValue v = m.get(key2);
System.out.println("s="+v.s);
4

3 回答 3

2

我可以相信 HashMap 在我的示例中有效吗?如果不是,在不重写方法 hashCode() 和 equals() 的情况下,将简单对象放入 map 中的正确方法是什么?

你不能避免提供一个合理的 hashCode 和 equals 方法,它是 HashMap 和其他 Hash 集合工作所必需的。(IdentityHashMap 除外)

我不确定,但我听说 Java 可能会在程序执行期间更改用户对象的位置和地址(是 GC 可能会这样做吗?)

虽然这是真的,但这与您的主要问题无关。

如果 key2 的地址和哈希码在该行之前发生了变化

地址和 hashCode 彼此无关。如果地址更改,它不会更改 hashCode,如果您更改 hashCode,它不会更改地址。

如果这是真的,那么我相信 IdentityHashMap() 出于同样的原因也是无用的。

即使您假设 hashCode 无用,这也不会影响 IndentityHashCode,因为它不使用hashCodeorequals方法。


对象基本上是从 Eden 空间连续分配到内存中的。如果你跑

Object[] objects = new Object[20];
for (int i = 0; i < objects.length; i++)
    objects[i] = new Object();

Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
for (int i = 0; i < objects.length; i++) {
    int location = unsafe.getInt(objects, Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * i);
    System.out.println(Integer.toHexString(location) + ": hashCode=" + Integer.toHexString(objects[i].hashCode()));
}

如果跟随某个内存位置,您可能希望它们是连续的,但它们不是

eac89d10: hashCode=634e3372
eac89d20: hashCode=2313b44d
eac89d30: hashCode=62a23d38
eac89d40: hashCode=9615a1f
eac89d50: hashCode=233aa44
eac89d60: hashCode=59243f75
eac89d70: hashCode=5ac2480b
eac89d80: hashCode=907f8ba
eac89d90: hashCode=6a5a7ff7
eac89da0: hashCode=5b8767ad
eac89db0: hashCode=50ba0dfc
eac89dc0: hashCode=2198a037
eac89dd0: hashCode=2b3e8c1c
eac89de0: hashCode=17609872
eac89df0: hashCode=46b8705b
eac89e00: hashCode=76d88aa2
eac89e10: hashCode=275cea3
eac89e20: hashCode=4513098
eac89e30: hashCode=6e4d4d5e
eac89e40: hashCode=15128ee5

Java 有四种不同的 32 位和 64 位引用编码方式,但是如果您的最大堆大小小于 2 GB,它将是一个简单的 32 位地址,就像我运行这个示例时一样。

于 2012-11-29T17:42:51.677 回答
1

我可以相信 HashMap 在我的示例中有效吗?如果不是,在不重写方法 hashCode() 和 equals() 的情况下,将简单对象放入 map 中的正确方法是什么?

您的示例不提供 hashCode 或 equals,因此它将使用默认值。默认值与对象标识一起使用,这意味着仅当 o 和 o2 引用同一对象时 o.equals(o2) 才会为真。

 MyKey m = new MyKey(1);
 MyKey m2 = new MyKey(1);
 MyKey m3 = m;

 map.put(m,...);
 map.get(m);//works
 map.get(m2); //different object
 map.get(m3);//works same object

我不确定,但我听说 Java 可能会在程序执行期间更改用户对象的位置和地址(是 GC 可能会这样做吗?)

对象的地址是不相关的,而默认的 hashCode 可能会使用它,这对每个对象只发生一次,然后保持不变。

于 2012-11-29T17:48:29.970 回答
0

首先,对象的默认哈希码在构造后不会更改,因此 IdentityHashMap 并非无用。Java 可以在内存中移动对象,但不会更改它们的身份哈希码。

其次,如果您不定义自己的equals()方法,那么您构造的每个对象都不会equals()相互关联。这意味着如果您想将它们用作 HashMap(或 IdentityHashMap)中的键,您将只能通过使用原始对象来检索它们。

例如:

MyKey key = new MyKey(5);
m.put(key, value);
...
MyKey newKey = new MyKey(5);
m.get(newKey); // Will not find the value

由于newKeykey是不同的对象,它们不是==。这就是为什么建议您为您的对象覆盖equals()(和)的原因。hashcode()

HashMap如果你不覆盖你的对象, A仍然有效equals()hashcode()但它通常不会做你想做的事 - 在这种情况下,它变得等同于IdentityHashMap

于 2012-11-29T17:54:34.350 回答