This blog attempts to explain about various methods that should be overridden and under which circumstances. Every class in java has Object class as the base class. Object class has methods defined in it. Some of them can be overridden while the remaining of them cannot be overridden (they are defined as final in the Object class like wait , notify etc). The methods that can be overridden are provided with a basic implementation so that for all basic purposes, the basic implementation can be used. In this document, we will be discussing about the non-final methods which can be overridden by the subclasses.
Non-Final Methods
clone
equals
finalize
hashCode
toString
In the following sections, we will discuss about the non-final methods present in the Object class. For every method, we will navigate through three sections
· Java-doc and Default Implementation – One liner crisp documentation as per java-doc will be quoted. The section will also detail about he basic implementation provided by the Object class.
· Usage – The various possible usages of the method will be detailed.
· Overriding Implementation - Possible implementation detail should the subclass decide to override the basic structure provided by the Object class.
Java-doc and Default Implementation
As per javadoc “Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object”. The default implementation will create a separate copy of the object and return to the caller. The default implementation is implemented as a native method.
Usage
The clone method is used in multiple scenarios
1. It is used to create a copy of the object so that application can work on different objects independently.
2. It can be used to ensure that the application doesn’t create multiple copies of the object and work on them independently. This is implemented by throwing an exception in the clone implementation method.
Overriding Implementation
If the object is made of only immutable objects and primitives, the clone method implemented in the Object class is sufficient for creating a complete clone of the object. It will create a new copy of the object and return it. If the object is composed of mutable objects, the clone method should be implemented to get a complete clone of the object (refer to shallow copy and deep copy procedures).
The implementation should navigate through the structure of the object and clone all of the composed mutable objects. It should replace the reference of the mutable objects within the object with a reference to its cloned copies. As a general rule, the clone method should be implemented if the object is composed of mutable objects and a complete or deep clone is required.
If the object which needs to be cloned is composed of large number of mutable objects, another approach create a clone is to perform serialization and de-serialization..
Clone method is also used in circumstances where a new copy of the object should not be created by cloning the object. In such a scenario, the clone method should be implemented and a CloneNotSupportedException should be thrown. This approach is useful while implementing caches and singleton classes.
2. finalize
Java-doc and Default Implementation
As per javadoc “Called by the garbage collector on an object when garbage collection determines that there are no more references to the object”. The default implementation of finalize method in the Object class does not perform any special function.
Usage
The garbage collection process consists of the mark and sweep process. The GC process will identify the objects that are not reachable from any other live threads. The GC will invoke the finalize method on all of the identified objects. After this, the GC process will again iterate through the objects to ensure that it is not reachable from any of the live threads. Once the object is identified as not reachable from any live threads, object is discarded.
This finalize method is hence invoked by the garbage collector when the object is marked for deletion by the GC Process. The order in which the finalize method on the marked objects will be invoked is not guaranteed. Also any garbage collection thread can invoke the finalize method on the marked object. This means, if there are 10 objects that are marked for garbage collection, the JVM can invoke the finalize method on these 10 objects in any order and multiple GC threads can perform the invocation on different objects at the same time.
Any unhandled exception thrown from this method will be ignored by the JVM and the finalize process on that object will terminate. It is also guaranteed that the finalize method will be invoked only once in the life cycle of the object.
Overriding Implementation
The finalize method should be implemented by the class if some clean up process needs to be performed only before the object is being completely evicted from the JVM. The finalize method can also make the object available again for use by other threads i.e. bring back from garbage collection process.
Care should be taken to avoid cyclic references within the objects that are marked for garbage collection process. This might bring in cyclic loops. The finalize method should be implemented keeping performance of the garbage collection process in mind. The finalize process can delay the garbage collection processing if implemented incorrectly.
3. toString
Java-doc and Default Implementation
Javadoc states that “Returns a string representation of the object.” The String returned from the toString method in the Object class is formed by appending the name of the class of which the object is an instance + “@ “ + the unsigned hexadecimal representation of the hashCode of the object.
Usage
The toString method is generating a String representation of the object. This will be useful during debugging and logging operations. AbstractCollection class has a decent toString implementation in which it iterates through all of the objects in the collection and invokes the toString on the contained objects. This way a meaningful representation of the collection object is returned to the user when the toString method is invoked
Overriding Implementation
The toString implementation should ensure that a meaningful string representation of the object is returned. For a composite object, the toString method can recursively invoke the toString method of all of the composed objects and generate the string representation. For collections, we can iterate through the objects and generate a string representation of the object.
4. equals
Java-doc and Default Implementation
As per javadoc “Indicates whether some other object is "equal to" this one”. The default implementation of equals method will check if the memory reference of an object and the compared object are the same. It performs a memory location comparison and not value comparison. The method definition of equals method in the Object class is
public boolean equals(Object obj) {
return (this == obj);
}
This method compares the memory address of the “obj” with the memory address of “this” object
Usage
The equal method is used to check the equality between two objects. The equals method is overridden in many of the commonly used classes like String class. In String class, the equals method will return true if the two objects point to same memory location or if the two string objects has the same sequence of characters.
Consider the below samples
Case 1:
String s1 = “Vijith”
String s2= “Vijith”
System.out.println(s1==s2 ) -> true
System.out.println(s1.equals(s2) ) -> true
The s1 == s2 is returning true because the two strings are pointing to the same memory location. In the s1.equals(s2) method, the method returns true because the s1 and s2 have same set of characters.
Case 2:
String s1= new String(“Vijith”);
String s2 = new String(“Vijith”);
System.out.println(s1==s2 ) -> false
System.out.println(s1.equals(s2) ) -> true
The s1==s2 will return false because s1 and s2 are pointing to different memory locations. A new operator will create the string objects pointing to different memory locations.
The s1.equals(s2) return true as the sequence of characters are same for s1 and s2 string object. The memory location of s1 and s2 are different in this case and still returns true as the default implementation of equals method in the Object class is overridden with a different implementation in the String class. The equals method of String class checks for the sequence of characters.
Overriding Implementation
The equals method should be implemented carefully. There are set of properties to be maintained when the equals method is overridden. For non-null objects, it should be symmetric, transitive, reflexive, consistent and x.equals(null) should return false.
Another rule of objects is that two equal objects should retrun the same hashCode. Hence overriding the equals method would generally need to override the hashCode method too.
A sample equal method implementation is shown below. In this method , the equals method compares the value of the key of two objects and if they return the same value, true is returned.
public boolean equals(Object o){
if(this == o)return true;
if (o==null)return false;
if(this.key1 == null ((MyObject)o).key1 == null )return false;
if (this.key1.equals(((MyObject)o).key1)){
return true;
}else{
return false;
}
}
Another significant place where the equals as well as the hashCode method overriding is important is the primary keys of an Entity Bean. The hashCode method should return well distributed hashCode against which the Entity Bean will be stored in the HashMap. If two objects return same hashCode, then the HashMap will use the equals method to determine the correct Object. I will be detailing the hash lookup in some other article at a later point.
Java-doc and Default Implementation
As per javadoc “Returns a hash code value for the object.”. The default implementation of hashCode method will return the integer representation of the memory reference of an object. The method is implemented as a native method in the Object class.
Usage
hashCode method is used for the benefit of implementations that depend on the hashing mechanism like HashMap, HashTable. This method is not invoked nor has direct relationship with the equals method except that the default implementation of hashCode and the equals method depends on the memory location of the object.
The hashCode method is invoked when the object is added or accessed in any implementation that uses hashing mechanism like HashMap. When the object is inserted into a HashMap, the hashCode is evaluated to determine the location to which the object needs to be added. If two objects return same hashCode, then equals method is used to identify if the keys are equal. A hashCode implementation can make the performance of the HashMap better or worse based on the implementation. A well distributed hashCode can be a good implementation while returning a constant value like “1” will be a poor implementation to perform a hash lookup.
Overriding Implementation
hashCode should be overridden to return a well distributed and repeatable hashCode for the object. The HashMap will evaluate the hashCode of the key to determine the object. If the hashCode lookup returns more than one object with the same hashCode, the equals method will be used to determine the correct object.
In the below implementation, the hashCode method appends the various internal fields to create a string object. The hashCode of the resultant string is returned as the hashCode for this object.
public int hashCode(){
StringBuffer sb = new StringBuffer();
sb.append(internal_value1);
sb.append(internal_value2);
sb.append(internal_value3);
String str = sb.toString();
retrun str.hashCode();
}