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:
Table Of Contents
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
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, thehashCode()
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.
You can download the full source code of this example here: Java Object Tutorial