6

I've got two objects with exactly the same attributes. Don't ask me why, this is generated code and I have to deal with this.

class Bean1 {
   int id;
}

class Bean2 {
   int id;
}

I want to compare objects of each class without writing the compare code for each attribute. This thread explains how to compare two objects but in my case this fails because they are not of instances of the same class.

This code returns false:

EqualsBuilder.reflectionEquals(new Bean1(1), new Bean2(1));

Is there another way to comapre objects and ignore object classes?

jaudo
  • 2,022
  • 4
  • 28
  • 48

4 Answers4

2

I was finally able to do this with Apache Common Lang's ReflectionToStringBuilder, because it provides a ToStringStyle.NO_CLASS_NAME_STYLE option.

Furthermore, as I'm using it with JUnit test, it's useful because if the objects are differents, I will be able to see which field is different.

Assert.assertEquals(
   new ReflectionToStringBuilder(bean1, ToStringStyle.NO_CLASS_NAME_STYLE).toString(),
   new ReflectionToStringBuilder(bean2, ToStringStyle.NO_CLASS_NAME_STYLE).toString());
jaudo
  • 2,022
  • 4
  • 28
  • 48
1

Unless you find a library that does that, you can always write your own reflection code that does what you need:

  • query the fields on your objects to compare
  • compare their content in case both sets of field names are equal
  • not take the actual class into account

If you are really dealing with such "simple" bean classes, implementing this yourself might actually not be too big of a deal.

Alternatively, you could create a generator like

public Bean1 from(Bean2 incoming) { ...

If you know your classes, you can "pull" all values from a Bean2 into a Bean1 instance, and then you can compare two Bean1 instances.

But of course, that idea is not helpful (it requires you to write code to treat all fields, or to again use reflection), but we can easily extend that to a non-reflection way: serialize your Bean2 to JSON, using GSON for example. Then de-serialize that JSON string into a Bean1!

So: if you don't find a library that does that reflection based equal ignoring the class, you should be able to JSON serialization to get to two objects of the same class.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
1

You can use reflection as workaround for your problem, but the best way to do it will be as @imperezivan said, using a common ancestor to have the attributes in the same place.

A reflection implementation will be as easy as getting all the fields of each object, and compare its values. Here you have an example:

public static boolean compareObjects(Object o1, Object o2) {        
        try {
            List<Field> fields1 = getFields(o1);
            List<Field> fields2 = getFields(o2);
            boolean found;
            Field field2Temp = null;
            for (Field field1 : fields1) {
                found = false;
                for (Field field2 : fields2) {
                    if (field1.getName().equals(field2.getName())) {
                        if (!field1.get(o1).equals(field2.get(o2))) {
                            System.out.println("Value " + field1.get(o1) + " for field " + field1 + " does not match the value " + field2.get(o2) + " for field " + field2);
                            return false;
                        }
                        field2Temp = field2;
                        found = true;
                    }
                }
                if (!found) {
                    System.out.println("Field " + field1 + " has not been found in the object " + o2.getClass());
                    return false;
                } else {
                    fields2.remove(field2Temp);
                }
            }
            if (fields2.size() > 0) {
                for (Field field : fields2) {
                    System.out.println("Field " + field + " has not been found in the object " + o1.getClass());
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private static List<Field> getFields(Object o) {
        Field[] fields = o.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
        }
        return new ArrayList<>(Arrays.asList(fields));
    }

As you can see I'm forcing the fields to match, so if one field is not found in the other object, the method will return false, but you can easily change it depending on your needs.

This code gives some details about where the problem is if two objects are different.

With this execution:

public static void main(String args[]){
        Bean1 bean1 = new Bean1(1);
        Bean2 bean2 = new Bean2(1);
        System.out.println("Equals? " + bean1.equals(bean2));
        System.out.println("Reflection equals? " + compareObjects(bean1, bean2));
        bean2 = new Bean2(2);
        System.out.println("Equals? " + bean1.equals(bean2));
        System.out.println("Reflection equals? " + compareObjects(bean1, bean2));
    }

The results you get are:

Equals? false
Reflection equals? true
Equals? false
Value 1 for field private int com.test.so.Bean1.id does not match the value 2 for field private int com.test.so.Bean2.id
Reflection equals? false

If you plan to use this code, please test the edge cases as I haven't done it

raven1981
  • 1,415
  • 1
  • 16
  • 20
0

If your entities have common attributes use an abstract class and override equals method

abstract class Bean {
   private int id;


   public int getId(){
      return id;
   }    

   @Override
   boolean equals(Object obj){
      if(obj is null){
        return false; 
      } else if (obj instanceof Bean) {
          return ((Integer)this.getId()).equals((Bean)obj).getId());
      } else {
         return false;
      }
   }
}

class Bean1 extends Bean {
   //int id;
}

class Bean2 extends Bean  {
   //int id;
}
imperezivan
  • 761
  • 5
  • 13
  • As explained, my classes are generated code, and also I have a lot of attributes so I don't want to write the equal method. – jaudo Feb 06 '19 at 09:28