Object methods

  • https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
  • equals(Object obj): boolean
  • hashCode(): int
  • toString() String
  • Object Management:
    • clone(): Object
    • getClass(): Class<?>
    • finalize(): void (deprecated JDK 9+)
  • Thread synchronization:
    • notify(): void
    • notifyAll(): void
    • wait(): void
    • wait(long timeout): void
    • wait(long timeout, int nanos): void

toString()

  • String representation of an Object
  • @ToString from Lombok automatically override and create a method for you
  • default implementation:
getClass().getName() + '@' + Integer.toHexString(hashCode())

equals() and == operator

  • equals() is logically equivalent
  • == checks for object reference equality not the content
  • The default behavior of the equals() checks whether objects references are equal.
  • This is not enough if you want to compare the objects by their values
  • If you want to override equals() then, you need to implement in such a way that mathematically:
    • Reflexivity: x.equals(x) == true
    • Symmetry: x.equals(y) == y.equals(x)
    • Transitivity: x.equals(y) && y.equals(z) ==> x.equals(z)
    • Consistency: x.equals(y) should not change with multiple invocation
    • Non-nullity: x.equals(null) == false
  • If you don’t perform the above tests, then equals() will not work properly
  • Strings are immutable in nature and hence should not be compared with ==

hashCode() and equals()

  • Default Implementation in JDK 17
@IntrinsicCandidate
public native int hashCode();
 
public boolean equals(Object obj) {
    return (this == obj);
}
  • hashCode()
    • returns integer
    • Along with equals() it is used to identify if the objects have the same reference
  • o1.equals(o2) o1.hashCode() == o2.hashCode() should always be true
    • Vice versa need not be true
  • It means if two objects are logically equal, then their hashCode should reflect the same
  • The Hash related data structures use this fact to calculate hashCode for equal objects
  • If equals() is overridden then you must override hashCode() so as not to “break the contract”.
  • Used in hash based collections:
    • HashMap, HashSet, and HashTable
  • Why overriding hashCode and equals is necessary:
class DataKey {
	private int id;
	DataKey(int id) {
	    this.id = id;
	}
}
 
public class MyClass {
    public static void main(String[] args) {
        Map<DataKey, String> hm = new HashMap<>();
        hm.put(new DataKey(1), "Chintoo");
        hm.put(new DataKey(1), "Chintoo");
 
        System.out.println(hm.size());
        // this will print 2
        // also now it is impossible to find out
        // the hashmap value by key
    }
}
  • It is also related to hash collisions and distribution of Keys
  • For two entries in hash table: (Each entry = {Obj, Val})
    • If they have same hashCode() but different equals(), then HashTable will insert into linked list for the same hashCode()
    • If they have different hashCode(), then HashTable will insert into different bucket
HashTable: [X0, X1, X2, ....] -- buckets
Each Key in HashTable point to linked list
 
X0 --> [ {Obj1, Val1} --> {Obj2, Val2} ]
X1 --> [ {Obj3, Val3} ]
 
From the above, we can see:
 
Obj1.hashCode() == Obj2.hashCode() # possible to have collision
Obj1.equals(Obj2) == false
 
Obj1.hashCode() != Obj3.hashCode()
which also implies Obj1.equals(Obj2) is false

clone()

  • Following is taken from JDK 17
    • It is implemented using native, hence implementation is internal
    • If the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown when calling this method
    • Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment.
    • Hence, This method performs a “shallow copy” of this object, not a “deep copy” operation.
@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
  • Interface Definition (taken from JDK 17)
    • It does not have a body hence a marker interface
public interface Cloneable {
}
  • A class that implements the Cloneable interface is expected to override the Object.clone() method to provide a public clone method since it is protected method and cannot be used anywhere else
class MyClass implements Cloneable {
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
 
// without Cloneable implementation
class MyClass2 {
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
 
public static void main(String... args) {
    MyClass obj = new MyClass();
    MyClass clonedObj = (MyClass) obj.clone();
 
    MyClass2 obj2 = new MyClass2();
    // throws CloneNotSupportedException
    MyClass2 clonedObj2 = (MyClass2) obj2.clone(); 
}

finalize()

  • Called by the garbage collector on an object when garbage collection determines that there are no more references to the object
  • Cons:
    • No guarantee of their run
    • Extremely difficult to write correctly
    • significant performance cost
  • We must avoid finalizers as much as possible
  • It is also deprecated in JDK 9+

getClass()

  • Returns the runtime Class object of this Object

wait(), notify() and notifyAll()