Introduction

Abstraction is the process of hiding the implementation details and showing only the functionality to the user. This helps to focus on what an object does rather than how it does it.

Object Class

The most common general methods applicable to any Java object are defined in the Object class. The Object class serves as the parent class of any Java class, whether directly or indirectly. Therefore, all the methods defined in the Object class are automatically available to any Java class. The Object class defines the following 11 methods:


  1. clone()
    Creates a new object of the same class as this object.
  2. equals(Object)
    Compares two Objects for equality.
  3. finalize()
    Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
  4. getClass()
    Returns the runtime class of an object.
  5. hashCode()
    Returns a hash code value for the object.
  6. notify()
    Wakes up a single thread that is waiting on this object's monitor.
  7. notifyAll()
    Wakes up all threads that are waiting on this object's monitor.
  8. toString()
    Returns a string representation of the object.
  9. wait()
    Waits to be notified by another thread of a change in this object.
  10. wait(long)
    Waits to be notified by another thread of a change in this object.
  11. wait(long, int)
    Waits to be notified by another thread of a change in this object.

toString() Method

To return String representation of an object.

  public String toString()
  {
      return getClass.getName() + '@' + Integer.toHexString(HashCode);
  }
        

  class Student
  {
        String name;	
        int rollno;
	
        Student(String name, int rollno)
        {
            this.name = name;
            this.rollno = rollno;
        }

        public static void main(String arg[])
        {
            Student s1 = new Student("vijay", 101);
            Student s2 = new Student("yash", 102);
            System.out.println(s1); // Student@10b62c9
            System.out.println(s2); // Student@82ba41
        }
  }
          

When ever we are passing object reference as argument to s.o.p() internally JVM will call toString() on that object. If we are not providing toString() then Object class toString() will be executed which is implemented as follows

  public String toString()
  {
      return getClass.getName() + '@' + Integer.toHexString(HashCode);
  }
        

Based on our requirement to provide our own String representation we have to override toString(). If we are printing Student Object reference to return name & roll no we have to override toString() as follows:

  public String toString()
  {
      return name + "--------" + rollno;
  }

It is highly recommended to override toString() in our classes.

hashCode() Method

The `hashCode` of an object represents a unique numerical value that can be utilized by the JVM when storing or adding objects into `HashSet`, `Hashtable`, or `HashMap`. The `hashCode()` method of the `Object` class is implemented to return a hash code based on the memory address of an object. However, according to our requirements, we can override the `hashCode()` method to generate our own custom hash codes.

Case: 1

  class Test
  {
      int i;    
      Test(int i)
      {
          this.i = i;
      }
      public int hashCode()
      {
         return i;
      }       
      public static void main(String arg[])
      {
         Test t1 = new Test(100);
         Test t2 = new Test(110);
         System.out.println(t1); // Output: 100
         System.out.println(t2); // Output: 110
      }
  }
          

Case: 2

  class HashCodeDemo
  {
      int i;    

      HashCodeDemo(int i)
      {
          this.i = i;
      }

      public int hashCode()
      {
          return i;
      }  

      public String toString()
      { 
          return i + "";
      }

      public static void main(String arg[])
      {
          HashCodeDemo h1 = new HashCodeDemo(100);
          HashCodeDemo h2 = new HashCodeDemo(110);
          System.out.println(h11); // Output: 100
          System.out.println(h2); // Output: 110
      }
  }        
        

equals() Method

default without overriding

  class Student
  {
      String name;
      int rollno;

      Student(String name,int rollno)
      {
          this.name = name;
          this.rollno = rollno;
      }

      public static void main(String arg[])
      {
            Student s1 = new Student ("vijay", 101);
            Student s2 = new Student ("yash", 102);
            Student s3 = new Student ("yash", 102);
            System.out.println(s1.equals(s2)); //false
            System.out.println(s2.equals(s3)); //false
      }
  }
          

r1 == r2 //reference Comparision.
r1.equals(r2) //reference Comparision

Note: In this case Object class .equals() has executed which is meant for reference comparison but based on our requirement it is recommended to override .equals() for content comparison. By over loading .equals() we have to consider the following 3 cases
Case1: The meaning of equality
Case2: In the case of heterogeneous objects we have to return false. (i.e) we have to handle ClassCastException to return false.
Case3: If we are passing null as the argument we have return false. (i.e) we have to handle NullPointerException to return false.


Overridden methods of equals

  class Student
  {
      String name;
      int rollno;

      Student(String name,int rollno)
      {
          this.name = name;
          this.rollno = rollno;
      }

      public boolean equals(Object obj)
      {
          try
          {
              String name1 = this.name;
              int rollno1 = this.rollno;
              Student s2 = (Student)obj;
              String name2 = s2.name;
              int rollno2 = s2.rollno;
              if(name1.equals(name2) && rollno1 == rollno2)
              {
                  return true;
              }
              else
              {
                  return false;
              }
          }
          catch (ClassCastException c)
          {
              return false;
          }
          catch (NullPointerException e)
          {
              return false;
          }
      }
      public static void main(String arg[])
      {
          Student s1 = new Student ("vijay", 101);
          Student s2 = new Student ("yash", 102);
          Student s3 = new Student ("yash", 102);
          System.out.println(s1.equals(s2)); // false
          System.out.println(s2.equals(s3)); // true
          System.out.println(s1.equals(null)); // false
      }
  }
          

Comparison between ‘==’ operator and ‘.equals()’

Comparison Point == Operator .equals() Method
1) Applicability Applicable for both primitive and Object references. Applicable for only Object references.
2) Meaning True if both references point to the same object on the heap (reference comparison). True if both references point to the same object on the heap (reference comparison).
3) Overriding Cannot be overridden. Can be overridden for content comparison.
4) Incompatibility Results in compile-time error if references are incompatible. Returns false if references are incompatible.
5) null Check Returns false for any Object reference compared to null. Returns false for any Object reference compared to null.

Relationship between ‘==’ and .equals()

If r1 == r2 is true then r1.equals(r2) is always true.
If r1.equals(r2) is true, then r1 == r2 need not to be true.

Contract Between .equals() and hashCode()

  1. If two objects are equal by .equals(), then their hash codes must be equal.
  2. Example: If r1.equals(r2) is true, then r1.hashCode() == r2.hashCode() is also true.

  3. If two objects are not equal by .equals(), then their hash codes may or may not be the same.
  4. If the hash codes of two objects are equal, then the objects may or may not be equal by .equals().
  5. If the hash codes of two objects are not equal, then the objects are always not equal by .equals().

To satisfy above contract when ever we are overriding .equals it is highly recommended to override hashCode() also.

clone() Method

The process of creating exactly duplicate Object is called Clonning. Object class contains the clone method to perform this
protected native Object clone() throws CloneNotSupportedException
In a subclass that implements the Cloneable Interface, it is expected to override the clone() method of the Object class. This method is utilized for creating a clone, essentially a duplicate of the object along with its member variable values, using the syntax aobj.clone(). If a class fails to implement the Cloneable interface, attempting this process results in a thrown CloneNotSupportedException.

  class Test implements Cloneable
  {
      int i = 10;
      int j = 20;
      public static void main(String arg[])throws CloneNotSupportedException
      {
          Test t1 = new Test();
          Test t2 = (Test)t1.clone();
          t1.i = 100;
          t1.j = 200;
          System.out.println(t2.i + "----" + t2.j); // 10----20
      }
  }
          

All the Objects can’t have the capability to produce cloned Objects. Only clonaeble objects having that capability.
An Object is said to be cloneable iff the corresponding class has to implement java.lang.cloneable interface.
It doesn’t contain any methods it is a marker interface.
Protected members can be accessible from outside package in the child classes but we should invoke them by using child class reference only. That is parent class reference is not allowed to invoke protected members from outside package.

class Test implements Cloneable
{
    public static void main(String arg[])throws CloneNotSupportedException
    {
        Object o = new Object(); //Case1: 
        Object o2 = o.clone();
        Object o = new Test(); //Case2:
        Object o2 = o.clone();
        Test t = new Test(); //Case3: 
        Object o = t.clone();
    }
}

          

It is highly recommended to override clone() in our class like doGet(), doPost() methods.

finalize() Method

The garbage collector triggers the finalize() method when removing an object from memory, specifically when there are no remaining references to that object.

wait() Method

By using this technique, the current thread is made to wait for a notification from another thread. Within the function, you must provide the time in milliseconds for which the thread will remain in this waiting state.

notify() Method

By using this technique, notifications are sent to a single thread at a time, waking it up while it is waiting for a certain item.

notifyAll() Method

This function awakens all threads that are currently waiting for an object and sends notifications to all threads at the same time.