tags:

views:

474

answers:

7

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.

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?

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.

+1  A: 

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

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

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

OscarRyz
+2  A: 

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:

@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;
 }
Pyrolistical
Be careful with this template. Don't use the super.equals call unless you're implementing a subclass to a class that has already implemented equals() - otherwise this method just reduces to reference equality. Also the line with the class cast has a syntax error.
Dave L.
You are correct, I took this out of a class where I was doing an equals on a subclass
Pyrolistical
+3  A: 

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.

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

Steve B.
+1  A: 

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:

  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. 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.
  3. 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).

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.

Yar
+2  A: 

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

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


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

From 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.

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

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.

From Effective Java:

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

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

  1. Favour composition over inheritance
  2. Design and document for inheritance or else prohibit it


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.

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.

McDowell
All of these were great answers. The reason I accepted your answer is because it comes closest to addressing the question I put forward. Thanks!
hal10001
A: 

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.

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.

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.

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")));
    }
}
Chris B.
A: 

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.

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.

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.

Apocalisp