Overriding Object’s equals() method

 


Plan

The equals(Object) method : principles and simple example

Overriding equals() in a way that tries to be too clever may violate the equals contract

When the parent class already overrided equals() and we can invoke the equals() method on a instance of the parent, there is no way to override equals() in the subclass while respecting the equals contract

When the parent class already overrided equals() and we can invoke the equals() method on a instance of the parent, we should prevent from making the two equals()  method work with only instances these two specific classes.


Brief definition of the equals() method 

The public boolean equals(Object obj) method  comes from the Object class.
According to its javadoc, it indicates whether some other object (the object passed as argument) is « equal to » this one.

Here, the  equality is a logical equality that we have to override if we deem that the default implementation of the equals() method doesn’t address the logical equality of the class.

Default implementation of the equals() method and when override it

According to the javadoc, the equals() method of the Object class implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
It means that  two objects are equals according to the equals() method if these refer to the same reference.
For example, with the following Address class that doesn’t override equals() :

public class Address {
 
    private String city;
    private String country;
 
    public Address(String city, String country) {
	this.city = city;
	this.country = country;
    }
}

This code :

Address address = new Address("my city", "my country");
Address addressReferencingTheSameObject = address;
Address otherAddress = new Address("my city", "my country");
 
System.out.println("is address == addressReferencingTheSameObject ? " + (address == addressReferencingTheSameObject));
System.out.println("is address.equals(addressReferencingTheSameObject) ? " + address.equals(addressReferencingTheSameObject));
 
System.out.println("is address == otherAddress ? " + (address == otherAddress));
System.out.println("is address.equals(otherAddress) ? " + address.equals(otherAddress));

produces the following output :

is address == addressReferencingTheSameObject ? true
is address.equals(addressReferencingTheSameObject ) ? true
is address == otherAddress ? false
is address.equals(otherAddress) ? false

address and addressReferencingTheSameObject refer to the same object (first output).
So, these are also equal according to the Object equals() method (second output).
address and otherAddress refer to two distinct objects (third output).
So, these are also not equal according to the Object equals() method (fourth output).

The default implementation of equals() has to be overriden when here:

Address address = new Address("my city", "my country");
Address otherAddress = new Address("my city", "my country");

we are aware that address == otherAddress will always be false but we need that address.equals(otherAddress) returns true as we consider that the city and the country fields identify an Address object. It means that if these fields are equal between two Address objects, these objects should be equal within the meaning of the equals() method.

The general contract to respect when we override the equals() method

Here is the part of the javadoc that exposes the general contract:

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

It specifies 5 points.

Three of them are very easy to respect : 

It is reflexive. An equals() implementation that could make this condition true :
a.equals(a) == false would be extremely tricky.

It is consistent. Violating this principle means generally that the implementation relies not only on the state of the objects to evaluate their equality. It very probably relies on external derived information which the value may change through the time even if information used in equals()  has not changed.
In addition to be more expensive in processing time, it’s also very tricky.

 For any non-null reference value x, x.equals(null) should return false. It means two things. A non null object should never be equal to null. Which seems obvious.
And it also means that we have to return something when equals() is invoked. It implies that passing a null object in the equals() method must not rise any exception that could make the method not return, such as NullPointerException.
So, we have to add the required null checks.
This also is rather natural as beyond the equals() method, any source code should be enough robust to prevent  any avoidable exception from being thrown.
Besides, this rule is enough easy to respect in so far as if we don’t respect this rule, an exception will be thrown. And generally, except if you shock them, you will have a fast feedback of the problem and so the issue will be early corrected.

Two of them are much easier to violate, even in implementations that look fine :

It is symmetric.

It is transitive.

At last, beyond respecting the mentioned rules of the equals() contract, when we override it, it is also strongly advised to override hashCode() in a consistent way with equals() as according to the specification of the language, if two objects are equals in terms of equals() method, invoking hashCode() on them has to return the same int value.
But the reverse is not true as two objects may have the same hashCode() value but not be equal in terms of equals() method. Look at this other post (to do) to know more about hashCode().

A simple example of equals() overriding that respects the contract

We will reuse the Address class that contains two fields : city and country.
We want to override equals() in a way where two MyAddress  objects should be considered as equal if these have the same city and the same country.
Otherwise, it should return false.
Here is the class :

public class Address {
 
    private String city;
    private String country;
 
    public Address(String city, String country) {
	this.city = city;
	this.country = country;
    }
 
    public String getCity() {
	return city;
    }
 
    public String getCountry() {
	return country;
    }
}

Here is the unit test that asserts we respect the 5 rules of the equals() contract.
But we also return false when the logical equality rule is not respected. That is, when both city and country don’t match in the two compared objects.

import org.junit.Test;
 
import junit.framework.Assert;
 
public class AddressTest {
 
    /*
     * it has to be reflexive: for any non-null reference value x, x.equals(x) should return true.
     */
    @Test
    public void equals_is_reflexive() throws Exception {
	Address address = new Address("my city", "my country");
	Assert.assertTrue(address.equals(address));
    }
 
    /*
     * It has to be consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is
     * modified.
     */
    @Test
    public void equals_is_consistent() throws Exception {
	Address addressOne = new Address("my city", "my country");
	Address addressOther = new Address("my city", "my country");
	Assert.assertTrue(addressOne.equals(addressOther));
	Assert.assertTrue(addressOne.equals(addressOther));
	Assert.assertTrue(addressOne.equals(addressOther));
    }
 
    /*
     * for any non-null reference value x, x.equals(null) should return false.
     */
    @Test
    public void equals_with_null_argument_returns_false() throws Exception {
	Address addressOne = new Address("my city", "my country");
	Assert.assertFalse(addressOne.equals(null));
    }
 
    /*
     * It has to be symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
     */
    @Test
    public void equals_is_symmetric() throws Exception {
	Address addressOne = new Address("my city", "my country");
	Address addressOther = new Address("my city", "my country");
	Assert.assertTrue(addressOne.equals(addressOther));
	Assert.assertTrue(addressOther.equals(addressOne));
    }
 
    /*
     * It has to be transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
     */
    @Test
    public void equals_is_transive() throws Exception {
	Address addressOne = new Address("my city", "my country");
	Address addressOther = new Address("my city", "my country");
	Address addressStillAnother = new Address("my city", "my country");
	Assert.assertTrue(addressOne.equals(addressOther));
	Assert.assertTrue(addressOther.equals(addressStillAnother));
	Assert.assertTrue(addressOne.equals(addressStillAnother));
    }
 
    /*
     * We validated in above tests that the equals() implementation respects the equals() contracts but
     * we also have to ensure that it returns false when it the two objects are not logically equals
     */
    @Test
    public void equals_with_different_city_or_country_returns_false() throws Exception {
	Address addressOne = new Address("my city", "my country");
	// First case
	Address addressOther = new Address("my other city", "my country");
	Assert.assertFalse(addressOne.equals(addressOther));
	// Second case
	addressOther = new Address("my city", "my other country");
	Assert.assertFalse(addressOne.equals(addressOther));
    }
 
}

Here is the equals() implementation :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle (lipskov principle)
	if (!(obj instanceof Address))
	    return false;
 
	// fail cases
	Address other = (Address) obj;
	if (!Objects.equals(city, other.city))
	    return false;
	if (!Objects.equals(country, other.country))
	    return false;
 
	// remaing case : successful
	return true;
    }

Some interesting things to note :

1) We can see that we keep a part of the original behavior of  Object equals(). Indeed, if the objects refer to the same reference, we return true. Adding it is cheap and may optimize the processing time of the equals() method if Address instances are in a some way cached and reused. 

2) The if (!(obj instanceof Address)) return false; statement allows both to check the compatibility of the parameter with the current object but also to check that it is not null as instanceof used with null returns false.

3) The Objects.equals(Object, Object) utility method introduced in Java 7 spares null checking as it checks that the passed objects are not null before trying to apply the equals() method otherwise it returns false.  Besides, it compares first the reference of the objects to be more effective. Here is its implementation :
return (a == b) || (a != null && a.equals(b));

Overriding equals() in a way that evaluates the value of the object based on the value of the other object violates the symmetry and the transitivity rule

Suppose we have a Computation class that owns a result integer field.
We want to override equals() in a way where we consider two Computation objects as equal whether their result is the same with a margin of 10 percents.

Suppose this naive implementation :

public class Computation {
 
    private int result;
 
    public Computation(int result) {
	this.result = result;
    }
 
    public int getResult() {
	return result;
    }
 
    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle (lipskov principle)
	if (!(obj instanceof Computation))
	    return false;
 
	// cast and comparison
	Computation other = (Computation) obj;
	float prct = (other.result - result) / (float) result;
	return prct <= 0.1F && prct >= 0;
    }
 
}

This violates the symmetry rule.
Suppose this code :

Computation computation = new Computation(100);
Computation otherComputation = new Computation(110);
Assert.assertTrue(computation.equals(otherComputation));
Assert.assertTrue(otherComputation.equals(computation));

The first assertion is successful as according to the equals() method, prct = 0.1 ((110-100)/100 = 0.1) and 0.1 is in the bounds >=0 and <=0.1.
But the second assertion fails as according to the equals() method, prct =~ -0.091 ((100-110)/110 =~ -0.091) and -0.091 is not in the bounds >=0 and <=0.1.
The equals() implementation doesn’t ensure the symmetry as the percentage between two Computation objects depends on which object the equals() method is invoked.
We could write the equals() method in a way where we compare always the same way the two objects. We could for example compute the percentage from the min result to the max result.
It could give this implementation :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle (lipskov principle)
	if (!(obj instanceof Computation))
	    return false;
 
	// cast and comparison
	Computation other = (Computation) obj;
 
	 int max = Math.max(result, other.result);
	 int min = result == max ? other.result : result;
 
	 float prct = (max-min)/(float) min;
 
	 return prct <= 0.1F && prct >= 0;
    }

Now this tests pass :

Computation computation = new Computation(100);
Computation otherComputation = new Computation(110);
Assert.assertTrue(computation.equals(otherComputation));
Assert.assertTrue(otherComputation.equals(computation));

But is the transitivity respected ? No it doesn’t and it was not the case in the first version either.
Suppose this code :

Computation computation = new Computation(100);
Computation otherComputation = new Computation(108);
Computation stillOtherComputation = new Computation(115);
Assert.assertTrue(computation.equals(otherComputation));
Assert.assertTrue(otherComputation.equals(stillOtherComputation));
Assert.assertTrue(computation.equals(stillOtherComputation));

The two first assertions are successful as in the two equality tests, there are not more than 10% between the result values of the compared objects.
According to the transitivity rule, if computation.equals(otherComputation) and otherComputation.equals(stillOtherComputation), then computation has to be equal to stillOtherComputation. But it is not the case as the result field is 100 for the first and is 115 for the last one. So we have more than 10% of difference between them.
The problem is that in equals() we don’t compare only the values from one object against those from another object. We indeed evaluate the value of each object by relying on the value of the other object. It is a crossed evaluation.
In these conditions, object1 and object2 may be equal because their crossed evaluation matches. It may also be true for object2 and object3.
But nothing ensures that the crossed evaluation of object1 and object3 matches too.

So, the equals() method should always compare two objects with their intrinsic values.

Overriding equals() in a way that interoperates with other classes violates the symmetry rule.

Suppose we have two classes representing an address.
The first one comes from a third party library (AddressFromAnotherLib class) and the other one is one of our own classes (Address class).
To make things simple, these own exactly the same fields.

public class Address {
 
    private String city;
    private String country;
 
    public Address(String city, String country) {
	this.city = city;
	this.country = country;
    }
 
    public String getCity() {
	return city;
    }
 
    public String getCountry() {
	return country;
    }
}
 
public class AddressFromAnotherLib {
 
    private String city;
    private String country;
 
    public AddressFromAnotherLib(String city, String country) {
	this.city = city;
	this.country = country;
    }
 
    public String getCity() {
	return city;
    }
 
    public String getCountry() {
	return country;
    }
}

Suppose now we want to make our Address equals() method flexible by making it able to handle the equality with instances of AddressFromAnotherLib.

We could implement equals() in this way in Address :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// try to handle the equality with a very close address class
	if (obj instanceof AddressFromAnotherLib) {
	    AddressFromAnotherLib other = (AddressFromAnotherLib) obj;
	    if (!Objects.equals(city, other.getCity()))
		return false;
	    if (!Objects.equals(country, other.getCountry()))
		return false;
 
	    return true;
	}
 
	// null check + cast respecting the substituable classes principle (lipskov principle)
	if (!(obj instanceof Address))
	    return false;
 
	// fail cases
	Address other = (Address) obj;
	if (!Objects.equals(city, other.city))
	    return false;
	if (!Objects.equals(country, other.country))
	    return false;
 
	// remaining case : successful
	return true;
    }

It seems fine as now that is successful :

Address address = new Address("my city", "my country");
AddressFromAnotherLib addressFromAnotherLib = new AddressFromAnotherLib("my city", "my country");
Assert.assertTrue("Address equals to AddressFromAnotherLib", address.equals(addressFromAnotherLib));

But if we reverse the objects used in the equality test, the assertion doesn’t pass any longer :

Assert.assertTrue("AddressFromAnotherLib equals to Address", addressFromAnotherLib.equals(address));

Why ? Because we handle both equality cases (Address and AddressFromAnotherLib) only in Address equals() implementation.
If AddressFromAnotherLib class is not modifiable, we could not change it to take into consideration the Address instances.
But even if AddressFromAnotherLib was modifiable, we should not try to do it.
It would create a two way coupling between the two classes that could break the client code when the dependent class changes its implementation.

Other problem : suppose AddressFromAnotherLib owns a district field that Address doesn’t have and also suppose  that AddressFromAnotherLib.equals() considers this field in.
It would violate the transitivity as the Address equals() doesn’t consider the district field while AddressFromAnotherLib considers it.
An AddressFromAnotherLib could be equal to an Address. This Address could be equal to another AddressFromAnotherLib but the first AddressFromAnotherLib could be not equal to the second AddressFromAnotherLib as only in this last equality test, the district field is considered. This code illustrates the problem :

AddressFromAnotherLib addressWithDistrict = new AddressFromAnotherLib("my district", "my city", "my country");
Address address = new Address("my city", "my country");
AddressFromAnotherLib anotherAddressWithDistrict = new AddressFromAnotherLib("my other district", "my city", "my country");
 
Assert.assertTrue(addressWithDistrict.equals(address));
Assert.assertTrue(address.equals(anotherAddressWithDistrict));
Assert.assertTrue(addressWithDistrict.equals(anotherAddressWithDistrict));

The last assertion fails as the district field in the objects referenced by addressWithDistrict and anotherAddressWithDistrict have not the same value (« my district » versus « my other district »)

Overriding equals() violates the symmetry rule if in the subclass we don’t consider the parent type in the instanceof check

Suppose we have the Address class that defines equals() as we have just seen :

import java.util.Objects;
 
public class Address {
 
    private String city;
    private String country;
 
    public Address(String city, String country) {
	this.city = city;
	this.country = country;
    }
 
    public String getCity() {
	return city;
    }
 
    public String getCountry() {
	return country;
    }
 
    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle
	// (lipskov principle)
	if (!(obj instanceof Address))
	    return false;
 
	// fail cases
	Address other = (Address) obj;
	if (!Objects.equals(city, other.city))
	    return false;
	if (!Objects.equals(country, other.country))
	    return false;
 
	// remaining case : successful
	return true;
    }
 
}

So far, so good. Suppose now we declare, AddressWithDistrict, a class that extends Address.
This new class adds a district field :

public class AddressWithDistrict extends Address {
 
    private String district;
 
    public AddressWithDistrict(String district, String city, String country) {
	super(city, country);
	this.district = district;
    }
 
    public String getDistrict() {
	return district;
    }

We wish override equals() in AddressWithDistrict in order to use country and city as in the parent class but also use the district field.
Suppose we override it in a similar way that it was done in Address :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// doesn't work in this way but work form the parent class
	if (!(obj instanceof AddressWithDistrict)) {
	    return false;
	}
	AddressWithDistrict other = (AddressWithDistrict) obj;
	return super.equals(other) && district.equals(other.district);
    }

This violates the symmetry rule.

For example here :

Address addressWithDistrict = new AddressWithDistrict("my district", "my city", "my country");
Address address = new Address("my city", "my country");
 
Assert.assertTrue(address.equals(addressWithDistrict));
Assert.assertTrue(addressWithDistrict.equals(address));

The first assertion is successful as the Address class considers as valid argument any instance of Address and an AddressWithDistrict object is indeed an instance of Address.
So this : if (!(obj instanceof Address)) is false and the comparison may be performed and is successful.
But the second assertion fails as the AddressWithDistrict class considers as valid argument any instance of AddressWithDistrict and an Address object is not a instance of AddressWithDistrict.
So this : if (!(obj instanceof AddressWithDistrict)) is true. As a result, equals() returns false.

Considering the parent type in the instanceof check may be a workaround but it will not work either. It is the next point.

Overriding equals() violates the transitivity rule if in the subclass we consider the parent type in the instanceof check

In the AddressWithDistrict equals() method, we could avoid returning false when we receive as parameter an Address object by replacing
if (!(obj instanceof AddressWithDistrict)) { return false;} by
if (!(obj instanceof Address)) { return false;}
This AddressWithDistrict equals() implementation respects now the symmetry rule :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// !!! compare with 2)AddressWithDistrict but also with 1)Address
	if (!(obj instanceof Address)) {
	    return false;
	}
 
	// 1) compare AddressWithDistrict with address by
	if (!(obj instanceof AddressWithDistrict)) {
	    return super.equals(this);
	}
 
	// 2) compare AddressWithDistrict with AddressWithDistrict
	AddressWithDistrict other = (AddressWithDistrict) obj;
	return super.equals(other) && district.equals(other.district);
    }

These assertions are now successful :

AddressWithDistrict addressWithDistrict = new AddressWithDistrict("my district", "my city", "my country");
Address address = new Address("my city", "my country");	
 
// symmetry ok
Assert.assertTrue(address.equals(addressWithDistrict));
Assert.assertTrue(addressWithDistrict.equals(address));

But the problem is that now the implementation doesn’t respect the transitive rule.
In the following code, the last assertion (the transitive equality) fails.

AddressWithDistrict addressWithDistrict = new AddressWithDistrict("my district", "my city", "my country");
Address address = new Address("my city", "my country");	
AddressWithDistrict anotherAddressWithDistrict = new AddressWithDistrict("my other district", "my city", "my country");
 
Assert.assertTrue(addressWithDistrict.equals(address));
Assert.assertTrue(address.equals(anotherAddressWithDistrict));
Assert.assertTrue(addressWithDistrict.equals(anotherAddressWithDistrict));

The problems is that Address equals() doesn’t consider the district value and AddressWithDistrict equals() doesn’t also consider it when it is invoked with an Address argument.
So, the two first assertions don’t bother with the district value.
But AddressWithDistrict considers district when it is invoked with an AddressWithDistrict argument.
So, the last assertion considers the district and it fails.

As we have just seen, trying to handle the equality between the equals() methods of two classes which one is the subclass of the other one cannot respect the equals() contract.
So, even if it may seem a flexible way to provide the equality between these two classes , we should not allow this  for a simple reason : it could not work.
We will see how to disallow that in the next point.

Overriding equals() respects the equals contract but defeats some of the benefits of the OOP if we replace the instanceof check by a getClass() check

A simple way to solve this problem is implementing equals() in a way where we compare  instances of the same specific class between them. If it is not case, we return false.
We will reuse the Address class and AddressWithDistrict, its subclass to illustrate that.
In Address, we could override equals() in this way :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle
	// (NOT RESPECT OF lipskov principle)
	if (obj == null || obj.getClass() != getClass())
	    return false;
 
	// fail cases
	Address other = (Address) obj;
	if (!Objects.equals(city, other.city))
	    return false;
	if (!Objects.equals(country, other.country))
	    return false;
 
	// remaining case : successful
	return true;
    }

And in AddressWithDistrict we could override equals() in this way :

    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	// null check + cast respecting the substituable classes principle
	// (NOT RESPECT OF lipskov principle)
	if (obj == null || obj.getClass() != getClass()) {
	    return false;
	}
 
	AddressWithDistrict other = (AddressWithDistrict) obj;
	return super.equals(other) && district.equals(other.district);
    }

These implementations respect the equals() contract as now the Address and AddressWithDistrict comparisons are not allowed any longer.

These assertion are successful :

Address address = new Address("my city", "my country");
AddressWithDistrict addressWithDistrict = new AddressWithDistrict("district", "my city", "my country");
 
Assert.assertFalse(address.equals(addressWithDistrict));
Assert.assertFalse(addressWithDistrict.equals(address));

But that way of doing brings a drawback.
obj.getClass() != getClass() defeats the inheritancy principle of the OOP and besides it violates also the Liskov principle.
Suppose that we declare a new class : OtherAddressWithNoAddedFieldInEquals. This new class extends Address and adds a new field in its declaration but don’t want to override equals().

Here is the class :

public class OtherAddressWithNoAddedFieldInEquals extends Address {
 
    private boolean flag;
 
    public OtherAddressWithNoAddedFieldInEquals(String city, String country, boolean flag) {	
	super(city, country);
	this.flag = flag;
    }
 
    public boolean isFlag() {
        return flag;
    }
 
}

When we compare two OtherAddressWithNoAddedFieldInEquals objects, it works as expected :

OtherAddressWithNoAddedFieldInEquals addressWithNoAddedFieldInEquals = new OtherAddressWithNoAddedFieldInEquals("my city", "my country", true);
OtherAddressWithNoAddedFieldInEquals anotherAddressWithNoAddedFieldInEquals = new OtherAddressWithNoAddedFieldInEquals("my city", "my country", false);
Assert.assertTrue(addressWithNoAddedFieldInEquals.equals(anotherAddressWithNoAddedFieldInEquals));

Nevertheless, because of the getClass() check, we cannot compare an OtherAddressWithNoAddedFieldInEquals object with an Address object while we could expect that it may work as OtherAddressWithNoAddedFieldInEquals inherits the equals() method of Address and in OOP, we generally expect to be able to use at runtime any subclasses where a super class is used as declared type.
For example these assertions will fail as with the getClass() check, a comparison between an Address object and an OtherAddressWithNoAddedFieldInEquals object returns always false.

Address address = new OtherAddressWithNoAddedFieldInEquals("my city", "my country", true);
Address anotherAddress = findAddress(1);
Assert.assertTrue(address.equals(anotherAddress));
...
public Address findAddress(int id){
   ...
   return new OtherAddressWithNoAddedFieldInEquals("my city", "my country", true);
}

As a general way, the getClass() instance should really be avoided in classes designed for the inheritancy as its goes against the OOP principles and benefits that allow to subclasses to inherit from the behavior defined in the parent class.

Overriding equals() by favoring the composition over the inheritancy is the right way

We saw that whatever the way we are using,  there is not a desirable way to override equals() in a subclass when the parent already overrides it.
In fact, in this case, we should avoid using inheritance.
The AddressWithDistrict should be not a subclass of Address.
It doesn’t mean that we cannot create a relation between AddressWithDistrict and Address. We could indeed compose the AddressWithDistrict class with an Address field.
Here is the new version of AddressWithDistrict :

public class AddressWithDistrict {
 
    private String district;
    private Address address;
 
    public AddressWithDistrict(String district, String city, String country) {
	address = new Address(city, country);
	this.district = district;
    }
 
    public String getDistrict() {
	return district;
    }
 
    public Address getAddress() {
	return address;
    }
 
    @Override
    public boolean equals(Object obj) {
	// performant comparison
	if (this == obj)
	    return true;
 
	if (!(obj instanceof AddressWithDistrict)) {
	    return false;
	}
	AddressWithDistrict other = (AddressWithDistrict) obj;
	return address.equals(other.address) && district.equals(other.district);
    }
 
}
Ce contenu a été publié dans equals, java, Object, symmetry, transitivity. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *