简体   繁体   中英

Compare two objects excluding some fields - Java

I need to compare two objects of the same class excluding some fields.

public final class Class1 {
  private String a;
  private String b;
  private String c;
:
:
:

  private String z;
  private Date createdAt; 
  private Date updatedAt; 
} 

How can i find if the two objects of the above class are equal excluding createdAt and updatedAt values? Since there are a lot of fields in this class, i don't want to compare each of them one by one.

Please don't give AssertJ's recursive comparison solution as I don't need it for UnitTests.

Thank you in Advance!

If overriding Object::equals and Object::hashCode is not an option, we can use the Comparator API to construct a corresponding comparator:

final Comparator<Class1> comp = Comparator.comparing(Class1::getA)
        .thenComparing(Class1::getB)
        .thenComparing(Class1::getC)
        .
        .
        .
        .thenComparing(Class1::getZ);

Unfortunately, there is no way to do this without comparing all fields that should be equal.

Try overriding equals method like below:

import java.util.Date;
import java.util.Objects;

public final class Class1 {
    private String a;
    private String b;
    private String c;
    private String z;
    private Date createdAt;
    private Date updatedAt;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Class1 class1 = (Class1) o;
        return Objects.equals(a, class1.a) && Objects.equals(b, class1.b) && Objects.equals(c, class1.c) && Objects.equals(z, class1.z);
    }

    @Override
    public int hashCode() {
        return Objects.hash(a, b, c, z);
    }
}

I addition to the Comparator and hashCode() / equals method, you could also use Reflections.

  1. Create an annotation to exclude certain fields:

Blacklisting Example:

@Retention(RetentionPolicy.RUNTIME) //
@Target(ElementType.FIELD) //on class level
public @interface IngoreForEqualCheck { /* tagging only */ }
  1. Use Reflection to analyze the objects you want to compare, by using pClass.getFields() and/or pClass.getDeclaredFields() on the objects' class. This may be even different classes.

  2. Iterate over all fields that are NOT tagged to be ignored, compare values.

Optimizations

  1. To extend on the blacklisting from above, also introduce whitelisting : also create an annotation UseForEqualCheck to only check those fields.

  2. For improved speed, when analyzing the respective class and its fields, you can create iterable lists of the fields to check, and instead of doing the Reflection fields analysis each time, simply use the lists.

  3. Normally you would use equals() on the detected field values. You could also a) tag the class with another custom annotation, or b) check the fields for any whitelisting/blacklisting annotations, so that you will reliably use your new method for embedded/inherited/delegated annotated classes.

Warning

As with all reflections, you might get into trouble when analyzing hierarchies of classes, that have been modified during the compile process (javac) by annotation preprocessors, or by bytecode weaving. This mostly refers to Java EE aka Jakarta, but can happen anywhere where behind-the-scenes functionality is incorporated in your code, or runtime behavior is changed, like with injections, aspect oriented libraries etc.

The quickest way without writing any code is Lombok

Lombok is one of the most used libraries in java and it takes a lot of Boilerplate code off your projects. If you need to read more on what it can and does, go here .

The way to implement what you need is pretty straightforward:

// Generate the equals and HashCode functions and Include only the fields that I annotate with Include
@EqualsAndHashCode(onlyExplicitlyIncluded = true) 
@Getter // Generate getters for each field
@Setter // Generate setters for each field
public class Class1
{

  @EqualsAndHashCode.Include // Include this field
  private Long identity;
  
  private String testStr1; // This field is not annotated with Include so it will not be included in the functions.

  // ... any other fields
}

Lombok can do a lot more than this. For more information on @EqualsAndHashCode refer to this .

You can always use @EqualsAndHashCode.Exclude for a quicker solution to your use case:

@EqualsAndHashCode
@Getter // Generate getters for each field
@Setter // Generate setters for each field
public final class Class1 {
  private String a;
  private String b;
  private String c;
:
:
:

  private String z;

  @EqualsAndHashCode.Exclude
  private Date createdAt; 
  @EqualsAndHashCode.Exclude
  private Date updatedAt; 
} 

@Renis1235 's Lombok solution is the easiest. However if for some reason in different contexts the equals can mean different things and you don't want to change the default Equals and Hashcode behaviour, I would advice to assign default values to the fields you want to exclude, and then use equals. (Assuming you can change the objects)

For example:

Class1 a = ...;
Class1 b = ...;
a.createdAt = null;
b.createdAt = null;
a.updatedAt = null;
b.updatedAt = null;
boolean isEqualExcludingTimestamps = a.equals(b);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM