10

I am using hibernate and need to override equals and hashCode(). I chose to use google-guava's equals and hashCode helpers.

I wanted to know if I am missing something here.

I have get/set methods for idImage and filePath.

@Entity
@Table(name = "IMAGE")
public class ImageEntity {
    private Integer idImage;
    private String filePath;

    @Override
    public int hashCode() {
        return Objects.hashCode(getFilePath());
    }

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

        if(obj instanceof ImageEntity){
            final ImageEntity otherImage = (ImageEntity) obj;
            return Objects.equal(getFilePath(), otherImage.getFilePath());
        }
        return false;
    }
}

EDIT:

Ran into inheritance and have a sample here

Community
  • 1
  • 1
brainydexter
  • 19,826
  • 28
  • 77
  • 115

3 Answers3

18

The problem with the instanceof operator is that it works taking into account polymorphism, if I may say so.

Let's say, for example, that you do this:

public class AdvancedImageEntity extends ImageEntity
{
    //some code here
}

and then you do this:

ImageEntity ie = new ImageEntity ();
AdvancedImageEntity advanced_ie = new AdvancedImageEntity ();

boolean this_will_be_true = ie.equals (advanced_ie);

As the name suggests, that equals call will return true, because of the instanceof operator.

I know this sounds like basic stuff and most people know it, but it's SOOOO damn easy to forget it. Now, if you want that behaviour, then it's fine, you implemented equals correctly. But if you consider that an ImageEntity object must not be equal to an (hypothetical) AdvancedImageEntity object, then either declare ImageEntity to be final OR forget about instanceof and implement your equals method like this:

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

    if (getClass ().equals (obj.getClass ()))
    {
        final ImageEntity otherImage = (ImageEntity) obj;

        return Object.equals (getFilePath(), otherImage.getFilePath());
    }

    return false;
}

This will check the object's true type, no matter what type the reference is. If the obj parameter is an instance of a subclass, it would "slip" by instanceof. But getClass is a lot more strict and won't allow it.

PS: I'm not saying that instanceof is bad and should not be used. I'm just saying that you must be aware of this particular situation and decide whether to use it taking this into account.

Radu Murzea
  • 10,724
  • 10
  • 47
  • 69
  • 1
    Better yet--don't subclass DTOs – Ray Feb 13 '12 at 14:57
  • 1
    http://en.wikipedia.org/wiki/Data_transfer_object; roughly equivalent to value objects, if you're more familiar with that term. In this case, you'd want to favor composition over inheritance. – Ray Feb 16 '12 at 19:31
  • I think first two `if` statements in `equals` are redundant because the Guava's `Object#equals` do those. – Sanghyun Lee Aug 02 '12 at 09:45
  • @Sangdol The `equals` method is overriden. There is no call anywhere to `super.equals()`. This means that whatever is going on in `Object#equals` is irrelevant (because it's never executed). – Radu Murzea Aug 02 '12 at 09:52
  • Oh, I misread your code. I thought you used Guava's `Objects#equal`. Check [this code](http://grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/r09/com/google/common/base/Objects.java#Objects.equal%28java.lang.Object%2Cjava.lang.Object%29) – Sanghyun Lee Aug 02 '12 at 10:02
  • @Sangdol I understand now what you meant. I think I should edit my answer though... I totally forgot about `obj instanceof A` where `A` is an interface, not a class. Maybe later tonight :) . – Radu Murzea Aug 02 '12 at 10:06
  • What's the purpose of comparing class names rather than classes? – Dave Newton Aug 14 '12 at 12:33
  • @DaveNewton Hmmm, I guess I thought that either works. But actually... what if both classes have the same name but are in different packages ? You probably have a point here, let me look into it :) . – Radu Murzea Aug 14 '12 at 13:05
  • `getName` returns the FQN, AFAIK. It just seems redundant. – Dave Newton Aug 14 '12 at 13:08
  • "Don't subclass DTOs" may not be possible with tools like Hibernate, which subclass your model objects with proxies to implement features like lazy loading. – Marvo Nov 25 '12 at 00:10
13

You could actually use the Guava EqualsTester to test your equals and hashCode implementation:

new EqualsTester()
     .addEqualityGroup("hello", "h" + "ello")
     .addEqualityGroup("world", "wor" + "ld")
     .addEqualityGroup(2, 1 + 1)
     .testEquals();

It's in the guava testlib.

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava-testlib</artifactId>
    <scope>test</scope>
</dependency>

Minor change to your implementation:

@Override public boolean equals(final Object obj) {
    if(obj == this) return true;
    return obj instanceof ImageEntity &&
        Objects.equal(getFilePath(), ((ImageEntity) obj).getFilePath());
}
Thomas Jung
  • 32,428
  • 9
  • 84
  • 114
3

Its fine as is. Although instanceof makes the null check unnessary.

Also, I think its fine to use auto generated id's, although this is very debatable.

Community
  • 1
  • 1
NimChimpsky
  • 46,453
  • 60
  • 198
  • 311