【问题标题】:Find an a specific instance, what is the best approach找到一个特定的实例,最好的方法是什么
【发布时间】:2017-08-20 00:47:56
【问题描述】:

假设我有一个类的两个实例:

public class MyClass {
    public void sayHello() {
         System.out.println("Hello");
    }
}

a = new MyClass();
b = new MyClass();

现在我将它们添加到另一个对象中,例如:

public class OtherClass {
    private ArrayList<MyClass> myClsList = new ArrayList<>();

    public void add(MyClass obj) {
        myClsList.add(obj);
    }

    public void remove(MyClass obj) {
        // ????
    }
}

c = new OtherClass();

c.add(a);
c.add(b);

现在我想删除一个特定的实例,例如

c.remove(a);
  1. 我可以遍历它们并测试是否相等吗?我的意思是理论上这应该可行,因为这两个实例具有不同的“内部指针”?

  2. 我想使用基于 HashMap 的方法会更有效,但我可以在那里使用什么作为键(假设我不能添加唯一的实例 ID 或其他东西)。

编辑:对于我到底想知道什么有些困惑。 这里的关键是我想知道是否有任何方法可以从 c 的 ArrayList 或我可能使用的任何聚合器对象中删除该特定实例,只需提供相应的对象引用即可。 我想这可以通过只保留 ArrayList 并测试是否相等来完成(尽管我不是 100% 确定),但如果可以不遍历整个列表,它会更干净。 我只是想知道在 Java 中是否有类似的东西。 (我知道如何通过使用附加信息来解决它,但线索是仅使用相应的对象引用来进行过滤/检索。

【问题讨论】:

  • 您可以为您的类使用唯一标识符(例如,键入 int),并将该标识符用作您的哈希条目。
  • 您是否有任何具体原因要查找未索引的附录?我所看到的只是你附加了一个,但我看不出有什么理由不能扩展这个特定的类。请记住,Java 一种解释型语言,而在实践中你可以这样做,你并没有给我们一个很好的例子。
  • 对于2.可以使用HashSet,如果需要保持插入LinkedHashSet的顺序。 PS:现在需要hashcode()
  • @Gio 我知道,但这就是我不想要的,正如问题中所述。
  • 使用 Object.equals 有什么问题?这基本上是为您实现唯一的 id 平等。

标签: java filter


【解决方案1】:

你可以使用a.toString(),根据Java doc

Object 类的 toString 方法返回一个字符串,该字符串由 对象是其实例的类的名称,at 符号 字符“@”和哈希的无符号十六进制表示 对象的代码。

这应该为您的类实例提供一个唯一标识符,因此您可以将其用作哈希键,而无需存储/创建任何额外的标识符。

注意:这种做法要小心,不要依赖`Object.toString()返回的值,因为它与实际的对象地址有关,详细解释见here

【讨论】:

  • 谢谢,这似乎是我一直在寻找的。所以这意味着只要 GC 没有释放该对象的内存,它应该没问题,因为我存储了对对象的非弱引用,这意味着只要 OtherClass 实例还活着(如果不是因为一些奇怪的JVM 内部)。沿着这条路走,用System.identityHashCode(obj)作为key不是更安全吗?
  • 安全方面我看不出有什么区别,但是只要您的对象属于同一类型,您也可以只使用 hashCode 作为键(不同类型的对象实例可能共享相同哈希键)。
  • 这只是增加了不必要的开销。
【解决方案2】:

虽然您的问题是许多初学者(包括我自己)都有的问题,但我认为您的担忧在这种情况下是不合理的。您要求的功能已经在规范级别内置到 Java 语言中。

首先,我们来看看Object.equals()。一方面,Language Specification 声明

equals 方法定义了对象相等的概念,它基于值,而不是引用,比较。

但是,Object.equals() 的文档明确指出

Object 类的equals 方法实现了对象上最有区别的可能等价关系;也就是说,对于任何非空引用值xy,当且仅当xy 引用同一个对象(x == y 具有值true 时,此方法才返回true )。

这意味着您可以安全地将OtherClass.remove 重定向到ArrayList.remove()。无论Object.equals 正在比较的东西都完全像一个唯一的ID。事实上,在许多(但不是全部)实现中,它会将内存地址与对象进行比较,对象是唯一 ID 的一种形式。

可以理解,您不希望每次都使用线性迭代。碰巧的是,Object 的机制非常适合与 HashSet 之类的东西一起使用,顺便说一下,我建议您在这种情况下使用它。

如果你不是在处理一些庞大的数据集,我们不需要讨论Object.hashCode()的优化。您只需要知道它将执行任何必要的合同,以便与 Object.equals 正常工作以使 HashSet.remove 正常工作。

spec itself 仅声明

hashCode 方法非常有用,与equals 方法一起,在java.util.Hashmap 等哈希表中。

这个真的不多说了,所以我们转向API参考。两个相关点是:

  • 如果两个对象根据 equals(Object) 方法相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
  • 如果两个对象根据equals(java.lang.Object) 方法不相等,则不需要对两个对象中的每一个调用hashCode 方法都必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

简单地说,相等对象的hashCode必须相同,但相等的hashCode并不一定意味着相等的对象。 Object 实现了这个契约,所以你可以将它与 HashSet 一起使用,它由 HashMap 支持。

缺少一条信息以使其成为支持不做任何额外工作的正式论点,这就是为什么我一直引用 API 参考,就好像它是语言规范一样。因为它happens:

如上所述,本规范通常引用 Java SE 平台 API 的类。特别是,某些类与 Java 编程语言有着特殊的关系。示例包括 ObjectClassClassLoaderStringThread 等类,以及包 java.lang.reflect 中的类和接口等。 本规范限制了此类类和接口的行为,但没有为它们提供完整的规范。读者可参考 Java SE 平台 API 文档。

[强调我的],但你明白了。 Java SE API 参考Object 的方法行为而言的语言规范。

顺便说一句,您可能希望远离TreeSet 之类的东西,因为这要求您在实现中添加一堆机制。至少,MyClass 实例必须是可订购的,要么通过实现 Comparable,要么通过将自定义 Comparator 分配给 Set

TL;DR

语言规范指出,您至少可以使用以下两个选项,而无需您付出额外的努力:

  1. myClsList 设置为ArrayList 并使用合适的add()/remove() 方法。
  2. myClsList 设为HashSet 并使用适当的add()/remove() 方法。

我推荐第二个选项。实际上,您可以考虑扩展 HashSet 而不是遏制,这样您就不必费心实现自己的 add/remove 方法。

最后说明

只要MyClass 覆盖既不 Object.equals 也不覆盖Object.hashCode,所有这些都有效。这样做的那一刻,您就将满足合同要求的重担完全放在了自己身上。

【讨论】:

  • 非常感谢您的详细回答。我看到我有点偏执。尤其是内存地址点困扰着我,如果它只是当前的内存地址,一切都会感觉安全,但这样我真的很不确定。好吧,非常感谢您花时间回答我:)
  • @meow。没问题。这种事情过去也一直困扰着我,所以我可以联系起来。
猜你喜欢
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多