Home Blog Page 98

Polymorphism – Overriding and Dynamic Binding in Java

0
java spring boot course
java spring boot course

Table of Contents

  1. Introduction to Polymorphism
  2. Types of Polymorphism
    • Compile-time Polymorphism (Method Overloading)
    • Runtime Polymorphism (Method Overriding)
  3. Method Overriding
  4. Dynamic Binding
  5. Benefits of Polymorphism
  6. Polymorphism in Action: Examples
  7. Common Pitfalls and Best Practices
  8. Summary

1. Introduction to Polymorphism

Polymorphism is another core principle of object-oriented programming (OOP), along with encapsulation, inheritance, and abstraction. Derived from the Greek words “poly” (many) and “morph” (forms), polymorphism allows one object to take many forms.

In the context of Java, polymorphism allows an object to behave in multiple ways based on the method call, either by overloading methods at compile-time or overriding them at runtime. In this module, we will focus on runtime polymorphism, achieved through method overriding and dynamic binding.


2. Types of Polymorphism

a. Compile-time Polymorphism (Method Overloading)

Compile-time polymorphism refers to method overloading, where multiple methods in a class can have the same name but different parameters (either in number or type). The decision of which method to invoke is made at compile time.

Example of method overloading:

class Calculator {
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}
}

Here, the method add is overloaded with two versions: one for integers and one for doubles. The compiler decides which method to call based on the arguments passed.

b. Runtime Polymorphism (Method Overriding)

Runtime polymorphism, also known as dynamic polymorphism, happens when a subclass provides a specific implementation of a method that is already defined in its superclass. The specific method to call is determined at runtime.


3. Method Overriding

Method overriding occurs when a subclass provides its own implementation of a method that is already defined in its parent class, but with the same method signature (name, return type, and parameters).

The method in the parent class is called a base method, and the method in the subclass is called an overridden method. The overriding method provides its own definition, which replaces the inherited version when the method is called on the object of the subclass.

Example:

class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // Upcasting
animal.sound(); // Calls Dog's overridden sound method
}
}

In this example:

  • Dog overrides the sound method of Animal.
  • The sound method called on an Animal reference (which points to a Dog object) invokes the Dog’s overridden method, not the parent class version.

4. Dynamic Binding

Dynamic binding (or late binding) refers to the runtime decision-making process of selecting the method to be invoked. When a method is called on an object, the JVM determines which method to invoke based on the actual object type (not the reference type) at runtime.

In the example above, the reference type is Animal, but the object type is Dog. Since Dog overrides the sound method, the JVM dynamically binds the method call to Dog‘s version of sound at runtime.

This mechanism is what allows polymorphic behavior in Java. The method invocation happens based on the actual object that the reference points to, even if the reference is of a superclass type.

Dynamic Binding Example:

class Shape {
void draw() {
System.out.println("Drawing a generic shape");
}
}

class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}

class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}

public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle(); // Upcasting
Shape shape2 = new Square(); // Upcasting

shape1.draw(); // Circle's draw method
shape2.draw(); // Square's draw method
}
}

In the code above:

  • shape1 is a reference of type Shape but holds a Circle object.
  • shape2 is a reference of type Shape but holds a Square object.
  • The draw method is invoked polymorphically, and the actual method executed is based on the object type (Circle or Square), not the reference type (Shape).

This dynamic method invocation is a core feature of polymorphism in Java.


5. Benefits of Polymorphism

Polymorphism provides several benefits, particularly in the context of object-oriented design and development:

  1. Flexibility: It allows one interface to be used for different underlying forms of data. This is particularly useful when you need to write code that can work with objects of different classes.
  2. Code Reusability: You can write more generic code, as a superclass reference can point to objects of any subclass, thus minimizing redundancy.
  3. Ease of Maintenance: Modifying the behavior of a subclass method does not affect the rest of the program as long as the method signature remains the same.
  4. Decoupling: Polymorphism decouples the class behavior, making the codebase easier to understand and modify.

6. Polymorphism in Action: Examples

Example 1: Employee Class Hierarchy

Let’s consider a real-world example of an employee hierarchy:

abstract class Employee {
abstract void work();
}

class Developer extends Employee {
@Override
void work() {
System.out.println("Writing code");
}
}

class Manager extends Employee {
@Override
void work() {
System.out.println("Managing the team");
}
}

public class Main {
public static void main(String[] args) {
Employee emp1 = new Developer(); // Developer object
Employee emp2 = new Manager(); // Manager object

emp1.work(); // Calls Developer's work method
emp2.work(); // Calls Manager's work method
}
}

In this example:

  • Employee is an abstract class with an abstract method work().
  • Developer and Manager are subclasses that override work().
  • At runtime, based on the actual object (Developer or Manager), the appropriate work() method is called.

Example 2: Payment System

Consider a payment system where different types of payment methods (credit card, PayPal, etc.) are used:

class Payment {
void pay() {
System.out.println("Making a payment");
}
}

class CreditCardPayment extends Payment {
@Override
void pay() {
System.out.println("Paying with Credit Card");
}
}

class PayPalPayment extends Payment {
@Override
void pay() {
System.out.println("Paying with PayPal");
}
}

public class Main {
public static void main(String[] args) {
Payment payment1 = new CreditCardPayment(); // CreditCardPayment object
Payment payment2 = new PayPalPayment(); // PayPalPayment object

payment1.pay(); // Calls CreditCardPayment's pay method
payment2.pay(); // Calls PayPalPayment's pay method
}
}

This example shows how polymorphism allows different types of payments to be processed through a common interface, which enhances flexibility and scalability.


7. Common Pitfalls and Best Practices

Pitfalls

  • Incorrect Method Signature: The overridden method in the subclass must have the same method signature (name, return type, and parameters) as the method in the superclass.
  • Overriding Non-Overridable Methods: You cannot override final, static, or private methods, as they are not subject to polymorphism.
  • Casting Issues: Misusing references (incorrect casting) can lead to ClassCastException.

Best Practices

  • Always use the @Override annotation when overriding a method. This improves readability and helps catch errors at compile time.
  • Avoid deep inheritance hierarchies; prefer interface-based polymorphism when possible.
  • Be cautious when using polymorphism in performance-critical sections of code, as dynamic binding can introduce overhead.

8. Summary

In this module, you have learned about polymorphism, particularly method overriding and dynamic binding, which are key features of runtime polymorphism. These mechanisms allow Java to execute the appropriate method based on the actual object type at runtime, providing flexibility, scalability, and ease of maintenance in object-oriented design.

By leveraging polymorphism, you can write more generic, reusable, and maintainable code, enhancing both design and performance in larger applications.

Inheritance and the super Keyword in Java

0
java spring boot course
java spring boot course

Table of Contents

  1. Introduction to Inheritance
  2. Types of Inheritance in Java
  3. The extends Keyword
  4. Why Use Inheritance?
  5. The super Keyword
    • Accessing Parent Class Members
    • Invoking Parent Class Constructor
  6. Method Overriding in Inheritance
  7. Inheritance with Constructors
  8. Hierarchical vs Multilevel Inheritance
  9. Composition vs Inheritance
  10. Common Pitfalls and Best Practices
  11. Summary

1. Introduction to Inheritance

Inheritance is one of the four fundamental principles of Object-Oriented Programming (OOP), alongside encapsulation, abstraction, and polymorphism. It allows a class (child or subclass) to inherit fields and methods from another class (parent or superclass), promoting code reuse and establishing a logical hierarchy between classes.

For example, if we have a class Vehicle, we can create child classes like Car, Bike, and Truck that inherit the general behavior of Vehicle.


2. Types of Inheritance in Java

Java supports the following types of inheritance:

  • Single Inheritance: One class inherits from another.
  • Multilevel Inheritance: A class inherits from a derived class.
  • Hierarchical Inheritance: Multiple classes inherit from a single parent class.
Java does not support multiple inheritance with classes to avoid ambiguity, but this can be achieved using interfaces.

3. The extends Keyword

To inherit from a class in Java, the extends keyword is used.

class Animal {
void eat() {
System.out.println("This animal eats food");
}
}

class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}

Here, Dog inherits the method eat() from Animal.


4. Why Use Inheritance?

  • Code Reusability: Write common logic once and reuse it across subclasses.
  • Logical Hierarchy: Reflect real-world relationships (e.g., Dog is-an Animal).
  • Method Overriding: Customize behavior in child classes.
  • Maintenance: Easier to manage code in one place.

5. The super Keyword

The super keyword refers to the immediate parent class and is used to:

  • Access parent class methods and variables
  • Call parent class constructor

a. Accessing Parent Class Members

class Animal {
String sound = "Generic Sound";

void displaySound() {
System.out.println("Animal Sound: " + sound);
}
}

class Cat extends Animal {
String sound = "Meow";

void displaySound() {
System.out.println("Cat Sound: " + sound);
System.out.println("Animal Sound: " + super.sound); // Accessing parent member
}
}

b. Invoking Parent Class Constructor

super() is used to call the constructor of the parent class and must be the first statement in the subclass constructor.

class Animal {
Animal() {
System.out.println("Animal constructor called");
}
}

class Dog extends Animal {
Dog() {
super(); // Calls Animal constructor
System.out.println("Dog constructor called");
}
}

This is especially useful when the parent class has a parameterized constructor.


6. Method Overriding in Inheritance

When a child class defines a method with the same signature as one in its parent class, it overrides the parent method.

class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Lion extends Animal {
@Override
void sound() {
System.out.println("Lion roars");
}
}

Using super to Access Overridden Method

class Lion extends Animal {
@Override
void sound() {
super.sound(); // Calls parent class method
System.out.println("Lion roars");
}
}

7. Inheritance with Constructors

Constructors are not inherited, but they can be called using super().

class Animal {
Animal(String name) {
System.out.println("Animal name: " + name);
}
}

class Elephant extends Animal {
Elephant() {
super("Elephant"); // Calls parent constructor
System.out.println("Elephant constructor done");
}
}

If the parent class does not have a default (no-argument) constructor, the child class must explicitly call a parameterized one using super.


8. Hierarchical vs Multilevel Inheritance

Hierarchical Inheritance

class Animal { }

class Cat extends Animal { }

class Dog extends Animal { }

class Cow extends Animal { }

Multiple classes share a single base class.

Multilevel Inheritance

class Animal { }

class Mammal extends Animal { }

class Human extends Mammal { }

Inheritance in a chain format—each level builds on the previous one.


9. Composition vs Inheritance

Sometimes, composition is preferred over inheritance, especially when the relationship is has-a rather than is-a.

  • Inheritance: Car is-a Vehicle
  • Composition: Car has-an Engine

Composition increases flexibility, avoids tight coupling, and enhances code maintainability.


10. Common Pitfalls and Best Practices

Pitfalls

  • Overusing inheritance can make the code rigid and hard to refactor.
  • Failing to use super() when necessary in constructors.
  • Forgetting that private members are not accessible in subclasses.
  • Confusing method overriding with method overloading.

Best Practices

  • Prefer composition over inheritance unless is-a clearly applies.
  • Always mark overridden methods with @Override for clarity and compile-time checks.
  • Avoid deep inheritance hierarchies; use interfaces where applicable.
  • Ensure constructors properly invoke parent constructors for initialization.

11. Summary

Inheritance in Java is a cornerstone of OOP that enables efficient code reuse and logical relationships between classes. The super keyword enhances control over parent-child interactions, whether it’s accessing variables, methods, or constructors. When used wisely, inheritance can lead to elegant and maintainable designs, but it must be applied with clear intent and good understanding of when alternatives like composition are better suited.

Today in History – 24 April

0
today in history 24 april

today in history 24 april

1311

General Malik Kafur returns to Delhi after campaign in South India.

1800

President John Adams approves legislation to appropriate $5,000 to purchase “such books as may be necessary for the use of Congress,” thus establishing the Library of Congress. The first books, ordered from London, arrived in 1801 and were stored in the U.S. Capitol, the library’s first home.

1858

Kunwar Singh, revolutionary of Indian mutiny and freedom fighter, passed away. He fought his last battle near Jagdishpur, Bihar on April 23, 1858 and defeated the Britishers but was deeply wounded. He also organised the national rebels.

1916

On Easter Monday in Dublin, the Irish Republican Brotherhood, a secret organization of Irish nationalists led by Patrick Pearse, launches the so-called Easter Rebellion, an armed uprising against British rule.

1922

Colin Ross is hanged to death in Australia for the rape and murder of 13-year-old Alma Tirtschke. Ross was one of the first criminals in Australia to be convicted based on forensic evidence.

1929

First non-stop flight from England to India takes-off.

1932

450 people seized by British for defying ban on Indian National Congress.

1959

Nehru meets the exiled Dalai Lama in Mussoorie.

1965

Kosi Barrage inaugurated by King Mahendra of Nepal.

1973

Sachin Ramesh Tendulkar, cricketer (prodigy at 16, Indian capt at 23), was born in Bombay.

1996

SC holds that bank managers acting beyond their authority in allowing overdrafts and passing cheques would be amounted to committing misconduct.

Related Articles: 

Today in History – 23 April

Today in History – 22 April

Today in History – 21 April

Today in History – 20 April

Constructors and the this Keyword in Java

0
java spring boot course
java spring boot course

Table of Contents

  1. Introduction
  2. What is a Constructor?
  3. Types of Constructors
    • Default Constructor
    • Parameterized Constructor
    • Constructor Overloading
  4. Purpose of Constructors
  5. The this Keyword
    • Referring to Instance Variables
    • Calling Constructors
    • Passing Current Object
    • Returning Current Object
  6. Constructor vs Method
  7. Common Pitfalls and Best Practices
  8. Summary

1. Introduction

Constructors are special methods in Java used to initialize objects. When you create a new object using the new keyword, the constructor of the class is automatically invoked. This module will explore how constructors work, how to overload them, and how the this keyword enhances clarity and functionality in object-oriented design.


2. What is a Constructor?

A constructor is a block of code that is called when an instance (object) of a class is created. It has the same name as the class and no return type—not even void.

Basic Syntax:

class MyClass {
MyClass() {
// Constructor code
}
}

Constructors can be used to assign default or user-defined values to instance variables when an object is instantiated.


3. Types of Constructors

a. Default Constructor

If you don’t define any constructor in your class, Java provides a default constructor automatically. This constructor does nothing and is used to create an object without any initial values.

class Book {
String title;
int pages;
}

public class Main {
public static void main(String[] args) {
Book b1 = new Book(); // Implicit default constructor is used
System.out.println(b1.title); // null
System.out.println(b1.pages); // 0
}
}

If you define any constructor yourself, Java no longer provides the default one automatically.


b. Parameterized Constructor

You can define constructors that accept parameters to initialize your object with specific values.

class Book {
String title;
int pages;

Book(String t, int p) {
title = t;
pages = p;
}
}

Now you can create objects with custom values:

Book b = new Book("Effective Java", 416);

c. Constructor Overloading

Constructor overloading allows you to define multiple constructors with different parameter lists within the same class.

class Book {
String title;
int pages;

Book() {
this("Unknown", 0); // Calls another constructor
}

Book(String t) {
this(t, 100); // Default pages if only title is given
}

Book(String t, int p) {
title = t;
pages = p;
}
}

This provides flexibility in object creation and is a powerful example of compile-time polymorphism.


4. Purpose of Constructors

Constructors serve the following main purposes:

  • Initialize instance variables
  • Allocate system resources, if needed
  • Enforce mandatory fields at object creation
  • Enhance readability and maintainability by controlling initialization logic

You should use constructors instead of setters if some values are mandatory to create a valid object.


5. The this Keyword

The this keyword is a reference to the current object. It’s particularly useful in constructors to distinguish between instance variables and parameters or to invoke another constructor.

a. Referring to Instance Variables

When parameter names shadow instance variables, use this to resolve ambiguity:

class Book {
String title;

Book(String title) {
this.title = title; // Refers to instance variable
}
}

Without this, the assignment would be ambiguous and ineffective.


b. Calling Constructors

You can call another constructor from within a constructor using this():

class Book {
String title;
int pages;

Book() {
this("Default Title", 100); // Calls another constructor
}

Book(String title, int pages) {
this.title = title;
this.pages = pages;
}
}

this() must be the first statement in the constructor.


c. Passing Current Object

The current object can be passed as an argument using this. This is useful in chaining or callback patterns.

class Book {
void print(Book b) {
System.out.println("Title: " + b.title);
}

void display() {
print(this); // Passing current object
}
}

d. Returning Current Object

Methods can also return the current object using this. This enables method chaining.

class Book {
String title;

Book setTitle(String title) {
this.title = title;
return this;
}

Book print() {
System.out.println(title);
return this;
}
}
new Book().setTitle("Clean Code").print();

6. Constructor vs Method

FeatureConstructorMethod
NameSame as classAny valid identifier
Return TypeNone (not even void)Must have a return type
InvocationAutomatically during object creationExplicitly using object reference
PurposeInitialize objectPerform operations
Overloading AllowedYesYes
InheritanceNot inheritedInherited

7. Common Pitfalls and Best Practices

Pitfalls

  • Not using this when instance variables are shadowed
  • Attempting to return a value from a constructor
  • Calling this() after a statement in a constructor (must be the first line)

Best Practices

  • Use this only when necessary to reduce verbosity.
  • Always initialize required fields using constructors.
  • Prefer constructor chaining to reduce duplicate code.
  • Avoid performing heavy operations in constructors; keep them lightweight.
  • Use parameterized constructors for better flexibility and clarity.

8. Summary

In this module, you’ve learned:

  • What constructors are and why they are used
  • Differences between default, parameterized, and overloaded constructors
  • The use of the this keyword in accessing variables, calling constructors, and enabling method chaining
  • How constructors differ from methods
  • Best practices for writing clean and efficient constructors

Understanding constructors and the this keyword lays the foundation for advanced object creation strategies, such as factory methods, builder patterns, and dependency injection.

Classes and Objects in Java

0
java spring boot course
java spring boot course

Table of Contents

  1. Introduction to Classes and Objects
  2. Defining a Class
  3. Fields (Instance Variables)
  4. Methods in a Class
  5. Creating Objects
  6. Memory Allocation for Objects
  7. Accessing Members using Objects
  8. Multiple Objects of the Same Class
  9. Anonymous Objects
  10. Summary and Best Practices

1. Introduction to Classes and Objects

Classes and objects are the foundation of object-oriented programming in Java. A class serves as a blueprint for creating objects, while an object is an instance of a class. Classes define properties and behaviors, and objects represent real-world entities with those properties and behaviors.

Understanding how classes and objects work is essential for building modular, maintainable, and scalable Java applications.


2. Defining a Class

In Java, a class is defined using the class keyword. It may contain fields (variables), methods, constructors, blocks, and nested classes or interfaces.

Syntax:

class ClassName {
// Fields
// Methods
}

Example:

class Car {
String color;
int speed;

void drive() {
System.out.println("Driving at " + speed + " km/h");
}
}

In the example above, Car is a class with two fields (color and speed) and one method drive().


3. Fields (Instance Variables)

Fields, also called instance variables, are variables declared inside a class but outside any method. Each object of the class gets its own copy of the instance variables.

class Student {
String name;
int age;
}

Each Student object will have its own name and age.


4. Methods in a Class

Methods define the behavior of the class. They are blocks of code that perform specific actions and can access the class’s fields.

class Student {
String name;
int age;

void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}

Methods can be parameterized, return values, or be void (return nothing). They help encapsulate the logic that operates on the class’s data.


5. Creating Objects

An object is created using the new keyword followed by the class constructor.

Student s1 = new Student();

This statement creates a new Student object and assigns it to reference variable s1.

Each time you use new, a new object is created in memory with its own copy of instance variables.


6. Memory Allocation for Objects

When a Java object is created:

  • Memory is allocated for instance variables in the heap memory.
  • Local variables and references (like s1 in the above example) are stored in stack memory.
  • The object’s methods are stored in method area.

Each object has its own copy of non-static fields, ensuring isolation and independence between instances.


7. Accessing Members using Objects

Object members (fields and methods) are accessed using the dot operator (.).

Student s1 = new Student();
s1.name = "Alice";
s1.age = 22;
s1.displayInfo(); // Output: Name: Alice, Age: 22

You can modify field values or call methods through object references.


8. Multiple Objects of the Same Class

You can create multiple objects from the same class, each with their own field values.

Student s1 = new Student();
s1.name = "Alice";
s1.age = 22;

Student s2 = new Student();
s2.name = "Bob";
s2.age = 25;

Each object maintains its own state. Changing one object’s data does not affect another.


9. Anonymous Objects

An anonymous object is created without a reference variable. These are typically used for a single-use purpose, such as method invocation.

new Student().displayInfo(); // Creates a Student object and calls displayInfo()

Although convenient, anonymous objects cannot be reused since there’s no reference to access them again.

Use anonymous objects when object reuse is unnecessary, such as in testing or method chaining.


10. Summary and Best Practices

Summary

  • A class defines the structure (fields) and behavior (methods) for its objects.
  • An object is an instance of a class created using the new keyword.
  • Objects are stored in heap memory, while their references are on the stack.
  • Each object maintains its own state through its fields.
  • Methods define what an object can do and can be invoked through object references.

Best Practices

  • Use meaningful class and object names (e.g., Student, Employee, BankAccount).
  • Keep fields private and use getters/setters to enforce encapsulation.
  • Avoid using unnecessary public fields as it breaks encapsulation.
  • Prefer creating named objects unless there’s a strong reason for anonymity.
  • Structure classes logically, keeping related data and behavior together.