Core Java

Using methods common to all objects

This article is part of our Academy Course titled Advanced Java.

This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!

1. Introduction

From part 1 of the tutorial, How to create and destroy objects, we already know that Java is an object-oriented language (however, not a pure object-oriented one). On top of the Java class hierarchy sits the Object class and every single class in Java implicitly is inherited from it. As such, all classes inherit the set of methods declared in Objectclass, most importantly the following ones:

MethodDescription
protected Object clone()Creates and returns a copy of this object.
protected void finalize()Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. We have discussed finalizers in the part 1 of the tutorial, How to create and destroy objects.
boolean equals(Object obj)Indicates whether some other object is “equal to” this one.
int hashCode()Returns a hash code value for the object.
String toString()Returns a string representation of the object.
void notify()Wakes up a single thread that is waiting on this object’s monitor. We are going to discuss this method in the part 9 of the tutorial, Concurrency best practices.
void notifyAll()Wakes up all threads that are waiting on this object’s monitor. We are going to discuss this method in the part 9 of the tutorial, Concurrency best practices.
void wait(), void wait(long timeout), void wait(long timeout, int nanos)Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. We are going to discuss these methods in the part 9 of the tutorial, Concurrency best practices.

Table 1

In this part of the tutorial we are going to look at equals,hashCode,toString and clone methods, their usage and important constraints to keep in mind.

2. Methods equals and hashCode

By default, any two object references (or class instance references) in Java are equal only if they are referring to the same memory location (reference equality). But Java allows classes to define their own equality rules by overriding the equals() method of the Object class. It sounds like a powerful concept, however the correct equals() method implementation should conform to a set of rules and satisfy the following constraints:

  • Reflexive. Object x must be equal to itself and equals(x) must return true.
  • Symmetric. If equals(y) returns true then y.equals(x) must also return true.
  • Transitive. If equals(y) returns true and y.equals(z) returns true, then x.equals(z) must also return true.
  • Consistent. Multiple invocation of equals() method must result into the same value, unless any of the properties used for equality comparison are modified.
  • Equals To Null. The result of equals(null) must be always false.

Unfortunately, the Java compiler is not able to enforce those constraints during the compilation process. However, not following these rules may cause very weird and hard to troubleshoot issues. The general advice is this: if you ever are going to write your own equals() method implementation, think twice if you really need it. Now, armed with all these rules, let us write a simple implementation of the equals() method for the Person class.

package com.javacodegeeks.advanced.objects;

public class Person {
    private final String firstName;
    private final String lastName;
    private final String email;
    
    public Person( final String firstName, final String lastName, final String email ) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
    
    public String getEmail() {
        return email;
    }
    
    public String getFirstName() {
        return firstName;
    }
    
    public String getLastName() {
        return lastName;
    }

    // Step 0: Please add the @Override annotation, it will ensure that your
    // intention is to change the default implementation.
    @Override
    public boolean equals( Object obj ) {
        // Step 1: Check if the 'obj' is null
        if ( obj == null ) {
            return false;
        }
        
        // Step 2: Check if the 'obj' is pointing to the this instance
        if ( this == obj ) {
            return true;
        }
        
        // Step 3: Check classes equality. Note of caution here: please do not use the 
        // 'instanceof' operator unless class is declared as final. It may cause 
        // an issues within class hierarchies.
        if ( getClass() != obj.getClass() ) {
            return false;
        }
        
        // Step 4: Check individual fields equality
        final Person other = (Person) obj;
        if ( email == null ) {
            if ( other.email != null ) {
                return false;
            } 
        } else if( !email.equals( other.email ) ) {
            return false;
        }
        
        if ( firstName == null ) {
            if ( other.firstName != null ) {
                return false;
            } 
        } else if ( !firstName.equals( other.firstName ) ) {
            return false;
        }
            
        if ( lastName == null ) {
            if ( other.lastName != null ) {
                return false;
            }
        } else if ( !lastName.equals( other.lastName ) ) {
            return false;
        }
        
        return true;
    }        
}

It is not by accident that this section also includes the hashCode() method in its title. The last, but not least, rule to remember: whenever you override equals() method, always override the hashCode() method as well. If for any two objects the equals() method returns true, then the hashCode() method on each of those two objects must return the same integer value (however the opposite statement is not as strict: if for any two objects the equals() method returns false, the hashCode() method on each of those two objects may or may not return the same integer value). Let us take a look on hashCode() method for the Person class.

// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public int hashCode() {
    final int prime = 31;
        
    int result = 1;
    result = prime * result + ( ( email == null ) ? 0 : email.hashCode() );
    result = prime * result + ( ( firstName == null ) ? 0 : firstName.hashCode() );
    result = prime * result + ( ( lastName == null ) ? 0 : lastName.hashCode() );
        
    return result;
}      

To protect yourself from surprises, whenever possible try to use final fields while implementing equals() and hashCode(). It will guarantee that behavior of those methods will not be affected by the field changes (however, in real-world projects it is not always possible).

Finally, always make sure that the same fields are used within implementation of equals() and hashCode() methods. It will guarantee consistent behavior of both methods in case of any change affecting the fields in question.

3. Method toString

The toString() is arguably the most interesting method among the others and is being overridden more frequently. Its purpose is it to provide the string representation of the object (class instance). The properly written toString() method can greatly simplify debugging and troubleshooting of the issues in real-live systems.

The default toString() implementation is not very useful in most cases and just returns the full class name and object hash code, separated by @, f.e.:

com.javacodegeeks.advanced.objects.Person@6104e2ee

Let us try to improve the implementation and override the toString() method for our Person class example. Here is a one of the ways to make toString() more useful.

// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public String toString() {
    return String.format( "%s[email=%s, first name=%s, last name=%s]", 
        getClass().getSimpleName(), email, firstName, lastName );
}

Now, the toString() method provides the string version of the Person class instance with all its fields included. For example, while executing the code snippet below:

final Person person = new Person( "John", "Smith", "john.smith@domain.com" );
System.out.println( person.toString() );

The following output will be printed out in the console:

Person[email=john.smith@domain.com, first name=John, last name=Smith]

Unfortunately, the standard Java library has a limited support to simplify toString() method implementations, notably, the most useful methods are Objects.toString(), Arrays.toString() / Arrays.deepToString(). Let us take a look on the Office class and its possible toString() implementation.

package com.javacodegeeks.advanced.objects;

import java.util.Arrays;

public class Office {
    private Person[] persons;

    public Office( Person ... persons ) {
         this.persons = Arrays.copyOf( persons, persons.length );
    }
    
    @Override
    public String toString() {
        return String.format( "%s{persons=%s}", 
            getClass().getSimpleName(), Arrays.toString( persons ) );
    }
    
    public Person[] getPersons() {
        return persons;
    }
}

The following output will be printed out in the console (as we can see the Person class instances are properly converted to string as well):

Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}

The Java community has developed a couple of quite comprehensive libraries which help a lot to make toString() implementations painless and easy. Among those are Google Guava's Objects.toStringHelper and Apache Commons Lang ToStringBuilder.


 

4. Method clone

If there is a method with a bad reputation in Java, it is definitely clone(). Its purpose is very clear – return the exact copy of the class instance it is being called on, however there are a couple of reasons why it is not as easy as it sounds.

First of all, in case you have decided to implement your own clone() method, there are a lot of conventions to follow as stated in Java documentation. Secondly, the method is declared protected in Object class so in order to make it visible, it should be overridden as public with return type of the overriding class itself. Thirdly, the overriding class should implement the Cloneable interface (which is just a marker or mixin interface with no methods defined) otherwise CloneNotSupportedException exception will be raised. And lastly, the implementation should call super.clone() first and then perform additional actions if needed. Let us see how it could be implemented for our sample Person class.

public class Person implements Cloneable {
    // Please add the @Override annotation, it will ensure that your
    // intention is to change the default implementation.
    @Override
    public Person clone() throws CloneNotSupportedException {
        return ( Person )super.clone();
    }
}

The implementation looks quite simple and straightforward, so what could go wrong here? Couple of things, actually. While the cloning of the class instance is being performed, no class constructor is being called. The consequence of such a behavior is that unintentional data sharing may come out. Let us consider the following example of the Office class, introduced in previous section:

package com.javacodegeeks.advanced.objects;

import java.util.Arrays;

public class Office implements Cloneable {
    private Person[] persons;

    public Office( Person ... persons ) {
         this.persons = Arrays.copyOf( persons, persons.length );
    }

    @Override
    public Office clone() throws CloneNotSupportedException {
        return ( Office )super.clone();
    }
    
    public Person[] getPersons() {
        return persons;
    }
}

In this implementation, all the clones of the Office class instance will share the same persons array, which is unlikely the desired behavior. A bit of work should be done in order to make the clone() implementation to do the right thing.

@Override
public Office clone() throws CloneNotSupportedException {
    final Office clone = ( Office )super.clone();
    clone.persons = persons.clone();
    return clone;
}

It looks better now but even this implementation is very fragile as making the persons field to be final will lead to the same data sharing issues (as final cannot be reassigned).

By and large, if you would like to make exact copies of your classes, probably it is better to avoid clone() / Cloneable and use much simpler alternatives (for example, copying constructor, quite familiar concept to developers with C++ background, or factory method, a useful construction pattern we have discussed in part 1 of the tutorial, How to create and destroy objects).

5. Method equals and == operator

There is an interesting relation between Java == operator and equals() method which causes a lot of issues and confusion. In most cases (except comparing primitive types), == operator performs referential equality: it returns true if both references point to the same object, and false otherwise. Let us take a look on a simple example which illustrates the differences:

final String str1 = new String( "bbb" );
System.out.println( "Using == operator: " + ( str1 == "bbb" ) );
System.out.println( "Using equals() method: " + str1.equals( "bbb" ) );

From the human being prospective, there are no differences between str1==”bbb” and str1.equals(“bbb”): in both cases the result should be the same as str1 is just a reference to “bbb” string. But in Java it is not the case:

Using == operator: false
Using equals() method: true

Even if both strings look exactly the same, in this particular example they exist as two different string instances. As a rule of thumb, if you deal with object references, always use the equals() or Objects.equals() (see please next section Useful helper classes for more details) to compare for equality, unless you really have an intention to compare if object references are pointing to the same instance.

6. Useful helper classes

Since the release of Java 7, there is a couple of very useful helper classes included with the standard Java library. One of them is class Objects. In particular, the following three methods can greatly simplify your own equals() and hashCode() method implementations.

MethodDescription
static boolean equals(Object a, Object b)Returns true if the arguments are equal to each other and false otherwise.
static int hash(Object... values)Generates a hash code for a sequence of input values.
static int hashCode(Object o)Returns the hash code of a non-null argument and 0 for a null argument.

Table 2

If we rewrite equals() and hashCode() method for our Person’s class example using these helper methods, the amount of the code is going to be significantly smaller, plus the code becomes much more readable.

@Override
public boolean equals( Object obj ) {
    if ( obj == null ) {
        return false;
    }
        
    if ( this == obj ) {
        return true;
    }
        
    if ( getClass() != obj.getClass() ) {
        return false;
    }
        
    final PersonObjects other = (PersonObjects) obj;
    if( !Objects.equals( email, other.email ) ) {
        return false;
    } else if( !Objects.equals( firstName, other.firstName ) ) {
        return false;            
    } else if( !Objects.equals( lastName, other.lastName ) ) {
        return false;            
    }
        
    return true;
}
        
@Override
public int hashCode() {
    return Objects.hash( email, firstName, lastName );
}      

7. Download the Source Code

8. What’s next

In this section we have covered the Object class which is the foundation of object-oriented programming in Java. We have seen how each class may override methods inherited from Object class and impose its own equality rules. In the next section we are going to switch our gears from coding and discuss how to properly design your classes and interfaces.

Andrey Redko

Andriy is a well-grounded software developer with more then 12 years of practical experience using Java/EE, C#/.NET, C++, Groovy, Ruby, functional programming (Scala), databases (MySQL, PostgreSQL, Oracle) and NoSQL solutions (MongoDB, Redis).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
mmesnjak
mmesnjak
8 years ago

Hi, Regarding point number 6… Would it not be more in line with OO paradigm to write this method in the following manner: @Override public boolean equals( Object obj ) { final PersonObjects other = (PersonObjects) obj; boolean retValue = true; if ( obj == null ) { retValue = false; } else if ( this == obj ) { retValue = true; } else if ( getClass() != obj.getClass() ) { retValue = false; } else if( !Objects.equals( email, other.email ) ) { retValue = false; } else if( !Objects.equals( firstName, other.firstName ) ) { retValue = false; }… Read more »

Andriy Redko
8 years ago
Reply to  mmesnjak

Hi,

Thank you very much for your question. It is never ending debate in the community about single vs multiple return statements. My personal opinion on that is simple: if for you as for a reader of code it is clear, easy to follow and understandable what the method does and what is going to be returned, than it does not matter much how many return statements are there. However there is no relation to OOP in this case, more to imperative programming styles.

Thank you.

Best Regards,
Andriy Redko

m1k0
m1k0
8 years ago

somethings different:
String a = “Ala”;
System.out.println(“a == Ala : ” + (a == “Ala”));

Iryna
Iryna
8 years ago

Hi Andriy,

Are practical exercises available somewhere for this course?

Thanks,
Iryna

indah.yusuf
indah.yusuf
3 years ago

Thanks for your tuto, I am still finding it difficult to grasp the difference between == and equals() concerning object

Back to top button