比较两个对象的相等性有哪些替代方法?

时间:2021-07-15 22:58:43

http://leepoint.net/notes-java/data/expressions/22compareobjects.html

It turns out that defining equals() isn't trivial; in fact it's moderately hard to get it right, especially in the case of subclasses. The best treatment of the issues is in Horstmann's Core Java Vol 1.

事实证明,定义equals()并不是微不足道的;事实上,正确地解决它是非常困难的,特别是在子类的情况下。问题的最佳处理方法是Horstmann的Core Java Vol 1。

If equals() must always be overridden, then what is a good approach for not being cornered into having to do object comparison? What are some good "design" alternatives?

如果必须始终覆盖equals(),那么什么是不被逼入必须进行对象比较的好方法?什么是一些好的“设计”替代品?

EDIT:

I'm not sure this is coming across the way that I had intended. Maybe the question should be more along the lines of "Why would you want to compare two objects?" Based upon your answer to that question, is there an alternative solution to comparison? I don't mean, a different implementation of equals. I mean, not using equality at all. I think the key point is to start with that question, why would you want to compare two objects.

我不确定这是否符合我的预期。也许这个问题应该更像是“你为什么要比较两个对象?”根据您对该问题的回答,是否有替代解决方案?我不是说,平等的不同实现。我的意思是,根本不使用平等。我认为关键是从这个问题开始,你为什么要比较两个对象。

7 个解决方案

#1


4  

If equals() must always be overridden, then what is a good approach for not being cornered into having to do object comparison?

如果必须始终覆盖equals(),那么什么是不被逼入必须进行对象比较的好方法?

You are mistaken. You should override equals as seldom as possible.

你误会了。你应该尽可能地重写equals。


All this info comes from Effective Java, Second Edition (Josh Bloch). The first edition chapter on this is still available as a free download.

所有这些信息都来自Effective Java,Second Edition(Josh Bloch)。关于此的第一版章节仍然可以免费下载。

From Effective Java:

来自Effective Java:

The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself.

避免问题的最简单方法是不要覆盖equals方法,在这种情况下,类的每个实例只与自身相等。

The problem with arbitrarily overriding equals/hashCode is inheritance. Some equals implementations advocate testing it like this:

任意覆盖equals / hashCode的问题是继承。有些等于实现主张测试它像这样:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

In fact, the Eclipse (3.4) Java editor does just this when you generate the method using the source tools. According to Bloch, this is a mistake as it violates the Liskov substitution principle.

事实上,当您使用源工具生成方法时,Eclipse(3.4)Java编辑器就是这样做的。据布洛赫说,这是一个错误,因为它违反了利斯科夫替代原则。

From Effective Java:

来自Effective Java:

There is no way to extend an instantiable class and add a value component while preserving the equals contract.

在保留equals合同的同时,无法扩展可实例化的类并添加值组件。

Two ways to minimize equality problems are described in the Classes and Interfaces chapter:

“类和接口”一章中描述了两种最小化相等问题的方法:

  1. Favour composition over inheritance
  2. 赞成合成而不是继承

  3. Design and document for inheritance or else prohibit it
  4. 继承的设计和文档,否则禁止它


As far as I can see, the only alternative is to test equality in a form external to the class, and how that would be performed would depend on the design of the type and the context you were trying to use it in.

据我所知,唯一的选择是以类外部的形式测试相等性,以及如何执行它将取决于类型的设计和您尝试使用它的上下文。

For example, you might define an interface that documents how it was to be compared. In the code below, Service instances might be replaced at runtime with a newer version of the same class - in which case, having different ClassLoaders, equals comparisons would always return false, so overriding equals/hashCode would be redundant.

例如,您可以定义一个记录比较方式的接口。在下面的代码中,Service实例可能在运行时被更新版本的同一类替换 - 在这种情况下,具有不同的ClassLoaders,等于比较将始终返回false,因此重写equals / hashCode将是多余的。

public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

"Why would you want to compare two objects?"

“你为什么要比较两个物体?”

The obvious example is to test if two Strings are the same (or two Files, or URIs). For example, what if you wanted to build up a set of files to parse. By definition, the set contains only unique elements. Java's Set type relies on the equals/hashCode methods to enforce uniqueness of its elements.

明显的例子是测试两个字符串是否相同(或两个文件或URI)。例如,如果要构建一组要解析的文件,该怎么办?根据定义,该集仅包含唯一元素。 Java的Set类型依赖于equals / hashCode方法来强制其元素的唯一性。

#2


4  

I don't think it's true that equals should always be overridden. The rule as I understand it is that overriding equals is only meaningful in cases where you're clear on how to define semantically equivalent objects. In that case, you override hashCode() as well so that you don't have objects that you've defined as equivalent returning different hashcodes.

我不认为平等应该总是被覆盖。我理解的规则是,重写equals只有在你清楚如何定义语义等价对象的情况下才有意义。在这种情况下,您也会覆盖hashCode(),以便您没有将已定义为等效的对象返回不同的哈希码。

If you can't define meaningful equivalence, I don't see the benefit.

如果你不能定义有意义的等价,我看不到好处。

#3


3  

How about just do it right?

做得怎么样?

Here's my equals template which is knowledge applied from Effective Java by Josh Bloch. Read the book for more details:

这是我的equals模板,它是Josh Bloch从Effective Java应用的知识。阅读本书了解更多详情:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

#4


1  

Mmhh

In some scenarios you can make the object unmodifiable ( read-only ) and have it created from a single point ( a factory method )

在某些情况下,您可以使对象不可修改(只读)并从单点创建它(工厂方法)

If two objects with the same input data ( creation parameters ) are needed the factory will return the same instance ref and then using "==" would be enough.

如果需要两个具有相同输入数据(创建参数)的对象,工厂将返回相同的实例引用,然后使用“==”就足够了。

This approach is useful under certain circumstances only. And most of the times would look overkill.

这种方法仅在某些情况下有用。大多数时候看起来都太过分了。

Take a look at this answer to know how to implement such a thing.

看看这个答案,知道如何实现这样的事情。

warning it is a lot of code

警告它是很多代码

For short see how the wrapper class works since java 1.5

简而言之,看看自Java 1.5以来包装器类是如何工作的

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

is true while

这是真的

new Integer( 2 ) == new Integer( 2 )  

is false.

It internally keeps the reference and return it if the input value is the same.

它在内部保留引用并在输入值相同时返回它。

As you know Integer is read-only

如您所知,Integer是只读的

Something similar happens with the String class from which that question was about.

与该问题有关的String类会发生类似的事情。

#5


1  

Maybe I'm missing the point but the only reason to use equals as opposed to defining your own method with a different name is because many of the Collections (and probably other stuff in the JDK or whatever it's called these days) expect the equals method to define a coherent result. But beyond that, I can think of three kinds of comparisons that you want to do in equals:

也许我错过了这一点,但是使用equals而不是使用不同名称定义自己的方法的唯一原因是因为许多集合(以及JDK中的其他东西或者现在所谓的其他东西)都期望equals方法定义一致的结果。但除此之外,我可以想到你想要做的三种比较:

  1. The two objects really ARE the same instance. This makes no sense to use equals because you can use ==. Also, and correct me if I've forgotten how it works in Java, the default equals method does this using the automatically generated hash codes.
  2. 这两个对象实际上是同一个实例。使用equals是没有意义的,因为你可以使用==。另外,如果我忘记了它在Java中是如何工作的那么纠正我,默认的equals方法使用自动生成的哈希码来做到这一点。

  3. The two objects have references to the same instances, but are not the same instance. This is useful, uh, sometimes... particularly if they are persisted objects and refer to the same object in the DB. You would have to define your equals method to do this.
  4. 这两个对象引用了相同的实例,但它们不是同一个实例。这很有用,呃,有时候......特别是如果它们是持久化对象并引用数据库中的同一个对象。您必须定义equals方法才能执行此操作。

  5. The two objects have references to objects that are equal in value, though they may or may not be the same instances (in other words, you compare values all the way through the hierarchy).
  6. 这两个对象引用了值相等的对象,尽管它们可能是也可能不是相同的实例(换句话说,您在层次结构中一直比较值)。

Why would you want to compare two objects? Well, if they're equal, you would want to do one thing, and if they're not, you would want to do something else.

你为什么要比较两个对象?好吧,如果他们是平等的,你会想要做一件事,如果他们不这样做,你会想做别的事情。

That said, it depends on the case at hand.

也就是说,这取决于手头的情况。

#6


0  

The main reason to override equals() in most cases is to check for duplicates within certain Collections. For example, if you want to use a Set to contain an object you have created you need to override equals() and hashCode() within your object. The same applies if you want to use your custom object as a key in a Map.

在大多数情况下,重写equals()的主要原因是检查某些集合中的重复项。例如,如果要使用Set来包含已创建的对象,则需要在对象中覆盖equals()和hashCode()。如果要将自定义对象用作Map中的键,则同样适用。

This is critical as I have seen many people make the mistake in practice of adding their custom objects to Sets or Maps without overriding equals() and hashCode(). The reason this can be especially insidious is the compiler will not complain and you can end up with multiple objects that contain the same data but have different references in a Collection that does not allow duplicates.

这很重要,因为我看到许多人在实践中将错误添加到集合或映射而不覆盖equals()和hashCode()。这可能是特别阴险的原因是编译器不会抱怨并且您最终可能会得到多个包含相同数据但在Collection中具有不同重复项的引用的对象。

For example if you had a simple bean called NameBean with a single String attribute 'name', you could construct two instances of NameBean (e.g. name1 and name2), each with the same 'name' attribute value (e.g. "Alice"). You could then add both name1 and name2 to a Set and the set would be size 2 rather than size 1 which is what is intended. Likewise if you have a Map such as Map in order to map the name bean to some other object, and you first mapped name1 to the string "first" and later mapped name2 to the string "second" you will have both key/value pairs in the map (e.g. name1->"first", name2->"second"). So when you do a map lookup it will return the value mapped to the exact reference you pass in, which is either name1, name2, or another reference with name "Alice" that will return null.

例如,如果你有一个名为NameBean的简单bean,它有一个String属性'name',你可以构造两个NameBean实例(例如name1和name2),每个实例都有相同的'name'属性值(例如“Alice”)。然后,您可以将name1和name2添加到Set中,该集合将是大小2而不是大小1,这是预期的。同样,如果你有一个Map这样的Map,以便将name bean映射到其他对象,你首先将name1映射到字符串“first”,然后将name2映射到字符串“second”,你将拥有两个键/值对在地图中(例如name1 - >“first”,name2 - >“second”)。因此,当您执行地图查找时,它将返回映射到您传入的确切引用的值,该值是name1,name2或名为“Alice”的另一个将返回null的引用。

Here is a concrete example preceded by the output of running it:

这是一个具体的例子,前面是运行它的输出:

Output:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Code:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

#7


0  

Equality is fundamental to logic (see law of identity), and there's not much programming you can do without it. As for comparing instances of classes that you write, well that's up to you. If you need to be able to find them in collections or use them as keys in Maps, you'll need equality checks.

平等是逻辑的基础(参见身份法则),没有它就没有太多的编程可以做。至于比较你写的类的实例,这取决于你。如果您需要能够在集合中找到它们或在地图中将它们用作键,则需要进行相等性检查。

If you've written more than a few nontrivial libraries in Java, you'll know that equality is hard to get right, especially when the only tools in the chest are equals and hashCode. Equality ends up being tightly coupled with class hierarchies, which makes for brittle code. What's more, no type checking is provided since these methods just take parameters of type Object.

如果您在Java中编写了多个非平凡的库,您就会知道平等很难做到,尤其是当胸部中的唯一工具是equals和hashCode时。平等最终与类层次结构紧密耦合,这使得代码变得脆弱。更重要的是,没有提供类型检查,因为这些方法只接受Object类型的参数。

There's a way of making equality checking (and hashing) a lot less error-prone and more type-safe. In the Functional Java library, you'll find Equal<A> (and a corresponding Hash<A>) where equality is decoupled into a single class. It has methods for composing Equal instances for your classes from existing instances, as well as wrappers for Collections, Iterables, HashMap, and HashSet, that use Equal<A> and Hash<A> instead of equals and hashCode.

有一种方法可以使等式检查(和散列)更容易出错并且更加类型安全。在Functional Java库中,您将找到Equal (和相应的Hash ),其中相等性被解耦为单个类。它具有从现有实例为类组成Equal实例的方法,以及使用Equal 和Hash 而不是equals和hashCode的Collections,Iterables,HashMap和HashSet的包装器。

What's best about this approach is that you can never forget to write equals and hash method when they are called for. The type system will help you remember.

这种方法的最佳之处在于,在调用它们时,您永远不会忘记编写equals和hash方法。类型系统将帮助您记住。

#1


4  

If equals() must always be overridden, then what is a good approach for not being cornered into having to do object comparison?

如果必须始终覆盖equals(),那么什么是不被逼入必须进行对象比较的好方法?

You are mistaken. You should override equals as seldom as possible.

你误会了。你应该尽可能地重写equals。


All this info comes from Effective Java, Second Edition (Josh Bloch). The first edition chapter on this is still available as a free download.

所有这些信息都来自Effective Java,Second Edition(Josh Bloch)。关于此的第一版章节仍然可以免费下载。

From Effective Java:

来自Effective Java:

The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself.

避免问题的最简单方法是不要覆盖equals方法,在这种情况下,类的每个实例只与自身相等。

The problem with arbitrarily overriding equals/hashCode is inheritance. Some equals implementations advocate testing it like this:

任意覆盖equals / hashCode的问题是继承。有些等于实现主张测试它像这样:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

In fact, the Eclipse (3.4) Java editor does just this when you generate the method using the source tools. According to Bloch, this is a mistake as it violates the Liskov substitution principle.

事实上,当您使用源工具生成方法时,Eclipse(3.4)Java编辑器就是这样做的。据布洛赫说,这是一个错误,因为它违反了利斯科夫替代原则。

From Effective Java:

来自Effective Java:

There is no way to extend an instantiable class and add a value component while preserving the equals contract.

在保留equals合同的同时,无法扩展可实例化的类并添加值组件。

Two ways to minimize equality problems are described in the Classes and Interfaces chapter:

“类和接口”一章中描述了两种最小化相等问题的方法:

  1. Favour composition over inheritance
  2. 赞成合成而不是继承

  3. Design and document for inheritance or else prohibit it
  4. 继承的设计和文档,否则禁止它


As far as I can see, the only alternative is to test equality in a form external to the class, and how that would be performed would depend on the design of the type and the context you were trying to use it in.

据我所知,唯一的选择是以类外部的形式测试相等性,以及如何执行它将取决于类型的设计和您尝试使用它的上下文。

For example, you might define an interface that documents how it was to be compared. In the code below, Service instances might be replaced at runtime with a newer version of the same class - in which case, having different ClassLoaders, equals comparisons would always return false, so overriding equals/hashCode would be redundant.

例如,您可以定义一个记录比较方式的接口。在下面的代码中,Service实例可能在运行时被更新版本的同一类替换 - 在这种情况下,具有不同的ClassLoaders,等于比较将始终返回false,因此重写equals / hashCode将是多余的。

public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

"Why would you want to compare two objects?"

“你为什么要比较两个物体?”

The obvious example is to test if two Strings are the same (or two Files, or URIs). For example, what if you wanted to build up a set of files to parse. By definition, the set contains only unique elements. Java's Set type relies on the equals/hashCode methods to enforce uniqueness of its elements.

明显的例子是测试两个字符串是否相同(或两个文件或URI)。例如,如果要构建一组要解析的文件,该怎么办?根据定义,该集仅包含唯一元素。 Java的Set类型依赖于equals / hashCode方法来强制其元素的唯一性。

#2


4  

I don't think it's true that equals should always be overridden. The rule as I understand it is that overriding equals is only meaningful in cases where you're clear on how to define semantically equivalent objects. In that case, you override hashCode() as well so that you don't have objects that you've defined as equivalent returning different hashcodes.

我不认为平等应该总是被覆盖。我理解的规则是,重写equals只有在你清楚如何定义语义等价对象的情况下才有意义。在这种情况下,您也会覆盖hashCode(),以便您没有将已定义为等效的对象返回不同的哈希码。

If you can't define meaningful equivalence, I don't see the benefit.

如果你不能定义有意义的等价,我看不到好处。

#3


3  

How about just do it right?

做得怎么样?

Here's my equals template which is knowledge applied from Effective Java by Josh Bloch. Read the book for more details:

这是我的equals模板,它是Josh Bloch从Effective Java应用的知识。阅读本书了解更多详情:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

#4


1  

Mmhh

In some scenarios you can make the object unmodifiable ( read-only ) and have it created from a single point ( a factory method )

在某些情况下,您可以使对象不可修改(只读)并从单点创建它(工厂方法)

If two objects with the same input data ( creation parameters ) are needed the factory will return the same instance ref and then using "==" would be enough.

如果需要两个具有相同输入数据(创建参数)的对象,工厂将返回相同的实例引用,然后使用“==”就足够了。

This approach is useful under certain circumstances only. And most of the times would look overkill.

这种方法仅在某些情况下有用。大多数时候看起来都太过分了。

Take a look at this answer to know how to implement such a thing.

看看这个答案,知道如何实现这样的事情。

warning it is a lot of code

警告它是很多代码

For short see how the wrapper class works since java 1.5

简而言之,看看自Java 1.5以来包装器类是如何工作的

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

is true while

这是真的

new Integer( 2 ) == new Integer( 2 )  

is false.

It internally keeps the reference and return it if the input value is the same.

它在内部保留引用并在输入值相同时返回它。

As you know Integer is read-only

如您所知,Integer是只读的

Something similar happens with the String class from which that question was about.

与该问题有关的String类会发生类似的事情。

#5


1  

Maybe I'm missing the point but the only reason to use equals as opposed to defining your own method with a different name is because many of the Collections (and probably other stuff in the JDK or whatever it's called these days) expect the equals method to define a coherent result. But beyond that, I can think of three kinds of comparisons that you want to do in equals:

也许我错过了这一点,但是使用equals而不是使用不同名称定义自己的方法的唯一原因是因为许多集合(以及JDK中的其他东西或者现在所谓的其他东西)都期望equals方法定义一致的结果。但除此之外,我可以想到你想要做的三种比较:

  1. The two objects really ARE the same instance. This makes no sense to use equals because you can use ==. Also, and correct me if I've forgotten how it works in Java, the default equals method does this using the automatically generated hash codes.
  2. 这两个对象实际上是同一个实例。使用equals是没有意义的,因为你可以使用==。另外,如果我忘记了它在Java中是如何工作的那么纠正我,默认的equals方法使用自动生成的哈希码来做到这一点。

  3. The two objects have references to the same instances, but are not the same instance. This is useful, uh, sometimes... particularly if they are persisted objects and refer to the same object in the DB. You would have to define your equals method to do this.
  4. 这两个对象引用了相同的实例,但它们不是同一个实例。这很有用,呃,有时候......特别是如果它们是持久化对象并引用数据库中的同一个对象。您必须定义equals方法才能执行此操作。

  5. The two objects have references to objects that are equal in value, though they may or may not be the same instances (in other words, you compare values all the way through the hierarchy).
  6. 这两个对象引用了值相等的对象,尽管它们可能是也可能不是相同的实例(换句话说,您在层次结构中一直比较值)。

Why would you want to compare two objects? Well, if they're equal, you would want to do one thing, and if they're not, you would want to do something else.

你为什么要比较两个对象?好吧,如果他们是平等的,你会想要做一件事,如果他们不这样做,你会想做别的事情。

That said, it depends on the case at hand.

也就是说,这取决于手头的情况。

#6


0  

The main reason to override equals() in most cases is to check for duplicates within certain Collections. For example, if you want to use a Set to contain an object you have created you need to override equals() and hashCode() within your object. The same applies if you want to use your custom object as a key in a Map.

在大多数情况下,重写equals()的主要原因是检查某些集合中的重复项。例如,如果要使用Set来包含已创建的对象,则需要在对象中覆盖equals()和hashCode()。如果要将自定义对象用作Map中的键,则同样适用。

This is critical as I have seen many people make the mistake in practice of adding their custom objects to Sets or Maps without overriding equals() and hashCode(). The reason this can be especially insidious is the compiler will not complain and you can end up with multiple objects that contain the same data but have different references in a Collection that does not allow duplicates.

这很重要,因为我看到许多人在实践中将错误添加到集合或映射而不覆盖equals()和hashCode()。这可能是特别阴险的原因是编译器不会抱怨并且您最终可能会得到多个包含相同数据但在Collection中具有不同重复项的引用的对象。

For example if you had a simple bean called NameBean with a single String attribute 'name', you could construct two instances of NameBean (e.g. name1 and name2), each with the same 'name' attribute value (e.g. "Alice"). You could then add both name1 and name2 to a Set and the set would be size 2 rather than size 1 which is what is intended. Likewise if you have a Map such as Map in order to map the name bean to some other object, and you first mapped name1 to the string "first" and later mapped name2 to the string "second" you will have both key/value pairs in the map (e.g. name1->"first", name2->"second"). So when you do a map lookup it will return the value mapped to the exact reference you pass in, which is either name1, name2, or another reference with name "Alice" that will return null.

例如,如果你有一个名为NameBean的简单bean,它有一个String属性'name',你可以构造两个NameBean实例(例如name1和name2),每个实例都有相同的'name'属性值(例如“Alice”)。然后,您可以将name1和name2添加到Set中,该集合将是大小2而不是大小1,这是预期的。同样,如果你有一个Map这样的Map,以便将name bean映射到其他对象,你首先将name1映射到字符串“first”,然后将name2映射到字符串“second”,你将拥有两个键/值对在地图中(例如name1 - >“first”,name2 - >“second”)。因此,当您执行地图查找时,它将返回映射到您传入的确切引用的值,该值是name1,name2或名为“Alice”的另一个将返回null的引用。

Here is a concrete example preceded by the output of running it:

这是一个具体的例子,前面是运行它的输出:

Output:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Code:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

#7


0  

Equality is fundamental to logic (see law of identity), and there's not much programming you can do without it. As for comparing instances of classes that you write, well that's up to you. If you need to be able to find them in collections or use them as keys in Maps, you'll need equality checks.

平等是逻辑的基础(参见身份法则),没有它就没有太多的编程可以做。至于比较你写的类的实例,这取决于你。如果您需要能够在集合中找到它们或在地图中将它们用作键,则需要进行相等性检查。

If you've written more than a few nontrivial libraries in Java, you'll know that equality is hard to get right, especially when the only tools in the chest are equals and hashCode. Equality ends up being tightly coupled with class hierarchies, which makes for brittle code. What's more, no type checking is provided since these methods just take parameters of type Object.

如果您在Java中编写了多个非平凡的库,您就会知道平等很难做到,尤其是当胸部中的唯一工具是equals和hashCode时。平等最终与类层次结构紧密耦合,这使得代码变得脆弱。更重要的是,没有提供类型检查,因为这些方法只接受Object类型的参数。

There's a way of making equality checking (and hashing) a lot less error-prone and more type-safe. In the Functional Java library, you'll find Equal<A> (and a corresponding Hash<A>) where equality is decoupled into a single class. It has methods for composing Equal instances for your classes from existing instances, as well as wrappers for Collections, Iterables, HashMap, and HashSet, that use Equal<A> and Hash<A> instead of equals and hashCode.

有一种方法可以使等式检查(和散列)更容易出错并且更加类型安全。在Functional Java库中,您将找到Equal (和相应的Hash ),其中相等性被解耦为单个类。它具有从现有实例为类组成Equal实例的方法,以及使用Equal 和Hash 而不是equals和hashCode的Collections,Iterables,HashMap和HashSet的包装器。

What's best about this approach is that you can never forget to write equals and hash method when they are called for. The type system will help you remember.

这种方法的最佳之处在于,在调用它们时,您永远不会忘记编写equals和hash方法。类型系统将帮助您记住。