Contents
Classes in Java
Understanding Classes and Objects
Object-Oriented Programming (OOP) refers to the concept of structuring software as a collection of objects that include both data and behavior. In this approach, programs revolve around objects, which helps simplify software development and maintenance. Instead of focusing solely on actions or logic, OOP allows for more flexible and maintainable software. It makes understanding and working with the program easier by bringing data and methods into a single location: the object.
Key Concepts of OOP:
- Object
- Class
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Importance of Classes and Objects in OOP
Classes:
A class acts as a blueprint or prototype from which objects are created. It defines a set of attributes and behaviors that are common to all objects of that type. The primary reasons classes are essential in OOP are:
- They offer a structure for creating objects that bind data and methods together.
- They contain method and variable definitions.
- They support inheritance, allowing for the maintenance of a class hierarchy.
- They enable the management of access to member variables.
Objects:
An object is the core unit of OOP. It represents real-life entities and combines attributes and behaviors.
Objects consist of:
- State: Represented by the object’s attributes.
- Behavior: Represented by the object’s methods.
- Identity: A unique identifier for each object, allowing it to interact with other objects.
In OOP, objects are important because they can call non-static functions not present in the main method but existing within the class.
Example of Creating and Using Objects and Classes:
To better understand this, let’s take an example where we add two numbers. By creating separate objects for each number, we can perform the necessary operations. Here’s a demonstration of the use of objects and classes:
// Java program to demonstrate objects and classes
public class Animal {
// Instance variables
String name;
String species;
int age;
// Constructor for the Animal class
public Animal(String name, String species, int age) {
this.name = name;
this.species = species;
this.age = age;
}
// Method to return the animal's name
public String getName() {
return name;
}
// Method to return the animal's species
public String getSpecies() {
return species;
}
// Method to return the animal's age
public int getAge() {
return age;
}
// Method to print the animal's details
@Override
public String toString() {
return "This is a " + species + " named " + name + " and it is " + age + " years old.";
}
public static void main(String[] args) {
// Creating an object of the Animal class
Animal animal1 = new Animal("Buddy", "Dog", 3);
System.out.println(animal1.toString());
}
}
Output:
This is a Dog named Buddy and it is 3 years old.
Object Creation Techniques in Java:
1. Using the new
Keyword:
This is the simplest and most common way to create an object in Java.
// Java program to demonstrate object creation using the new keyword
class Vehicle {
String type;
String model;
Vehicle(String type, String model) {
this.type = type;
this.model = model;
}
}
public class Test {
public static void main(String[] args) {
// Creating two objects of the Vehicle class
Vehicle car = new Vehicle("Car", "Sedan");
Vehicle bike = new Vehicle("Bike", "Cruiser");
// Accessing object data
System.out.println(car.type + ": " + car.model);
System.out.println(bike.type + ": " + bike.model);
}
}
Output:
Car: Sedan
Bike: Cruiser
2. Using Class.newInstance()
:
This method dynamically creates objects, invoking a no-argument constructor.
// Java program to demonstrate object creation using Class.newInstance()
class Example {
void displayMessage() {
System.out.println("Welcome to OOP!");
}
}
public class Test {
public static void main(String args[]) {
try {
Class> cls = Class.forName("Example");
Example obj = (Example) cls.newInstance();
obj.displayMessage();
} catch (Exception e) {
System.out.println(e);
}
}
}
Output:
Welcome to OOP!
3. Using the clone()
Method:
This method creates a copy (or clone) of an existing object. The class must implement the Cloneable
interface.
// Java program to demonstrate object creation using the clone() method
class Person implements Cloneable {
int id;
String name;
// Constructor
Person(int id, String name) {
this.id = id;
this.name = name;
}
// Cloning method
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) {
try {
// Creating original object
Person person1 = new Person(101, "John");
// Cloning person1
Person person2 = (Person) person1.clone();
System.out.println(person1.id + ", " + person1.name);
System.out.println(person2.id + ", " + person2.name);
} catch (CloneNotSupportedException e) {
System.out.println(e);
}
}
}
Output:
101, John
101, John
Singleton Method Design Pattern
In object-oriented programming, a singleton class in Java is a class designed to allow only one instance to exist at any given time. When multiple variables attempt to instantiate this class, they all reference the same instance. Any changes made through one reference are visible to all others, because they all point to the same object.
Key Aspects of Defining a Singleton Class:
1. Private constructor: The constructor is made private to prevent direct instantiation from outside the class.
2. Static method: A static method, using lazy initialization, returns the single instance of the class.
Purpose of a Singleton Class:
The singleton pattern is primarily used to limit the number of instances of a class to just one. This is particularly useful for controlling access to resources such as a database connection or system configuration, where having multiple instances might lead to inconsistent behavior or unnecessary resource use.
Benefits of a Singleton:
- Avoids memory wastage: By restricting instance creation to just one, it avoids the overhead of multiple object creations.
- Reusability: The single instance can be reused as needed, making the singleton pattern useful in scenarios such as logging, caching, or connection pooling.
Example Use Case:
In situations where only one connection (e.g., to a database) is allowed or needed, a singleton ensures that all threads share that same connection, rather than creating multiple ones.
Steps to Create a Singleton Class in Java:
1. Ensure only one instance exists:
- Declare the class constructor as private.
- Provide a static method to get the single instance (using lazy initialization).
2. Provide global access:
- Store the single instance as a private static variable.
- Use a static method to return this instance when needed.
Difference Between a Normal Class and a Singleton Class:
A normal class allows multiple instances to be created using a constructor. In contrast, a singleton class restricts this by providing the instance through a static method (like getInstance()
).
While the normal class disappears at the end of an application’s lifecycle, the singleton’s instance may persist and be reused across the application’s duration.
Types of Singleton Patterns:
1. Eager Initialization: The instance is created when the class is loaded.
2. Lazy Initialization: The instance is created only when it’s requested for the first time.
Example 1: Using getInstance()
Method
// Singleton Class Implementation
class SingletonExample {
// Static variable to hold the one and only instance
private static SingletonExample singleInstance = null;
// A variable to demonstrate instance behavior
public String message;
// Private constructor to prevent instantiation
private SingletonExample() {
message = "This is a part of the SingletonExample class";
}
// Static method to provide access to the single instance
public static synchronized SingletonExample getInstance() {
if (singleInstance == null) {
singleInstance = new SingletonExample();
}
return singleInstance;
}
}
// Main class to demonstrate Singleton behavior
public class MainClass {
public static void main(String[] args) {
// Accessing Singleton class through different references
SingletonExample a = SingletonExample.getInstance();
SingletonExample b = SingletonExample.getInstance();
SingletonExample c = SingletonExample.getInstance();
// Print hash codes for the instances
System.out.println("Hashcode of a: " + a.hashCode());
System.out.println("Hashcode of b: " + b.hashCode());
System.out.println("Hashcode of c: " + c.hashCode());
// Checking if all references point to the same instance
if (a == b && b == c) {
System.out.println("All variables point to the same instance.");
} else {
System.out.println("Different instances are created.");
}
}
}
Output:
Hashcode of a: 12345678
Hashcode of b: 12345678
Hashcode of c: 12345678
All variables point to the same instance.
In this example, a
, b
, and c
all refer to the same instance, as demonstrated by their identical hash codes.
Example 2: Singleton Class Using Class Name as Method
// Singleton Class with Method Named After Class
class Singleton {
private static Singleton singleInstance = null;
// A variable to store a message
public String message;
// Private constructor
private Singleton() {
message = "This is part of the Singleton class";
}
// Static method with the same name as class to return the instance
public static Singleton Singleton() {
if (singleInstance == null) {
singleInstance = new Singleton();
}
return singleInstance;
}
}
// Main class to test
public class MainTest {
public static void main(String[] args) {
// Get instances from Singleton class
Singleton first = Singleton.Singleton();
Singleton second = Singleton.Singleton();
Singleton third = Singleton.Singleton();
// Modify the variable through the first reference
first.message = first.message.toUpperCase();
// Print the message from each reference
System.out.println("Message from first: " + first.message);
System.out.println("Message from second: " + second.message);
System.out.println("Message from third: " + third.message);
// Modify the variable through the third reference
third.message = third.message.toLowerCase();
// Print the message again
System.out.println("Message from first: " + first.message);
System.out.println("Message from second: " + second.message);
System.out.println("Message from third: " + third.message);
}
}
Output:
Message from first: THIS IS PART OF THE SINGLETON CLASS
Message from second: THIS IS PART OF THE SINGLETON CLASS
Message from third: THIS IS PART OF THE SINGLETON CLASS
Message from first: this is part of the singleton class
Message from second: this is part of the singleton class
Message from third: this is part of the singleton class
Object Class in Java
The Object class is part of the java.lang
package and serves as the superclass of every class in Java. This means all classes in Java, either directly or indirectly, inherit from the Object class. If a class doesn’t explicitly extend another class, it automatically becomes a direct child of the Object class. However, if a class extends another class, it indirectly inherits from the Object class. Consequently, all methods provided by the Object class are available to all Java classes. This makes the Object class the root of Java’s inheritance hierarchy.
Methods of the Object Class
The Object class provides several important methods, which are:
1. toString() method.
2. hashCode() method
3. equals(Object obj) method
4. finalize() method
5. getClass() method
6. clone() method
7. wait(), notify(), and notifyAll() methods (used for thread synchronization)
1. toString() Method : The toString()
method returns a string representation of an object. The default implementation provided by the Object class includes the class name, followed by the @
symbol and the hexadecimal representation of the object’s hash code. The method is defined as:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
It’s a common practice to override the toString()
method to give a more meaningful string representation of an object.
Example:
class Employee {
String name;
int id;
Employee(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Employee [Name: " + name + ", ID: " + id + "]";
}
public static void main(String[] args) {
Employee e = new Employee("John", 101);
System.out.println(e);
System.out.println(e.toString());
}
}
Output:
Employee [Name: John, ID: 101]
Employee [Name: John, ID: 101]
2. hashCode() Method : The hashCode()
method returns a hash code value for an object, which is typically used in hashing algorithms and data structures like HashMap
. Each object has a unique hash code by default, but you can override this method to customize the hash code calculation.
class Car {
String model;
int year;
Car(String model, int year) {
this.model = model;
this.year = year;
}
@Override
public int hashCode() {
return year + model.hashCode();
}
public static void main(String[] args) {
Car c = new Car("Toyota", 2020);
System.out.println("Car's hash code: " + c.hashCode());
}
}
Output:
Car's hash code: 207319
3. equals(Object obj) Method : The equals()
method checks whether two objects are equal. By default, it compares object references. However, this method is often overridden to compare the actual content of the objects.
Example:
class Book {
String title;
String author;
Book(String title, String author) {
this.title = title;
this.author = author;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Book book = (Book) obj;
return title.equals(book.title) && author.equals(book.author);
}
public static void main(String[] args) {
Book b1 = new Book("1984", "George Orwell");
Book b2 = new Book("1984", "George Orwell");
System.out.println(b1.equals(b2));
}
}
Output:
true
4. getClass() Method : The getClass()
method returns the runtime class of the object. This method is final and cannot be overridden.
Example:
class Animal {
public static void main(String[] args) {
Animal a = new Animal();
System.out.println("Class of the object: " + a.getClass().getName());
}
}
Output:
Class of the object: Animal
5. finalize() Method : The finalize()
method is called by the garbage collector when there are no more references to an object. It is often used to clean up resources.
Example:
class Demo {
@Override
protected void finalize() throws Throwable {
System.out.println("Finalize method called.");
}
public static void main(String[] args) {
Demo d = new Demo();
d = null;
System.gc(); // Requesting garbage collection
}
}
Output:
Finalize method called.
6. clone() Method : The clone()
method creates and returns a copy (clone) of the object. To use this method, a class must implement the Cloneable
interface.
Example:
class Product implements Cloneable {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
protected Product clone() throws CloneNotSupportedException {
return (Product) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Product p1 = new Product("Laptop", 999.99);
Product p2 = p1.clone();
System.out.println(p1.name + " - " + p2.name);
}
}
Laptop - Laptop
Inner Class
Understanding Inner Classes in Java
In Java, an inner class is a class defined within another class or an interface. This concept was introduced to bring logically related classes together, following Java’s object-oriented principles, making the code more intuitive and closer to real-world representations. But why were inner classes introduced? Let’s dive into their advantages:
Advantages of Inner Classes:
- They help in creating cleaner and more readable code.
- Inner classes can access private members of their outer class, adding flexibility in designing real-world scenarios.
- They help optimize code modules by grouping closely related logic together.
As we progress through Java’s object-oriented programming concepts, you will see inner classes becoming more common, especially when you want certain operations to have limited access to other classes. We will now explore the different types of inner classes in Java, along with detailed examples.
Types of Inner Classes in Java
There are four main types of inner classes:
1. Member Inner Class . (Nested Inner Class)
2. Method Local Inner Classes
3. Static Nested Classes
4. Anonymous Inner Classes
We will examine each type with examples.
1. Member Inner Class (Nested Inner Class): A member inner class is defined within the body of another class. It can access all the members of the outer class, including private members.
Example:
class Outer {
private String message = "Welcome to the inner class!";
class Inner {
public void displayMessage() {
System.out.println(message); // Accessing outer class's private field
}
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // Creating an instance of inner class
inner.displayMessage();
}
}
Output:
Welcome to the inner class!
2. Method Local Inner Class : A method-local inner class is defined within a method of the outer class. This class is only accessible within the method and can access the final or effectively final local variables of the method.
Example:
class Outer {
public void outerMethod() {
System.out.println("Inside outerMethod");
class Inner {
public void innerMethod() {
System.out.println("Inside innerMethod");
}
}
Inner inner = new Inner(); // Creating an instance of the local inner class
inner.innerMethod();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.outerMethod();
}
}
Output:
Inside outer method
Inside inner method
3. Static Nested Class : Static nested classes are not considered true inner classes because they don’t have access to the outer class’s instance variables. Instead, they function like static members of the outer class.
// Outer class
class OuterClass {
// Static nested class
static class StaticNestedClass {
void display() {
System.out.println("Inside static nested class");
}
}
}
// Main class
public class MainClass {
public static void main(String[] args) {
// Create an instance of static nested class
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.display();
}
}
Output:
Inside static nested class
3. Anonymous Inner Class : Anonymous inner classes have no name and are typically used for quick implementations of interfaces or to extend classes for single-use cases.
Example 1: Extending a Class
// Base class
class Greeting {
void sayHello() {
System.out.println("Hello from Greeting class");
}
}
// Main class
public class MainClass {
public static void main(String[] args) {
// Anonymous inner class extending Greeting
Greeting greet = new Greeting() {
@Override
void sayHello() {
System.out.println("Hello from anonymous class");
}
};
greet.sayHello();
}
}
Output:
Hello from anonymous class
Throwable Class
Classes and Objects form the foundation of Object-Oriented Programming, and the concept revolves around real-world entities. A class is a user-defined template or blueprint from which objects are instantiated. It represents a collection of properties or methods that are common to all objects of a particular type. In this article, we will explore the Throwable class, its constructors, and various methods available in this class.
The Throwable class serves as the superclass for every error and exception in the Java language. Only objects that are a subclass of Throwable can be thrown by either the “Java Virtual Machine” (JVM) or by the Java throw statement. For compile-time exception checking, Throwable and its subclasses (excluding Error and RuntimeException) are treated as checked exceptions.
The Throwable class is at the root of the Java Exception Hierarchy and is extended by two primary subclasses:
1. Exception
2. Error
The Throwable class implements the Serializable interface, and its direct known subclasses are Error and Exception. It contains a snapshot of the execution stack of its thread at the time of its creation. It may also include a message string to provide additional context about the error. Furthermore, it can suppress other throwables from propagating.
Users can create their own custom throwable by extending the Throwable class.
Example:
class MyCustomThrowable extends Throwable {
// Custom Throwable created by the user
}
class Example {
public void testMethod() throws MyCustomThrowable {
// Custom throwable used here
throw new MyCustomThrowable();
}
}
Declaration of java.lang.Throwable
:
public class Throwable extends Object implements Serializable
Constructors:
Any class can have constructors, which are of three main types: default, parameterized, and non-parameterized. The Throwable class has the following constructors:
1. Throwable(): A non-parameterized constructor that creates a new Throwable with a null detailed message.
2. Throwable(String message): A parameterized constructor that creates a new Throwable with a specific detailed message.
3. Throwable(String message, Throwable cause): A parameterized constructor that creates a new Throwable with a specific message and a cause.
4. Throwable(Throwable cause): A parameterized constructor that creates a new Throwable with the specified cause and a message derived by calling the toString() method on the cause.
Protected Constructors:
Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace): Constructs a new throwable with the specified message, cause, and options for enabling/disabling suppression and stack trace writability.
Methods:
The Throwable class provides several predefined methods:
1. addSuppressed(Throwable exception): Appends the specified exception to the list of suppressed exceptions.
public final void addSuppressed(Throwable exception)
2. fillInStackTrace(): Records the current stack trace into the Throwable object.
public Throwable fillInStackTrace()
3. getCause(): Returns the cause of the Throwable or null
if the cause is unknown.
public Throwable getCause()
4. getLocalizedMessage(): Provides a localized description of the Throwable.
public String getLocalizedMessage()
5. getMessage(): Returns the detailed message string.
public String getMessage()
6. getStackTrace(): Returns an array of stack trace elements representing the stack frames.
public StackTraceElement[] getStackTrace()
7. getSuppressed(): Returns an array containing all suppressed exceptions.
public final Throwable[] getSuppressed()
8. initCause(Throwable cause): Initializes the cause of the Throwable.
public Throwable initCause(Throwable cause)
9. printStackTrace(): Prints the throwable and its stack trace to the standard error stream.
public void printStackTrace()
10. setStackTrace(StackTraceElement[] stackTrace): Sets the stack trace elements for the Throwable.
public void setStackTrace(StackTraceElement[] stackTrace)
11. toString(): Returns a short description of the Throwable.
public String toString()