C#/Java:当 Equals 测试引用标识时正确实现 CompareTo

发布于 2024-08-24 15:47:09 字数 870 浏览 7 评论 0原文

我相信这个问题同样适用于 C# 和 Java,因为两者都要求 {c,C}ompareTo 与 {e,E}quals 一致:

假设我希望我的 equals() 方法与引用检查相同,即:

public bool equals(Object o) {
    return this == o;
}

在这种情况下,我如何实现compareTo(Object o)(或其通用等效项)?一部分很简单,但我不确定另一部分:

public int compareTo(Object o) {
    MyClass other = (MyClass)o;
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

我不能盲目地返回1或-1,因为解决方案应该遵循compareTo的正常要求。我可以检查所有实例字段,但如果它们都相等,我仍然希望compareTo返回0以外的值。 a.compareTo(b) == -(b.compareTo(a) 应该是正确的),并且只要对象的状态不改变,顺序就应该保持一致。

不过,我并不关心虚拟机调用之间的顺序。这让我觉得我可以使用内存地址之类的东西,如果我能得到它的话。话又说回来,也许这行不通,因为垃圾收集器可能会决定移动我的对象。

hashCode 是另一个想法,但我想要一些始终唯一的东西,而不仅仅是大部分唯一。

有什么想法吗?

I believe this question applies equally well to C# as to Java, because both require that {c,C}ompareTo be consistent with {e,E}quals:

Suppose I want my equals() method to be the same as a reference check, i.e.:

public bool equals(Object o) {
    return this == o;
}

In that case, how do I implement compareTo(Object o) (or its generic equivalent)? Part of it is easy, but I'm not sure about the other part:

public int compareTo(Object o) {
    MyClass other = (MyClass)o;
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

I can't just blindly return 1 or -1, because the solution should adhere to the normal requirements of compareTo. I can check all the instance fields, but if they are all equal, I'd still like compareTo to return a value other than 0. It should be true that a.compareTo(b) == -(b.compareTo(a)), and the ordering should stay consistent as long as the objects' state doesn't change.

I don't care about ordering across invocations of the virtual machine, however. This makes me think that I could use something like memory address, if I could get at it. Then again, maybe that won't work, because the Garbage Collector could decide to move my objects around.

hashCode is another idea, but I'd like something that will be always unique, not just mostly unique.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

妞丶爷亲个 2024-08-31 15:47:09

首先,如果您使用的是 Java 5 或更高版本,您应该实现 Comparable 而不是普通的旧 Comparable,因此您的 compareTo 方法应该采用 MyClass 类型的参数,而不是Object

public int compareTo(MyClass other) {
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

就你的问题而言,Josh Bloch 在《Effective Java》(第 3 章,第 12 项)中说:

实现者必须确保 sgn(x.compareTo(y)) == -sgn(y.compare-
To(x)) 对于所有 x 和 y。 (这意味着 x.compareTo(y) 必须抛出异常
当且仅当 y.compareTo(x) 抛出异常时。)

这意味着如果上面代码中的 c == 0,则必须返回 0。

这又意味着您可以拥有对象 A 和 B,但它们不相等,但它们的比较返回 0。布洛赫先生对此有何评论?

强烈建议(但不严格要求) (x.compareTo(y)
== 0) == (x.等于(y))。一般来说,任何实现了
Comparable 接口并违反此条件应明确指出
这个事实。推荐的语言是“注意:这门课有一个自然的
与 equals 不一致的顺序。”

一个类,其compareTo方法强加一个顺序
与 equals 不一致的仍然有效,但包含的排序集合
类的元素可能不遵守适当集合的一般契约
接口(集合、集合或映射)。这是因为一般合同
这些接口是根据 equals 方法定义的,但集合是排序的
使用由compareTo 施加的相等性测试来代替equals。它不是一个
如果发生这种情况,那将是一场灾难,但需要注意。

更新:因此恕我直言,对于您当前的类,您无法使 compareToequals 一致。如果您确实需要这个,我认为唯一的方法是引入一个新成员,这将为您的班级提供严格的自然顺序。然后,如果两个对象的所有有意义字段都比较为 0,您仍然可以根据它们的特殊顺序值来决定两个对象的顺序。

这个额外的成员可以是实例计数器或创建时间戳。或者,您可以尝试使用 UUID。

First of all, if you are using Java 5 or above, you should implement Comparable<MyClass> rather than the plain old Comparable, therefore your compareTo method should take parameters of type MyClass, notObject:

public int compareTo(MyClass other) {
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

As of your question, Josh Bloch in Effective Java (Chapter 3, Item 12) says:

The implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compare-
To(x)) for all x and y. (This implies that x.compareTo(y) must throw an exception
if and only if y.compareTo(x) throws an exception.)

This means that if c == 0 in the above code, you must return 0.

That in turn means that you can have objects A and B, which are not equal, but their comparison returns 0. What does Mr. Bloch have to say about this?

It is strongly recommended, but not strictly required, that (x.compareTo(y)
== 0) == (x.equals(y)). Generally speaking, any class that implements
the Comparable interface and violates this condition should clearly indicate
this fact. The recommended language is “Note: This class has a natural
ordering that is inconsistent with equals.”

And

A class whose compareTo method imposes an order
that is inconsistent with equals will still work, but sorted collections containing
elements of the class may not obey the general contract of the appropriate collection
interfaces (Collection, Set, or Map). This is because the general contracts
for these interfaces are defined in terms of the equals method, but sorted collections
use the equality test imposed by compareTo in place of equals. It is not a
catastrophe if this happens, but it’s something to be aware of.

Update: So IMHO with your current class, you can not make compareTo consistent with equals. If you really need to have this, the only way I see would be to introduce a new member, which would give a strict natural ordering to your class. Then in case all the meaningful fields of the two objects compare to 0, you could still decide the order of the two based on their special order values.

This extra member may be an instance counter, or a creation timestamp. Or, you could try using a UUID.

心碎无痕… 2024-08-31 15:47:09

一般来说,在 Java 或 C# 中,对象没有固定的顺序。在执行compareTo 或使用compareTo 的排序操作时,垃圾收集器可以移动实例。

正如您所说,哈希码通常不是唯一的,因此它们不可用(具有相同哈希码的两个不同实例使您回到原来的问题)。许多人认为 Java Object.toString 实现可以显示对象 id (MyObject@33c0d9d),它只不过是对象的类名后跟哈希码。据我所知,JVM 和 CLR 都没有实例 id 的概念。

如果您确实希望类的顺序保持一致,您可以尝试为您创建的每个新实例使用递增的数字。请注意,递增此计数器必须是线程安全的,因此它的成本相对较高(在 C# 中,您可以使用 Interlocked.Increment)。

In Java or C#, generally speaking, there is no fixed ordering of objects. Instances can be moved around by the garbage collector while executing your compareTo, or the sort operation that's using your compareTo.

As you stated, hash codes are generally not unique, so they're not usable (two different instances with the same hash code bring you back to the original question). And the Java Object.toString implementation which many people believe to surface an object id (MyObject@33c0d9d), is nothing more than the object's class name followed by the hash code. As far as I know, neither the JVM nor the CLR have a notion of an instance id.

If you really want a consistent ordering of your classes, you could try using an incrementing number for each new instance you create. Mind you, incrementing this counter must be thread safe, so it's going to be relatively expensive (in C# you could use Interlocked.Increment).

我做我的改变 2024-08-31 15:47:09

两个对象不需要引用相等才能位于同一等价类中。在我看来,两个不同的对象相同进行比较应该是完全可以接受的,但引用不相等。例如,对我来说,如果您对数据库中同一行的两个不同对象进行水合,那么出于比较目的,它们将是相同的,但引用不相等。

实际上,我更倾向于修改 equals 的行为以反映它们的比较方式,而不是相反。对于我能想到的大多数目的来说,这会更自然。

Two objects don't need to be reference equal to be in the same equivalence class. In my opinion, it should be perfectly acceptable for two different objects to be the same for a comparison, but not reference equal. It seems perfectly natural to me, for example, that if you hydrated two different objects from the same row in the database, that they would be the same for comparison purposes, but not reference equal.

I'd actually be more inclined to modify the behavior of equals to reflect how they are compared rather than the other way around. For most purposes that I can think of this would be more natural.

美煞众生 2024-08-31 15:47:09

在我看来,通用等效项更容易处理,具体取决于您的外部要求是什么,这是一个 IComparable 示例:

public int CompareTo(MyClass other) {
    if (other == null) return 1;
    if (this == other) {
        return 0;
    } else {
        return foo.CompareTo(other.foo);
    }
}

如果类相等或 if foo相等,这就是比较的结束,除非有次要的东西要排序,在这种情况下,将其添加为返回 if foo.CompareTo(other.foo) == 0

如果您的类有ID之类的东西,然后将其作为辅助进行比较,否则不用担心......他们存储的集合以及到达这些类进行比较的顺序将决定在以下情况下的最终顺序相等的对象或相等的 object.foo 值。

The generic equivalent is easier to deal with in my opinion, depends what your external requirements are, this is a IComparable<MyClass> example:

public int CompareTo(MyClass other) {
    if (other == null) return 1;
    if (this == other) {
        return 0;
    } else {
        return foo.CompareTo(other.foo);
    }
}

If the classes are equal or if foo is equal, that's the end of the comparison, unless there's something secondary to sort on, in that case add it as the return if foo.CompareTo(other.foo) == 0

If your classes have an ID or something, then compare on that as secondary, otherwise don't worry about it...the collection they're stored it and it's order in arriving at these classes to compare is what's going to determine the final order in the case of equal objects or equal object.foo values.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文