22

I've got a Java class, here's an example:

public class Car {

    private int fuelType;
    private Date made;
    private String name;
.
.
. // and so on

Now let's say I have two car objects and I want to compare if all their variables are equal.

Right now, I've solved this by overriding method equals(Object o) and I check if all the variables match in both objects.

The problem here is that if I have 20 classes, I'll have to override equals(Object o) in every single one of them.

Is there a way create some sort of universal method that could compare any of the two objects that I pass to it and let me know if they match in every single variable or not?

Nathaniel Waisbrot
  • 23,261
  • 7
  • 71
  • 99
Guy
  • 6,414
  • 19
  • 66
  • 136
  • 5
    if using eclipse right click -> source-> generate equals and hash. the generated equals will compare using whatever the fields you set it up with. by the way, yes, that is the std method `Object.equals(Object other)`. – eduyayo Oct 13 '15 at 13:40
  • 5
    Yup. You can create a helper that uses some reflection and end up with brittle code that's just *bound* to create problems at some point. Or you can suck it up once and work through the 20 generation windows offered by your IDE. – Jeroen Vannevel Oct 13 '15 at 13:41
  • 1
    That's the simplest way, you can also use reflection: http://stackoverflow.com/questions/1449001/is-there-a-java-reflection-utility-to-do-a-deep-comparison-of-two-objects – Luis Alves Oct 13 '15 at 13:42

7 Answers7

23

You have a few options for automating Equals & Hashcode (option #3 BLEW MY MIND!):

  1. Your IDE. I would not recommend it for most objects as they can slowly drift out of date with the actual class definition. They also look ugly and pollute your codebase with boilerplate code.
  2. Apache Commons has a bunch of stuff for making this easier, including a reflective version so no risk of drifting out of date with the class definition. It is better than #1 unless you require a speedy equals/hashcode, but still too much boilerplate for my liking.
  3. Project Lombok and annotation processing. Whack an EqualsAndHashCode annotation on ya class and be done with it. I recommend using Project Lombok. It adds a touch of magic into the build (but not much) and so requires a plugin for your IDE to behave nicely but they are a small price to pay for no boilerplate code. Lombok is an annotation processor that run at compile time so you have no runtime performance hit.
  4. Using a different language that supports it out the box, but also targets the JVM. Groovy uses an annotation and Kotlin supports data classes. Unless your existing code can quickly be converted, I would avoid this.
  5. Google's Auto has an AutoValue. Like Project Lombok this is an annotation processor, however has less magic at the expense of little more boilerplate (thanks to Louis Wasserman)
Community
  • 1
  • 1
Michael Lloyd Lee mlk
  • 14,561
  • 3
  • 44
  • 81
  • 1
    Alternately, if you dislike magically generated methods, https://github.com/google/auto/tree/master/value achieves similar goals to Lombok with less magic. [This section](https://github.com/google/auto/tree/master/value#alternatives) discusses some of the differences with Lombok. – Louis Wasserman Oct 13 '15 at 16:58
  • 2
    In my experience (working on some relatively large 250k+ LOC Java applications) reflection tends to make code harder to understand and maintain and should be well thought through before introducing it (ignoring possible performance problems, that can easily be solved later on a case by case basis here anyhow). I can't remember a single bug due to forgetting to adapt the equals method when changing the state of a data function. Such data classes tend to be reasonably short and simple, so this is easily caught in code reviews at least. – Voo Oct 13 '15 at 21:32
  • Thanks for the detailed answer. I went with the #1 option for now, but I really love the #3 as you said. #1 is sufficient for now but if I ever get to the point when it's a hassle to generate new code when classes change, I'll just use #3. – Guy Oct 14 '15 at 07:47
  • @Voo - I've worked on big and small projects. I've definitely come across bugs introduced by forgetting to update the equals method (even with code reviews). While in general I agree reflection add unneeded magic, when wrapped up nicely in an API I personally have no issue with it. – Michael Lloyd Lee mlk Oct 14 '15 at 08:39
  • @LouisWasserman Thanks, I will take a look and update the answer to include it. – Michael Lloyd Lee mlk Oct 14 '15 at 08:39
  • 1
    @mlk I don't want to come across as always being against reflection (there are definitely use cases where the additional mental overhead is worth the price), I just came around to being being more guarded about it over the years - bad experiences. I agree that it's pretty harmless since it's wrapped up nicely and a pure implementation detail here. So on introspection I might be overcautious here. – Voo Oct 14 '15 at 19:18
  • reflection-equals - yay – Kalpesh Soni Sep 19 '18 at 19:45
6

you can use :

 org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare(Object lhs, Object rhs);

it uses reflection to compare the fileds here is the javadoc : javadoc

hic1086
  • 755
  • 3
  • 10
  • 1
    Juste pay attention to the use of refelction because this type of operation is always going to be slower than just doing the same operation directly. For more details look here: http://www.ibm.com/developerworks/library/j-dyn0603 (Reflection performance paragraph) – hic1086 Oct 13 '15 at 14:10
4

I'll take the dissenting opinion to the majority (use apache commons with reflection) here: Yes, this is a bit code you have to write (let your IDE generate really), but you only have to do it once and the number of data classes that need to implement equals/hashcode is generally rather manageable - at least in all of the large projects (250k+ LOC) I worked on.

Sure if you add a new member to the class you will have to remember to update the equals/hashcode functions, but that's generally easy to notice, at the latest during code reviews.

And honestly if you use a simple little helper class that's even in Java7, you can cut down the code that Wana Ant showed immensely. Really all you need is:

@Override
public boolean equals(Object o) {
    if (o instanceof Car) { // nb: broken if car is not final - other topic
        Car other = (Car) o;
        return Objects.equals(fuelType, other.fuelType) && 
               Objects.equals(made, other.made) && 
               Objects.equals(name, other.name);
    }
    return false;
}

similar for hashcode:

@Override
public int hashCode() {
    return Objects.hash(fuelType, made, name);
}

Not as short as the reflection solution? True, but it's simple, easy to maintain, adapt and read - and performance is orders of magnitude better (which for classes that implement equals and hashcode is often important)

Voo
  • 29,040
  • 11
  • 82
  • 156
3

Typically you can generate equals/hashCode methods by your IDE - all big players in this field are capable of that (Eclipse, IntelliJ Idea and Netbeans).

Generally you can create some code that will use reflection but I don't recommend this one as objective approach is clearer and more maintainable. Also reflection won't be as fast as "standard" way. If you really want to go this way, there exist utilities like EqualsBuilder and HashCodeBuilder.

Just for your information, there are JVM-based languages that already support these features, e.g. Kotlin data classes, which can be pretty nicely used in existing Java projects.

zdenda.online
  • 2,451
  • 3
  • 23
  • 45
2

I'll just throw in a plug for my favorite solution to this problem: @AutoValue.

This is an open-source project from Google that provides an annotation processor that generates a synthetic class that implements equals and hashCode for you.

Since it's auto-generated code, you don't have to worry about accidentally forgetting a field or messing up the equals or hashCode implementation. But since the code is generated at compile time, there's zero runtime overhead (unlike reflection-based solutions). It's also "API-invisible" -- users of your class can't tell the difference between an @AutoValue type and a type you implemented yourself, and you can change back and forth in the future without breaking callers.

See also this presentation which explains the rationale and does a better job comparing it to other approaches.

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
1

Theoretically you could use reflection to create some kind of util, as many people suggest you in comments. Personally I don't recommend you to do it. you will end up with something which is partially working.

Many things in Java rely on equal or hashCode, for example method contains which you can find in anything which implements Collection.

Overriding equal (and hashCode) is recommended solution. By addition, i think any decent IDE will have option to generate them for you. Hence you can do it quicker than by using reflection.

user902383
  • 8,420
  • 8
  • 43
  • 63
0

That's the way I would do it:

@Override
public boolean equals(Object obj) {
    if (obj instanceof Car) {
        return internalEquals((Car) obj);
    }
    return super.equals(obj);
}

protected boolean internalEquals(Car other) {
    if(this==other){
        return true;
    }
    if (other != null) {
        //suppose fuelType can be Integer.
        if (this.getFuelType() !=null) {
            if (other.getFuelType() == null) {
                return false;
            } else if (!this.getFuelType().equals(other.getFuelType())) {
                return false;
            }
        } else if(other.getFuelType()!=null){
            return false;
        }
        if (this.getName() != null) {
            if (other.getName() == null) {
                return false;
            } else if (!this.getName().equals(other.getName())) {
                return false;
            }
        }
        else if(other.getName()!=null){
            return false;
        }
         if (this.getDate() != null) {
            if (other.getDate() == null) {
                return false;
            } else if (!this.getDate().getTime()!=(other.getDate().getTime())) {
                return false;
            }
        }
        else if(other.getDate()!=null){
            return false;
        }
        return true;
    } else {
        return false;
    }
}

EDIT
Simplified version

     public class Utils{
          /**
           * Compares the two given objects and returns true, 
           * if they are equal and false, if they are not.
           * @param a one of the two objects to compare
           * @param b the other one of the two objects to compare
           * @return if the two given lists are equal.
           */
           public static boolean areObjectsEqual(Object a, Object b) {

               if (a == b){
                  return true;
               }
               return (a!=null && a.equals(b));
          }

          public static boolean areDatesEqual(Date a, Date b){
             if(a == b){
                return true;
             }
             if(a==null || b==null){ 
                return false;
             }
             return a.getTime() == b.getTime();
          }
   }

   @Override
   public boolean equals(other obj) {
      if(this == other){
         return true;
      } 
      if(other == null){
          return false;
      }
      if (other instanceof Car) {
          return internalEquals((Car) other);
      }
      return super.equals(obj);
   }

   protected boolean internalEquals(Car other) {        
        //suppose fuelType can be Integer.
        if (!Utils.areObjectsEqual(this.getName(), other.getName()){                   
            return false;
        }
        if (!Utils.areObjectsEqual(this.getName(), other.getName()){ 
            return false;
        }
        if (!Utils.areDatesEqual(this.getDate(), other.getDate()){ 
            return false;
        } 
        return true;
    }
}

Also don't forget about hashcode, they code hand in hand.

Andrei T
  • 2,985
  • 3
  • 21
  • 28
  • First `equals` has to handle being called with null as an argument so half the null checks are unnecessary. Second I'd recommend a simple equalsHelper class that handles the case when the own property can be null to vastly simplify the code. It's nothing but `o1 == o2 || (o1 != null && o1.equals(o2)` and vastly simplifies your code. Edit: It's even by default in Java7 [see here](http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#equals%28java.lang.Object,%20java.lang.Object%29). – Voo Oct 13 '15 at 21:39
  • yeah, that's what I have in my app(utility class), but he wanted some equals for his stuff. Also, regarding null, at the beginning of the equals maybe you can handle null object checks. Regarding the rest, I know the null part, I just wanted him to see how it works. – Andrei T Oct 13 '15 at 21:51
  • Honestly that giant amount of code when compared to what's actually necessary is probably just going to scare him off - it would certainly scare me if I had to write stuff like that. – Voo Oct 13 '15 at 22:08
  • It depends on the experience, at the beginning you must - my opinion, understand the insight, next, you can simplify. Or at least that was my path. – Andrei T Oct 13 '15 at 22:38