Java programming course: 9.3 Recommended overrides from the Object class

In the previous lesson we looked at preventing the compromisation of immutable classes.


Recommended overrides from the Object class

You will recall that every class (both those supplied by Java and those you write yourself) inherits from the Java supplied class called Object. So far, the only method from Object that you have overridden in your classes has been toString(), to return a textual representation of your object's state, and it is recommended that you override this method for virtually all classes that you write.

There are two further methods from Object that you should consider overriding within your classes, although this time you need to decide whether it is appropriate and meaningful to do so, or whether the default processing that you automatically inherit should be retained.

The two methods are equals() and hashCode(), and they work hand-in-hand.

Overriding equals() and hashCode()

You have previously seen the double equals operator (==) being used to compare two primitive values, for example:

int a = 3;
int b = 3;
int c = 4;

boolean x = a == b;
boolean y = a == c;

System.out.println("x=" + x);
System.out.println("y=" + y);
 

Above, three int primitives called a, b and c are assigned the values 3, 3 and 4 respectively. Then, a is compared to b (with the result stored in boolean primitive x), and a is compared to c (with the result stored in boolean primitive y). Hopefully you can see that this will result in x containing true and y containing false (the comparisons are performed first, and then the assignment is performed using the result of the comparison).

What happens if you use the double equals comparison operator on object references? You may recall that each primitive type has an object "wrapper" class, and the wrapper for int is the class Integer. To see what happens, create three Integer objects and two comparisons:

Integer integerA = new Integer(3);
Integer integerB = new Integer(3);
Integer integerC = new Integer(4);
        
boolean x = integerA == integerB;
boolean y = integerA == integerC;

System.out.println("x=" + x);
System.out.println("y=" + y);        
 

You will find that both x and y result in false. While the result for y should not surprise you, that for x might since integerA and integerB were set to the same value. The reason that they are considered to be different is because when comparing object references using == it is their identity which is compared, and not their contents. By identity is meant their independent existence in memory – the references integerA and integerB point to two separate objects, and the fact that their attribute values are the same is irrelevant to the == operator.

Often, however, when you compare object references you want it to be comparing their state (i.e., attribute values) since they can be considered "logically" equal even if they exist in separate objects in memory. This is the purpose of the equals() method provided within the Object class.

The equals() method

Change the two comparison statements above to the following:

boolean x = integerA.equals(integerB);
boolean y = integerA.equals(integerC);
 

You will now find that while y is still false, x now results in true since it is the attribute content (i.e., the actual numeric value) of the objects which are being compared.

This is not the end of the story, however. Try entering the following statements which use your recently defined Email class instead of Integer:

Email emailA = new Email("");
Email emailB = new Email("");
Email emailC = new Email("");
        
boolean x = emailA.equals(emailB);
boolean y = emailA.equals(emailC);
        
System.out.println("x = " + x);
System.out.println("y = " + y);
 

Now, x results in false, even though the attribute value of emailA and emailB are identical. The reason is that the default implementation of equals() in class Object merely does the following:

public boolean equals(Object other) {
    return this == other;
}
 

The designers of Java overrode the implementation of the equals() method in the Integer class to compare the actual value rather than the perform the simple identity comparison. Your Email class, however, has not overridden the equals() method so when you invoke equals() on Email objects it performs the default identity comparison, inherited from Object.

Therefore, you should override the equals() method when your class can have instances that have logical equality. This is typically the case for value classes; that is, classes that have some intrinsic state that represents a value. As well as Integer and the other primitive wrapper classes, Java also overrides it in the String class (among others), enabling sensible text comparisons:

String stringA = "abc";
String stringB = "abc";
String stringC = "def";
        
boolean x = stringA.equals(stringB);
boolean y = stringA.equals(stringC);

System.out.println("x=" + x); // will output true
System.out.println("y=" + y); // will output false
 

Having determined that two separate Email objects which have the same attribute value (i.e., the same email string) ought to be considered as logically equal, the next step is to override the equals() method inside the Email class. Enter the following statements:

@Override
public boolean equals(Object other) {
    if (! (other instanceof Email)) return false;	// 1
    Email otherEmail = (Email) other;		    	// 2
    return email.equals(otherEmail.email);		    // 3
}
 
  • The method signature accepts an argument which can be any Object (which also means it can accept any subclass of Object, such as Email)
  • Statement 1 uses the instanceof operator to see whether the argument reference (called other) is an instance of the Email class. The ! Operator applies the "not" condition, so the statement can be read as "if other is not an instance of Email, return false"
  • Statement 2 performs a cast on other to convert it back to an Email object. This is needed so as to enable you to access its members
  • Statement 3 compares the email String attribute value of the current object and the argument Email object. Because the email attribute is a String, the equals() invoked on this statement is the one overridden by the String class. The result of the comparison is a boolean, and is returned

Now you can re-run the email comparison and receive a more sensible outcome:

Email emailA = new Email("");
Email emailB = new Email("");
Email emailC = new Email("");
        
boolean x = emailA.equals(emailB);
boolean y = emailA.equals(emailC);
        
System.out.println("x = " + x); // will now output true
System.out.println("y = " + y); // will still output false
 

The hashCode() method

The hashCode() method returns an int, and it works hand-in-hand with the equals() method. Its purpose is to return a numeric identifier for any object, so that objects of a particular type can be efficiently stored in collections. A collection is a grouping of multiple objects and will be explored in Section 11. If two objects are equal according to the equals() method then their hash codes should both have the same value. Many Java supplied classes, including String, override their version of hashCode() and you can take advantage of this for the Email class.

Define the following method in the Email class:

@Override
public int hashCode() {
    return email.hashCode();
}
 

The formula for generating a hash code is not always as straightforward as above. You can use NetBeans to generate it for you by right-clicking where you would start to enter the next method and selecting Insert Code... followed by hashCode(). NetBeans can also generate the equals() method for you, or even both equals() and hashCode() at the same time.

To complete this section, a natural question to ask is whether the other classes you have developed so far should also have implementations to override the equals() and hashCode() methods. A short explanation for each of these follows:

  • For Emailable, interface types cannot provide implementations of any method, only the method signatures. Since all classes automatically inherit equals() and hashCode() anyway (through Object) there is also no need to ever specify them in any interface
  • For Gender, enum types specify the equals() and hashCode() methods as final, meaning you cannot override them even if you wanted to. The Java provided implementation correctly treats each constant in an enum as unique
  • For the Person class the attributes are name, address and gender. Suppose you create the following two objects
Person p1 = new Person("Fred", "London", Gender.MALE);
Person p2 = new Person("Fred", "London", Gender.MALE);
 

The question is, does this refer to two separate people who just happen to have the same name, address and gender, or should they be treated as if they refer to the same person? This course takes the view that if separate Person objects are created with the same attribute values then they are still independent entities. This is because we know from real life that different people can have the same name. Therefore, comparison though object identity is the correct approach, and equals() and hashCode() should not be overridden.

  • This course will also not override equals() and hashCode() for Animal, ZooKeeper, ZooAdministrator, or Visitor for similar reasons

There is in fact another reason to be cautious about overriding the methods in the above classes, including Person, and that is that the classes are mutable. The methods equals() and hashCode() are used when placing objects inside collections, but if their attribute values are changed while inside a collection then unpredictable results can occur. This is not to suggest you should never override these methods for mutable classes; but it is only safe to do so if the attributes being used in the overridden methods are not themselves mutable. This can sometimes require creating an additional attribute (such as a unique id number), but this course takes the simpler approach of treating each mutable object by its identity. 


In the next series of lessons we will look at documentation, testing, and debugging.

Next lesson: 10.1 Documentation, testing, and debugging


Print
×
Stay Informed

When you subscribe, we will send you an e-mail whenever there are new updates on the site.

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Saturday, 13 December 2025

Captcha Image