Core Java

Java Object Tutorial

In this post, we feature a comprehensive article on the Java Object class, which is the parent class of all the classes. The article and the examples used are based on JDK 8.

You can also check the Java Classes and Objects Tutorial in the following video:

Java Classes and Objects Tutorial – video

 

1. Introduction

The essence of Object Oriented Programming is abstraction using classes and objects.

All Java programs use Object. Type of the Object is defined by a class.

The process of creating an object is called instantiation. The object is created using the class as its blueprint. In Java, java.lang.Object refers to the Object and java.lang.Class refers to the class. java.lang.Object is the root of the class hierarchy. All Java classes extend java.lang.Object directly or indirectly.

An Object is instantiated using the new operator. The instantiation process dynamically allocates the required memory for the object.

2.Parts of an Object

Java Object
Java Object

2.1.Instance variables/methods

The fields of an object are called instance variables. The instance variables of an Object denotes its state. The methods of an object are called instance methods. The methods define the behavior of the object.

2.2.Static variables/methods

A field that belongs to Class is called a static variable. It is initialized when the class is loaded at runtime. Similarly, a method that belongs to a class is called a static method. The static members (variables/methods) are declared with the keyword static. Static members are accessed using the class name or using object reference.

2.3.Constructors

A constructor is similar to a method. It does not return a value. It has the same name as that of the class. It is called as soon as the Object is created using the new operator. Its primary objective is to initialize the Object. By default, a no-argument constructor is generated by the compiler. A class can have additional constructors. Please refer to here for more details.

2.4.Static initialization blocks

This is a block of code enclosed within curly braces and preceded by a static keyword. A class can have more than one static initializer, anywhere in the class body. When a class is loaded by the runtime, the system executes the static blocks in the order of their occurrence in the source code.

static {
    //code for initializing
}

2.5.Instance initialization blocks

This is a block of code enclosed within curly braces. The compiler copies the initializer block into every constructor.

{
   //code for initializing
}

2.6.Finalizer

protected void finalize() throws Throwable

The finalize() method is called by the Garbage Collector (GC) when the GC determines that there are no more references to the object. The JVM’s decision to call the finalize() method may vary. It may or may not call the method.

For a detailed study on Garbage collection, please refer here.

The following example illustrates the order of execution of static initializer, instance initializer, and constructor during object instantiation.

CreateObjectExample.java

public class CreateObjectExample {

    {
        System.out.println("Instance initializer 1");
    }
    
    static {
        System.out.println("Static initializer 1");
    }

    static {
        System.out.println("Static initializer 2");
    }
    
    public CreateObjectExample(){
        System.out.println("no-arg Constructor");
    }
    
    public CreateObjectExample(boolean status){
        System.out.println("boolean-arg Constructor");
    }
    
    public static void main(String[] args){
        //Object creation - using no-arg constructor
        CreateObjectExample obj1 = new CreateObjectExample();
        
        //Object creation - using boolean-arg constructor
        CreateObjectExample obj2 = new CreateObjectExample(true);
        
        //calling instance method
        obj1.print();
        
        //calling static method using classname
        CreateObjectExample.print1();
        
        //calling static method using object reference
        obj2.print1();
    }
    //instanceinitiliser
    {
        System.out.println("Instance initializer 2");
    }
    
    public void print(){
        System.out.println("instance method: print method of the object");
    }
    
    public static void print1(){
        System.out.println("static method: print method of the class");
    }
    
    static {
        System.out.println("static initializer 3");
    }
    
    protected void finalize() throws Throwable
    {
        super.finalize();
        System.out.println("finalizer");
    }
}

OUTPUT

Static initializer 1
Static initializer 2
static initializer 3
Instance initializer 1
Instance initializer 2
no-arg Constructor
Instance initializer 1
Instance initializer 2
boolean-arg Constructor
instance method: print method of the object
static method: print method of the class
static method: print method of the class

3.Object Methods

Some of the most used methods of java.lang.Object are :

3.1.equals() method

public boolean equals(Object obj)

The equals() method is used to compare two objects. The equality of the Object is implemented by this method. The default implementation simply checks if two Object references are equal. This means, that the default implementation returns true only of the comparing objects refer to the same object.

A class can provide its own definition of how the objects can be checked for equality.

3.2.hashCode() method

public int hashCode()

This method returns a hash code value for the object. The hash code value is used when the Object is stored in hash tables. The general contract of hashCode is that:

  • If two objects are equal, then their hashCode values must be equal. However, the vice versa does not have to be true i.e if two objects are unequal, their hashcode values do not have to be equal. For this reason, if equals() method is overridden, the hashCode() method also needs to be implemented accordingly.
  • No matter how many times the hashCode() method is invoked on the same object, it must return the same hashCode value.

Please refer to the following example. In User.java, the equals and hashcode methods are overridden to compare based on the field userId. Please refer Line#6, Line#7 of ObjectComparison.java. The references user4 and user 5 are created with the same userId. Hence, on Line#12, true is returned by equals.

User.java

public class User {
    private int userId;
    private String username;
    
    public User(int id, String name){
        this.userId = id;
        this.username = name;
    }
    @Override
    public boolean equals(Object obj){
        if( obj == this) { return true; }
        
        if(obj ==null || !(obj instanceof User) ) {return false;}
        
        
        if( userId == ((User)obj).getUserId()) {
            return true;
        } else {return false;}
        
    }
    
    @Override
    public int hashCode(){
        return userId;
    }
    
    public int getUserId(){ return userId;}
}

ObjectComparison.java

public class ObjectComparison {
    public static void main(String[] args){
        User user1 = new User(1,"Ashley");
        User user2 = user1;
        User user3 = null;
        User user4 = new User(2,"Brian");
        User user5 = new User(2, "Chetna");
        
        System.out.println("user1 vs user2 :" + user1.equals(user2));
        System.out.println("user1 vs user3 :" + user1.equals(user3));
        System.out.println("user1 vs user4 :" + user1.equals(user4));
        System.out.println("user4 vs user5 :" + user4.equals(user5));
    }

}
user1 vs user2 :true
user1 vs user3 :false
user1 vs user4 :false
user4 vs user5 :true

3.3.clone() method

A clone is an exact copy of the original object. The clone() method creates a copy of the object which is being cloned and returns the new reference. The objects implementing the clone() method must implement a marker interface Cloneable. The clone() method is protected on java.lang.Object. So, it needs to be overridden as public.

There are 2 types of cloning. 1. Shallow Cloning 2. Deep Cloning

Shallow cloning is the default copying process implemented by clone() method. A shallow copy, when created has exact fields as that of the original object. If the object has references to another object, only the references are copied to the shallow copy. If you change the value of the shallow copy, it is reflected in the original copy too. Hence, a shallow clone is dependant on the original object.

In Deep Cloning, cloned object has exact fields of the original object. If the object has references to another object, then the references are also cloned by calling the respective clone() methods. Thus, a deep cloned object is independent of the cloned object.

Please refer to the examples below. The Student class implements the Cloneable interface. The Student has Course as one of the attributes.

Student.java

public class Student implements Cloneable{
    private int studentId;
    private String studentName;
    private Course enrolledCourse;

    public Student(int id, String name, Course course){
        this.studentId = id;
        this.studentName = name;
        this.enrolledCourse = course;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException 
    {
        return super.clone();
    }

     public String getStudentName() {
        return studentName;
    }
    public Course getEnrolledCourse() {
        return enrolledCourse;
    }
    
}

Course.java

public class Course {
    String subject1;
    String subject2;

    public Course(String subj1, String subj2){
        this.subject1 = subj1;
        this.subject2 = subj2;
    }    
}

In the ShallowCloner.java, at Line#6, student2 is created by cloning student1. Please note the Line#3 and Line#6 of the Output. Both, student1 and student2 has same Object reference for Course.

ShallowCloner.java

public class ShallowCloner {
    public static void main(String[] args){
        Course grade5 = new Course("Maths", "Science");
        Student student1 = new Student(1,"Krish", grade5);
        try {
        Student student2 = (Student) student1.clone();
        System.out.println("-----Student 1--------");
        System.out.println(student1.getStudentName());
        System.out.println(student1.getEnrolledCourse());
        System.out.println("-----Student 2--------");
        System.out.println(student2.getStudentName());
        System.out.println(student2.getEnrolledCourse());
        } catch(CloneNotSupportedException ex){
            ex.printStackTrace();
        }
    }
}
-----Student 1--------
Krish
jcg.methods.examples.clone.Course@15db9742
-----Student 2--------
Krish
jcg.methods.examples.clone.Course@15db9742

Now, let us see an example of deep cloning. In the examples below, CloneableStudent has CloneableCourse and both implement their one clone methods.

In CloneableStudent‘s clone() implementation, the course is also cloned. Please refer Line#16, 17 of CloneableStudent.

CloneableCourse.java

public class CloneableCourse extends Course implements Cloneable{

    public CloneableCourse(String sub1, String sub2){
        super(sub1, sub2);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}

CloneableStudent.java

public class CloneableStudent implements Cloneable {

    private int studentId;
    private String studentName;
    private CloneableCourse enrolledCourse;

    public CloneableStudent(int id, String name, CloneableCourse course){
        this.studentId = id;
        this.studentName = name;
        this.enrolledCourse = course;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException 
    {
        CloneableStudent deepClone = (CloneableStudent)super.clone();
        deepClone.enrolledCourse = (CloneableCourse)this.enrolledCourse.clone();
        return deepClone;
    }

    public String getStudentName() {
        return studentName;
    }

    public Course getEnrolledCourse() {
        return enrolledCourse;
    }
    
    
}

In the DeepCloner class, at Line#7, student2 is created by cloning student1. Now, as a result of deep cloning, the enrolledCourse attribute is also cloned and both the clones are now independent of each other, with their own instances.

DeepCloner.java

public class DeepCloner {
    
    public static void main(String[] args){
        CloneableCourse grade6 = new CloneableCourse("History", "Science");
        CloneableStudent student1 = new CloneableStudent(2,"Ratha", grade6);
        try {
        CloneableStudent student2 = (CloneableStudent) student1.clone();
        System.out.println("-----Student 1--------");
        System.out.println(student1.getStudentName());
        System.out.println(student1.getEnrolledCourse());
        System.out.println("-----Student 2--------");
        System.out.println(student2.getStudentName());
        System.out.println(student2.getEnrolledCourse());
        } catch(CloneNotSupportedException ex){
            ex.printStackTrace();
        }
    }


}

-----Student 1--------
Ratha
jcg.methods.examples.clone.CloneableCourse@15db9742
-----Student 2--------
Ratha
jcg.methods.examples.clone.CloneableCourse@6d06d69c

3.4.toString() method

The toString() method returns the textual representation of the Object and returns a String. This method is by default called by System.out.println() method and String concatenation

By default, the toString() method implementation returns a String that represents the class name and the unsigned hexadecimal representation of the hashcode of the object in the format classname@hashcode.

In the following example, the class Order uses the default toString() method provided by the Object class. At Line#11, System.out.println() calls the object’s toString() function to convert the object to a textual representation.

Order.java

public class Order {
    private int id;
    private String description;

    public Order(int id, String desc){
        this.id = id;
        this.description = desc;
    }
    public static void main(String[]args){
        Order instance = new Order(1,"daily order");
        System.out.println(instance);
    }
}

OUTPUT

jcg.methods.examples.Order@15db9742

Let us now override the toString() method and see what happens. The return value of the toString() method is printed at line#11.

Order.java

@Override
    public String toString(){
        return id+ ":"+description;
    }

OUTPUT

1:daily order

3.5.getClass() method

The getClass() returns the runtime class of the object. Please note the output is the string “class ” concatenated with the class name. This is because of the toString() method of the java.lang.Class<T> is overridden to display the word “class / interface” along with the class name.

BakeryOrder.java

public class BakeryOrder extends Order
{
    public BakeryOrder(int id, String desc) {
        super(id, desc);
    }
    
    public static void main(String[] args){
        BakeryOrder obj1 = new BakeryOrder(1, "Bakes & Bakes");
        System.out.printf("obj1 : %s%n",obj1.getClass());
        Order obj2 = new Order(2, "Garments order");
        System.out.printf("obj2 : %s%n" ,obj2.getClass());
        Order obj3 = new BakeryOrder(3,"Cake order");
        System.out.printf("obj3 : %s%n" ,obj3.getClass());
        
    }

}
obj1 : class jcg.methods.examples.BakeryOrder
obj2 : class jcg.methods.examples.Order
obj3 : class jcg.methods.examples.BakeryOrder

4.Access Modifiers

An access modifier specifies which class can access the class and eventually the respective object, its fields, methods, and constructors. An access modifier is specified at

  • Class level
  • Member level

Following are the access modifiers in the order of accessibility:

  • public
  • protected
  • default (package)
  • private

public and default access modifiers are applicable at class level. A public class can be accessed from anywhere in the code. The default access modifier is marked with ‘default’ keyword. When there is no access modifier specified, it also takes the default access level. The default access level is also called as ‘package default’. In a package default access level, the class can be accessed from within the same package only. private and protected are not applicable to class level.

Access modifiers can also be specified to fields, methods, and constructors. If a method/variable is marked as private, it can be accessed only within the class and the nested classes. If a method/variable is marked as default or does not have any access modifier, then it can be accessed within the class & nested class and other classes in the same package. Subclasses that belong to a different package can not access the default members. If a method/variable is marked as protected, then the member can be accessed within the class & nested class, other classes from the same package and subclasses from a different package.

4.1.Visibility of members

Access modifier Global Subclass Within package Within class
public Yes Yes Yes Yes
protected No Yes Yes Yes
default No No Yes Yes
private No No No Yes

5.Objects utility

java.util.Objects is a utility class for operations carried out on Object. This utility class was introduced in Java 1.7 . This class predominantly focuses on null-safe operations on java and reducing the boilerplate code.It has the following methods.

  • compare
  • deepEquals
  • equals
  • hash
  • hashCode
  • isNull
  • nonNull
  • requireNonNull
  • toString

We shall see examples of a few methods in the following sections.

5.1.Objects.isNull()

public static boolean isNull(Object obj)

The isNull() method returns true when the object passed as an argument is null. There is no difference between obj==null and Objects.isNull(obj). The method is intended to be used in lambda filtering. For More details on Lambda please read here

5.2.Objects.nonNull()

public static boolean nonNull(Object obj)

This method returns true if the object passed to it is not null. For more details, please refer here. A small self-explanatory example below where Objects.nonNull is used as a Predicate. The filter is a method of Stream that accepts Predicate.

UtilityCheckNull.java

public class UtilityCheckNull {
    public static void main(String[] args){
        List productList = new ArrayList();
        productList.add("AppleCake");
        productList.add("Muffins");
        productList.add(null);
        productList.add("Brownie");
        System.out.println((productList==null));
        System.out.println((Objects.isNull(productList)));
        System.out.println((Objects.nonNull(productList)));
        System.out.println(productList);
        List filtered = productList.stream().filter(Objects::nonNull).collect(Collectors.toList());
        System.out.println(filtered);
    }

}

The line highlighted in the Output below is the filtered list after removing null value

OUTPUT

false
false
true
[AppleCake, Muffins, null, Brownie]
[AppleCake, Muffins, Brownie]

5.3.Objects.requireNonNull()

public static <T> T requireNonNull(T obj)

public static <T> T requireNonNull(T obj, String message )

public static <T> T requireNonNull(T obj, Supplier messageSupplier)

The requireNonNull() method checks if the object passed as argument is null. If the object is non-null, the passed object is returned. Otherwise, it provides 2 other ways to handle null. Please refer to the following examples.

UtilityClass1.java

public static void main(String[] args){
        Map testMap = new HashMap();
        
        System.out.println(Objects.requireNonNull(testMap));
        //Throws NullPointerException with a customised message
        System.out.println(Objects.requireNonNull(testMap.get("key1"),  "key1 is not present"));
    }

In Line#4 of UtilityClass1.java, testMap is an empty map and is non-null. Hence the map is returned to System.out.println(). This results in printing empty map in the Output line#1

In Line#6, since the testMap is empty, the line testMap.get("key1") returns null. At this line, a NullPointerException is assigned a customized message "key1 is not present" , thereby helping the troubleshooting easier.

OUTPUT

{}
Exception in thread "main" java.lang.NullPointerException: key1 is not present
	at java.util.Objects.requireNonNull(Objects.java:228)
	at com.jcg.utilities.UtilityClass1.main(UtilityClass1.java:17)

Refer to the following example UtilityClass2.java . Here a Supplier method called handleError is used to handle the null value returned. The handleError method sets the processStatus to false and also returns a customised error message. The NullPointerException is thrown along with the custom error message.

UtilityClass2.java

public class UtilityClass2 {
    private boolean processStatus;
    
    public static void main(String[] args){
        
        UtilityClass2 obj = new UtilityClass2();
        obj.execute();
     
    }
    
    private void execute(){
        Map testMap = new HashMap();
        
        System.out.println(Objects.requireNonNull(testMap.get("key1"), handleError()));
        //Throws NullPointerException with a customised message
    }
    
    private String handleError(){
        processStatus = false;
        return "Technical Error in the Utility Module. Please contact admin.";
    }
}

OUTPUT

Exception in thread "main" java.lang.NullPointerException: Technical Error in the Utility Module. Please contact admin.
	at java.util.Objects.requireNonNull(Objects.java:228)
	at com.jcg.utilities.UtilityClass2.execute(UtilityClass2.java:24)
	at com.jcg.utilities.UtilityClass2.main(UtilityClass2.java:17)

5.4.Objects.toString()

public static String toString(Object o)

public static String toString(Object o, String nullDefault)

There are 2 overloaded toString methods in the Objects class. Let us see an example of the method overloaded for nullDefault.

This method returns a default value if the toString() method returns null. Refer to the following example. In Line#9, a default value of “null String” is provided. Even when the testMap.get("key1") returns null in the code, there is no NullPointerException thrown in line#9. Instead, it is handled to return a default string.

In Line#11, Object’s toString() method is called on the testMap.get("key1"). As there are no entries with “key1” in the map, null is returned. The toString() method() called on null throws NullPointerException.

UtilityClass3.java

public class UtilityClass3 {

    public static void main(String[] args){
        UtilityClass3 obj = new UtilityClass3();
        obj.execute();
    }
    public  void execute(){
        Map testMap = new HashMap();
        String value = Objects.toString(testMap.get("key1"), "null string");
        System.out.println("value using Objects utility " + value);
        String value1 = testMap.get("key1").toString();
        System.out.println("value using Object toString " + value1);

    }
}

OUTPUT

value using Objects utility null string
Exception in thread "main" java.lang.NullPointerException
	at com.jcg.utilities.UtilityClass3.execute(UtilityClass3.java:20)
	at com.jcg.utilities.UtilityClass3.main(UtilityClass3.java:14)

6. Download the Source Code

This was a tutorial on the Java Object class, which is the parent class of all the classes.

Download
You can download the full source code of this example here: Java Object Tutorial

hashtagmercury

The author is a professional with experience in development projects ranging from Java to Mainframes and loves exploring new technologies and in keeping her knowledge on par with the trend.
Subscribe
Notify of
guest

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

0 Comments
Inline Feedbacks
View all comments
Back to top button