
Despite the enticing title of this two part series, I'm sure its content will prove to be quite obvious and mundane for many experienced Java and/or C# developers out there... but that doesn't make it any less important. This is a topic that I often find myself covering with our new entry level developers, and on occasion even with some more seasoned developers.
Java & C#, huh?
You probably noticed that I mentioned both Java and C# above... and I did so for good reason(s):
- this is applicable to both languages (and probably many other for that matter)
- We are a purely Java shop at my day job [the one that pays the bills] but as a evening/weekend code warrior I'm a C# type of guy
It should also be noted that the information and code snippets below are presented from a Java perspective and syntax, but they can be applied almost directly to the C# language.
What is the equals(Object obj) method?
The little mentioned equals method of java.lang.Object acts just like the == operator in that they both check to see if two objects are identical. Two object are identical if they both refer to the same instance of a class... that is, they share the same address in memory. This is generally known as an object identity comparison, but is also referred to as a pointer comparison.
The inherent contract of the equals method for objects that extend/inherit from Object class is that it tests for object equality rather than identity. Two objects are said to to be equivalent if they are both instances of the same Type and if the value of each field of the first object is equal to the value of the same field in the second object. Essentially this means we are recursively calling the equals method for each attribute on the objects. Java provides an implementation of equals for the basic/wrapper Types of String, Boolean, Integer, Double, BigDecimal, etc... and all primitives use the == operator for comparison.
An Example.
Here is an equals method that will compare to instances of the Car class: Two cars are equal if and only if their license plates are equal.
public boolean equals(Object obj)
{
if (obj instanceof Car)
{
Car thatCar = (Car) obj;
return this.licensePlate == thatCar.licensePlate ||
(this.licensePlate != null &&
this.licensePlate.equals(thatCar.licensePlate));
}
return false;
}
A few notes on the above.
- Often developers will want to test
obj == null before testing the properties of this and obj. However, this step is unnecessary as null will never be an instance of any class. That is to say, null instanceof Object will always return false.
- The method signature must always look exactly as it does above, taking in an Object and returning a primitive boolean. This will ensure that the method can always test against any other object extending from
Object and will properly override the public boolean Object.equals(Object obj) method.
Does every Object need it's own equals?
The answer is, maybe not... as long as you're happy with the implementation of equals the object inherits from it's hierarchy[1]. But, as the sample code can help to illustrate, it is impossible to write an equals method that is generic enough to be applied to any two objects and have it return a meaningful result. In other words, because different classes have different attributes, there is no universal and/or generic way to compare all of the attributes of one class with all of the attributes of another (different) class.
Even more to the point, it is entirely likely that some attributes of a class will be irrelevant to the test for equality. For example, we could imagine that the Car class has attributes of speed, fuelAmount, engineRpm, numberOfPassengers, etc... none of which have any thing to do with car1 being equal to car2. Therefore, the equals method need not worry about these attributes. We could very easily think of another attribute which is important in determine the equality of two Car objects, such as the car's VIN. In this case we could simple expand on our above example and compare the Car.vinNumber attribute of each object like so:
public boolean equals(Object obj)
{
if (obj instanceof Car)
{
Car thatCar = (Car) obj;
boolean isEqual = this.licensePlate == thatCar.licensePlate ||
(this.licensePlate != null &&
this.licensePlate.equals(thatCar.licensePlate));
isEqual &= (this.vinNumber == thatCar.vinNumber ||
(this.vinNumber != null &&
this.vinNumber.equals(thatCar.vinNumber)));
return isEqual;
}
return false;
}
Be sure to check read Part 2 of this topic to learn why it is so important to override the hashCode any time you override equals.
[1] = There is actually much more discussion to be had on this topic as in some cases you may want a parent class to be able to provide the equals method for any subClasses, but in other cases not. Depending on the intended usage/implementation of the classes in question, allowing the parent to provide the method could violate the symmetry contract of the method. To avoid this, instead of the instanceof check, you should use this:
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
This could mean that all subClasses would need implement their own equals method to ensure they don't default back to their parent's implementation.