Inheritance is a fundamental concept in Object-Oriented Programming (OOP) that allows one class to acquire the properties and behaviors (methods) of another class. It promotes code reusability, modularity, and hierarchy in Java programming
Types of Inheritance in Java
1. Single Inheritance
A subclass (child class) inherits from a single superclass (parent class).
Example:
class Animal {
void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound(); // Inherited method
d.bark();
}
}
2. Multilevel Inheritance
A class inherits from another class, which in turn inherits from another class.
Example:
class Animal {
void sound() { System.out.println("Animals make sounds"); }
}
class Mammal extends Animal {
void walk() { System.out.println("Mammals walk"); }
}
class Dog extends Mammal {
void bark() { System.out.println("Dog barks"); }
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
d.walk();
d.bark();
}
}
3. Hierarchical Inheritance
A single superclass has multiple child classes.
Example:
class Animal {
void sound() { System.out.println("Animals make sounds"); }
}
class Dog extends Animal {
void bark() { System.out.println("Dog barks"); }
}
class Cat extends Animal {
void meow() { System.out.println("Cat meows"); }
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
d.bark();
Cat c = new Cat();
c.sound();
c.meow();
}
}
Java does not support multiple inheritance (i.e., inheriting from multiple classes) to avoid ambiguity problems caused by the Diamond Problem.
Instead, Java provides interfaces to achieve multiple inheritance behavior.
Using super Keyword in Inheritance
👉The super keyword is used to refer to the immediate parent class object.
👉It can be used to call parent class methods or constructors.
Example:
class Animal {
Animal() {
System.out.println("Animal Constructor");
}
void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
Dog() {
super(); // Calls parent constructor
System.out.println("Dog Constructor");
}
void sound() {
super.sound(); // Calls parent method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
}
}
Advantages of Inheritance
✔ Code reusability – Avoids code duplication.
✔ Enhances modularity – Simplifies program structure.
✔ Extensibility – Easy to add new features.
✔ Reduces maintenance effort – Common code is maintained in a single class.
Super and Subclass
✨In Java Inheritance, the concepts of superclass and subclass are fundamental.
✨Superclass (Parent Class): The class whose properties and methods are inherited by another class.
✨Subclass (Child Class): The class that inherits from another class. It can have additional properties and methods.
1. Defining a Superclass and Subclass
class Animal { // Superclass
void eat() {
System.out.println("This animal eats food");
}
}
class Dog extends Animal { // Subclass
void bark() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // Inherited from Animal class
d.bark(); // Defined in Dog class
}
}
Output:
This animal eats food
Dog barks
2. super Keyword in Java
The super keyword is used inside a subclass to refer to its immediate superclass. It is commonly used for:
1. Calling superclass methods
2. Accessing superclass fields
3. Calling superclass constructors
a) Using super to Call Superclass Methods
class Animal {
void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
void sound() {
super.sound(); // Calls superclass method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
}
}
Output:
Animals make sounds
Dog barks
b) Using super to Access Superclass Fields
class Animal {
String type = "Wild Animal";
}
class Dog extends Animal {
String type = "Domestic Animal";
void displayType() {
System.out.println("Dog type: " + type); // Child class field
System.out.println("Animal type: " + super.type); // Superclass field
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.displayType();
}
}
Output:
Dog type: Domestic Animal
Animal type: Wild Animal
c) Using super to Call Superclass Constructor
class Animal {
Animal() {
System.out.println("Animal Constructor");
}
}
class Dog extends Animal {
Dog() {
super(); // Calls superclass constructor
System.out.println("Dog Constructor");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
}
}
Output:
Animal Constructor
Dog Constructor
Advantages of Using Superclass and Subclass
✔ Code reusability – Reduces duplication.
✔ Extensibility – Easily add new functionalities.
✔ Polymorphism – Allows overriding methods for dynamic behavior.
✔ Better organization – Helps in creating a structured hierarchical
Method Overriding
Method Overriding is an OOP feature in Java where a subclass provides a specific implementation of a method that is already defined in its superclass.
Key Rules for Method Overriding
1. Same Method Signature – The method in the subclass must have the same name, return type, and parameters as in the superclass.
2. Inheritance is Required – The subclass must extend the superclass.
3. Cannot Reduce Visibility – The overridden method cannot have a more restrictive access modifier (e.g., public method in the superclass cannot be overridden as private).
4. @Override Annotation (Optional but Recommended) – Helps prevent mistakes and improves code readability.
5. Final Methods Cannot Be Overridden – A method declared with final in the superclass cannot be overridden.
6. Static Methods Cannot Be Overridden – They are class-level methods, not instance-level.
7. Constructors Cannot Be Overridden – Because constructors are not inherited.
Example of Method Overriding
class Animal {
void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
@Override
void sound() { // Overriding the superclass method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
a.sound(); // Calls the overridden method in Dog
}
}
Output:
Dog barks
Using super to Call Superclass Method
If you need to access the superclass method inside the subclass, use super.methodName().
class Animal {
void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
@Override
void sound() {
super.sound(); // Calls the superclass method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
}
}
Output:
Animals make sounds
Dog barks
Overriding vs Overloading
Method Overriding with final, static, and private Methods
1. Final Methods Cannot Be Overridden
class Animal {
final void sound() {
System.out.println("Animals make sounds");
}
}
class Dog extends Animal {
// Compilation Error: Cannot override final method
// void sound() { System.out.println("Dog barks"); }
}
2. Static Methods Are Not Overridden, They Are Hidden
class Animal {
static void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
static void sound() { // Hides the method, does not override
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.sound(); // Calls Animal's method due to static binding
}
}
Output:
Animal makes sound
3. Private Methods Are Not Inherited
class Animal {
private void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
// This is NOT overriding; it's a new method
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound(); // Calls Dog's method, not Animal's
}
}
Output:
Dog barks
Using @Override Annotation
Although not required, it's recommended to use @Override to avoid mistakes when overriding.
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override // Ensures method is correctly overridden
void sound() {
System.out.println("Dog barks");
}
}
Polymorphism and Method Overriding
Overriding enables runtime polymorphism, allowing Java to determine which method to call at runtime.
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.sound(); // Calls Dog's sound() method
a2.sound(); // Calls Cat's sound() method
}
}
Output:
Dog barks
Cat meows
Advantages of Method Overriding
✔ Supports Runtime Polymorphism – Helps achieve dynamic method dispatch.
✔ Increases Code Reusability – Enhances maintainability.
✔ Provides Specific Implementations – Allows customizing inherited methods.
Object Class
In Java, the Object class is the superclass of all classes. Every class in Java implicitly inherits from java.lang.Object, either directly or indirectly.
Key Features of the Object Class
✨It is part of java.lang package.
✨It provides common methods that all Java objects can use.
✨If a class does not explicitly extend another class, it automatically extends Object.
Methods in the Object Class
The Object class provides several useful methods, which can be overridden in subclasses:
1. toString() Method
The toString() method returns a string representation of the object.
By default, it returns:
ClassName@hashcode
Example
class Car {
String model;
Car(String model) {
this.model = model;
}
@Override
public String toString() {
return "Car model: " + model;
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("Tesla Model S");
System.out.println(car); // Implicitly calls car.toString()
}
}
Output:
Car model: Tesla Model S
2. equals(Object obj) Method
👉Used to compare two objects.
👉Default implementation compares memory addresses (reference comparison).
✨Should be overridden to compare object properties.
Example (Default vs. Overridden equals)
class Car {
String model;
Car(String model) {
this.model = model;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // Same reference
if (obj == null || getClass() != obj.getClass()) return false;
Car car = (Car) obj;
return model.equals(car.model);
}
}
public class Main {
public static void main(String[] args) {
Car c1 = new Car("Tesla Model S");
Car c2 = new Car("Tesla Model S");
System.out.println(c1.equals(c2)); // true (after overriding)
}
}
Output:
true
3. hashCode() Method
👉Returns an integer hash code for the object.
👉Used in hash-based collections like HashMap, HashSet.
👉Should be overridden along with equals().
Example
class Car {
String model;
Car(String model) {
this.model = model;
}
@Override
public int hashCode() {
return model.hashCode();
}
}
public class Main {
public static void main(String[] args) {
Car c1 = new Car("Tesla Model S");
Car c2 = new Car("Tesla Model S");
System.out.println(c1.hashCode() == c2.hashCode()); // true
}
}
4. getClass() Method
Returns the runtime class of an object.
Example
class Car {}
public class Main {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.getClass().getName()); // Outputs: Car
}
}
5. clone() Method
👉Used to create a copy of an object.
👉Requires the Cloneable interface.
Example
class Car implements Cloneable {
String model;
Car(String model) {
this.model = model;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Car c1 = new Car("Tesla");
Car c2 = (Car) c1.clone();
System.out.println(c1.model); // Tesla
System.out.println(c2.model); // Tesla
}
}
6. finalize() Method
👉Called by the Garbage Collector before destroying an object.
👉Not recommended for resource cleanup (use try-with-resources instead).
Example
class Car {
@Override
protected void finalize() {
System.out.println("Car object is being destroyed");
}
}
public class Main {
public static void main(String[] args) {
Car c = new Car();
c = null;
System.gc(); // Requests garbage collection
}
}
7. wait(), notify(), notifyAll() Methods
Used in multithreading to handle inter-thread communication.
Example (Using wait() and notify())
class SharedResource {
synchronized void produce() throws InterruptedException {
System.out.println("Producing...");
wait(); // Releases the lock and waits
System.out.println("Resumed after notification");
}
synchronized void consume() {
System.out.println("Consuming...");
notify(); // Notifies the waiting thread
}
}
public class Main {
public static void main(String[] args) {
SharedResource sr = new SharedResource();
new Thread(() -> {
try {
sr.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> sr.consume()).start();
}
}
Why is Object Class Important?
✔ Universal Superclass – Every Java class extends Object.
✔ Provides Common Methods – Methods like toString(), equals(), and hashCode() are essential for object manipulation.
✔ Supports Polymorphism – Enables generic programming.
Dynamic Binding
Dynamic Binding (also known as Late Binding or Runtime Polymorphism) in Java refers to the process where method calls are resolved at runtime instead of compile-time. This allows method overriding to work dynamically.
Key Characteristics of Dynamic Binding
✔ Happens at runtime.
✔ Used in method overriding.
✔ Uses upcasting (superclass reference pointing to subclass object).
✔ Achieved through dynamic method dispatch (method resolution happens dynamically).
Example of Dynamic Binding
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal a; // Superclass reference
a = new Dog(); // Upcasting
a.sound(); // Calls Dog's sound() method (Dynamic Binding)
a = new Cat(); // Upcasting
a.sound(); // Calls Cat's sound() method (Dynamic Binding)
}
}
Output:
Dog barks
Cat meows
How It Works?
💫Animal a = new Dog(); → a is of type Animal, but it holds a Dog object.
💫When a.sound() is called, Java resolves the method at runtime and executes Dog's sound() method.
💫Similarly, when a refers to a Cat object, the Cat's sound() method is executed.
Compile-time Binding vs. Runtime Binding
Example of Compile-time vs. Runtime Binding
class CompileTimeBinding {
void show(int a) {
System.out.println("Integer: " + a);
}
void show(double a) {
System.out.println("Double: " + a);
}
}
class RunTimeBinding {
void display() {
System.out.println("Superclass method");
}
}
class SubClass extends RunTimeBinding {
@Override
void display() {
System.out.println("Overridden method in subclass");
}
}
public class Main {
public static void main(String[] args) {
// Compile-time Binding (Overloading)
CompileTimeBinding obj1 = new CompileTimeBinding();
obj1.show(5); // Integer version
obj1.show(5.5); // Double version
// Runtime Binding (Overriding)
RunTimeBinding obj2 = new SubClass(); // Upcasting
obj2.display(); // Calls subclass method dynamically
}
}
Output:
Integer: 5
Double: 5.5
Overridden method in subclass
Why Use Dynamic Binding?
✔ Supports Runtime Polymorphism – Enables flexible code execution.
✔ Reduces Code Coupling – Allows using superclass references for various subclass objects.
✔ Extensible Design – New subclasses can be added without modifying existing code.
Example: Real-world Scenario
Imagine a Payment system where different payment methods (CreditCard, PayPal, UPI) are processed using a common interface.
class Payment {
void pay() {
System.out.println("Processing payment...");
}
}
class CreditCard extends Payment {
@Override
void pay() {
System.out.println("Payment done using Credit Card");
}
}
class PayPal extends Payment {
@Override
void pay() {
System.out.println("Payment done using PayPal");
}
}
public class Main {
public static void main(String[] args) {
Payment p;
p = new CreditCard();
p.pay(); // Calls CreditCard's pay() method
p = new PayPal();
p.pay(); // Calls PayPal's pay() method
}
}
Output:
Payment done using Credit Card
Payment done using PayPal
Here, Payment reference can call different payment methods dynamically at runtime.
Conclusion
💫Dynamic Binding enables method overriding in Java.
💫It allows runtime polymorphism, making code more flexible and maintainable.
💫Achieved using superclass references and overridden methods.
Generic Programming
Generic Programming in Java allows writing code that is flexible, reusable, and type-safe. It enables defining classes, interfaces, and methods with type parameters, which means they can work with any data type without specifying it in advance.
Why Use Generics?
✔ Type Safety – Prevents ClassCastException at runtime.
✔ Code Reusability – One class/method works with multiple data types.
✔ Compile-time Checking – Errors are caught at compile time rather than runtime.
✔ Eliminates Type Casting – No need for explicit type conversions.
1. Generic Classes
A generic class uses a type parameter (T), which stands for "Type".
Example: Generic Box Class
class Box<T> { // Generic class with type parameter T
private T value;
void setValue(T value) {
this.value = value;
}
T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>(); // Box for Integer
intBox.setValue(10);
System.out.println(intBox.getValue()); // Output: 10
Box<String> strBox = new Box<>(); // Box for String
strBox.setValue("Hello");
System.out.println(strBox.getValue()); // Output: Hello
}
}
Here, T is replaced with Integer and String at runtime.
2. Generic Methods
A generic method allows type parameters inside methods instead of whole classes.
Example: Generic Print Method
class GenericMethodExample {
// Generic method with type <T>
static <T> void printArray(T[] arr) {
for (T element : arr) {
System.out.print(element + " ");
}
System.out.println();
}
}
public class Main {
public static void main(String[] args) {
Integer[] intArr = {1, 2, 3};
String[] strArr = {"A", "B", "C"};
GenericMethodExample.printArray(intArr); // Output: 1 2 3
GenericMethodExample.printArray(strArr); // Output: A B C
}
}
3. Generic Interfaces
A generic interface allows defining reusable data structures like collections.
Example: Generic Comparable Interface
interface MinMax<T extends Comparable<T>> {
T min();
T max();
}
class Numbers<T extends Comparable<T>> implements MinMax<T> {
private T[] values;
Numbers(T[] values) {
this.values = values;
}
@Override
public T min() {
T min = values[0];
for (T val : values) {
if (val.compareTo(min) < 0) min = val;
}
return min;
}
@Override
public T max() {
T max = values[0];
for (T val : values) {
if (val.compareTo(max) > 0) max = val;
}
return max;
}
}
public class Main {
public static void main(String[] args) {
Integer[] numbers = {3, 5, 1, 9, 2};
Numbers<Integer> obj = new Numbers<>(numbers);
System.out.println("Min: " + obj.min()); // Output: Min: 1
System.out.println("Max: " + obj.max()); // Output: Max: 9
}
}
Here, the MinMax interface is generic, and the Numbers class implements it for any Comparable type.
4. Bounded Type Parameters (extends)
Sometimes, we want a type parameter to extend a specific class or implement an interface.
Example: Bounded Type (extends Number)
class Calculator<T extends Number> { // T must be a subclass of Number
Casting Objects
Object Casting in Java refers to converting one type of object reference to another. It is mainly used in inheritance when working with superclasses and subclasses.
Types of Object Casting
1. Upcasting (Implicit Casting)
💫Converting a subclass reference into a superclass reference.
💫Automatic and safe (No explicit cast needed).
2. Downcasting (Explicit Casting)
✨Converting a superclass reference back to a subclass reference.
✨Needs explicit casting and should be done carefully using instanceof.
1. Upcasting (Implicit Casting)
✔ Automatically converts a subclass reference into a superclass reference.
✔ The object still retains its subclass behavior but is limited to superclass methods.
Example of Upcasting
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting (Implicit)
a.sound(); // Allowed (Inherited method)
// a.bark(); // Not Allowed (Compile-time error)
}
}
Output:
Animal makes a sound
Here, Dog is upcasted to Animal. The bark() method is not accessible because a is treated as an Animal.
2. Downcasting (Explicit Casting)
✔ Converting a superclass reference back into a subclass reference.
✔ Requires explicit casting (Subclass) reference.
✔ Should be used only if the object was originally of the subclass type.
✔ Risky – If downcasting is done incorrectly, it throws ClassCastException.
Example of Downcasting
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting (Implicit)
Dog d = (Dog) a; // Downcasting (Explicit)
d.bark(); // Allowed now
}
}
Output:
Dog barks
✔ Downcasting works correctly because a was originally a Dog.
3. Preventing ClassCastException using instanceof
If the object is not actually of the subclass type, downcasting can cause runtime errors (ClassCastException).
✔ Always check with instanceof before downcasting.
Example: Safe Downcasting with instanceof
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
void meow() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
if (a instanceof Dog) {
Dog d = (Dog) a; // Safe Downcasting
d.bark();
} else {
System.out.println("Downcasting not possible");
}
}
}
Output:
Dog barks
✔ instanceof ensures safe downcasting and prevents ClassCastException.
4. Casting Objects in Hierarchy
✔ Casting only works within the same inheritance hierarchy.
✔ Unrelated classes cannot be cast.
Incorrect Downcasting (Throws Exception)
public class Main {
public static void main(String[] args) {
Animal a = new Cat(); // Upcasting
Dog d = (Dog) a; // ERROR: Cat cannot be cast to Dog
d.bark();
}
}
Error:
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
✔ Fix: Always check with instanceof before casting.
5. Object Casting with Interfaces
✔ An interface reference can refer to any class that implements it.
✔ Upcasting works automatically when storing an object in an interface reference.
✔ Downcasting requires explicit casting.
Example: Casting with Interfaces
interface Animal {
void sound();
}
class Dog implements Animal {
public void sound() {
System.out.println("Dog barks");
}
void guard() {
System.out.println("Dog guards the house");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting (Implicit)
a.sound();
if (a instanceof Dog) {
Dog d = (Dog) a; // Downcasting (Explicit)
d.guard(); // Now allowed
}
}
}
Output:
Dog barks
Dog guards the house
✔ Upcasting works automatically with interfaces.
✔ Downcasting requires instanceof to avoid errors.
instanceof Operator
The instanceof operator in Java is used to check whether an object is an instance of a specific class or a subclass of that class. It helps prevent ClassCastException when performing downcasting.
Syntax:
objectReference instanceof ClassName
✔ Returns true if objectReference is an instance of ClassName or its subclass.
✔ Returns false if objectReference is null or belongs to an unrelated class.
1. Basic Example of instanceof
class Animal { }
class Dog extends Animal { }
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
System.out.println(a instanceof Animal); // true
System.out.println(a instanceof Dog); // true
}
}
✔ a instanceof Animal → true because Dog is a subclass of Animal.
✔ a instanceof Dog → true because a refers to a Dog object.
2. Using instanceof to Prevent ClassCastException (Safe Downcasting)
class Animal { }
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
class Cat extends Animal { }
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
if (a instanceof Dog) { // Check before downcasting
Dog d = (Dog) a; // Safe downcasting
d.bark();
}
Animal b = new Cat();
if (b instanceof Dog) { // Prevents ClassCastException
Dog d = (Dog) b;
d.bark();
} else {
System.out.println("b is not an instance of Dog");
}
}
}
✔ The first downcasting (Dog d = (Dog) a) works safely.
✔ The second one (Dog d = (Dog) b) is prevented because b is a Cat, not a Dog.
Output:
Dog barks
b is not an instance of Dog
3. instanceof with Interfaces
✔ instanceof can check if an object implements an interface.
Example: Checking Interface Implementation
interface Animal { }
class Dog implements Animal { }
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
System.out.println(d instanceof Animal); // true
System.out.println(d instanceof Dog); // true
}
}
✔ d instanceof Animal → true because Dog implements Animal.
✔ d instanceof Dog → true because d is a Dog object.
4. instanceof with Null Objects
✔ If the object is null, instanceof always returns false.
Example: null Check
class Animal { }
public class Main {
public static void main(String[] args) {
Animal a = null;
System.out.println(a instanceof Animal); // false
}
}
✔ Prevents NullPointerException since null is never an instance of any class.
5. instanceof with Abstract Classes
✔ Works with abstract classes since objects are created from subclasses.
Example: Checking an Abstract Class
abstract class Animal { }
class Dog extends Animal { }
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
System.out.println(a instanceof Animal); // true
System.out.println(a instanceof Dog); // true
}
}
✔ Even though Animal is abstract, Dog is an instance of Animal.
6. instanceof in Inheritance Hierarchy
✔ Works for parent-child relationships in an inheritance tree.
Example: Checking Multiple Levels of Inheritance
class Animal { }
class Mammal extends Animal { }
class Dog extends Mammal { }
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
System.out.println(d instanceof Dog); // true
System.out.println(d instanceof Mammal); // true
System.out.println(d instanceof Animal); // true
}
}
✔ Dog is an instance of Dog, Mammal, and Animal because of inheritance.
7. instanceof with Generic Classes
✔ Works even when using generics in Java.
Example: Generic Box
class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>(10);
System.out.println(intBox instanceof Box); // true
}
}
✔ intBox instanceof Box → true because intBox is an object of Box<T>.
8. instanceof with Arrays
✔ Arrays can be checked using instanceof.
Example: Checking Array Types
public class Main {
public static void main(String[] args) {
int[] numbers = {1, 2, 3};
System.out.println(numbers instanceof int[]); // true
System.out.println(numbers instanceof Object); // true
}
}
✔ Arrays are instances of Object in Java.
When to Use instanceof?
✔ Before Downcasting – Prevents ClassCastException.
✔ Checking Interface Implementation – Ensures correct object type.
✔ Handling Null Objects – Avoids NullPointerException.
✔ Working with Collections – Verifies data types in lists/maps.
Abstract Class
An abstract class in Java is a class that cannot be instantiated and is used as a blueprint for other classes. It can have abstract methods (without a body) and concrete methods (with implementation).
1. Key Features of Abstract Class
✔ Cannot be instantiated directly.
✔ Can have both abstract and concrete methods.
✔ Can have constructors, static methods, and final methods.
✔ Supports inheritance (subclasses must implement abstract methods or be abstract themselves).
2. Defining an Abstract Class
Use the abstract keyword:
abstract class Animal {
abstract void sound(); // Abstract method (No body)
void eat() { // Concrete method (Has body)
System.out.println("Animal is eating");
}
}
3. Creating a Subclass
✔ A subclass must implement all abstract methods or be declared abstract itself.
Example: Implementing an Abstract Class
abstract class Animal {
abstract void sound(); // Abstract method
void eat() { // Concrete method
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
void sound() { // Implementing abstract method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound(); // Dog barks
d.eat(); // Animal is eating
}
}
✔ sound() is implemented in Dog.
✔ eat() is inherited from Animal.
Output:
Dog barks
Animal is eating
4. Abstract Class with Constructors
✔ An abstract class can have constructors, which are called when a subclass is instantiated.
Example: Abstract Class Constructor
abstract class Animal {
Animal() { // Constructor
System.out.println("Animal constructor called");
}
abstract void sound();
}
class Dog extends Animal {
Dog() {
System.out.println("Dog constructor called");
}
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
}
}
Output:
Animal constructor called
Dog constructor called
Dog barks
✔ Superclass constructor runs first before the subclass constructor.
5. Abstract Class vs Interface
6. Abstract Class with Multiple Subclasses
✔ Multiple subclasses can extend the same abstract class.
Example: Multiple Subclasses
abstract class Animal {
abstract void sound();
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.sound(); // Dog barks
a2.sound(); // Cat meows
}
}
✔ Polymorphism: Different implementations of sound().
7. Abstract Class with Static Methods
✔ Static methods in an abstract class can be called without an instance.
Example: Static Method in Abstract Class
abstract class Animal {
static void show() {
System.out.println("Static method in abstract class");
}
}
public class Main {
public static void main(String[] args) {
Animal.show(); // Calling static method directly
}
}
✔ Output: Static method in abstract class
8. Abstract Class with Final Methods
✔ Final methods cannot be overridden in subclasses.
Example: Final Method
abstract class Animal {
final void sleep() {
System.out.println("Animal is sleeping");
}
}
class Dog extends Animal {
// void sleep() { } // ERROR: Cannot override final method
}
✔ sleep() cannot be overridden because it is final.
Summary
✔ Abstract classes cannot be instantiated but can have constructors.
✔ Abstract methods must be implemented by subclasses.
✔ Can contain both abstract and concrete methods.
✔ Supports inheritance but not multiple inheritance.
✔ Useful for defining a base class with common behavior for subclasses.
Interface in Java
An interface in Java is a blueprint for a class that contains only abstract methods (before Java 8) and static or final variables. It allows multiple classes to implement the same behavior without inheritance limitations.
1. Key Features of an Interface
✔ 100% abstraction (before Java 8, all methods were abstract).
✔ No object creation (cannot be instantiated).
✔ Multiple inheritance supported (a class can implement multiple interfaces).
✔ Only abstract methods (before Java 8).
✔ Only public, static, and final variables.
✔ Allows default and static methods (from Java 8).
2. Declaring and Implementing an Interface
Use the interface keyword. A class implements an interface using implements.
Example: Interface with Abstract Methods
interface Animal {
void sound(); // Abstract method
}
class Dog implements Animal {
public void sound() { // Implementing interface method
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.sound(); // Dog barks
}
}
✔ sound() is implemented in Dog since Animal is an interface.
Output:
Dog barks
3. Interface with Multiple Methods
✔ A class must implement all methods of an interface.
interface Vehicle {
void start();
void stop();
}
class Car implements Vehicle {
public void start() {
System.out.println("Car is starting");
}
public void stop() {
System.out.println("Car is stopping");
}
}
public class Main {
public static void main(String[] args) {
Car c = new Car();
c.start();
c.stop();
}
}
Output:
Car is starting
Car is stopping
✔ All methods of Vehicle are implemented in Car.
4. Interface Variables (Final & Static)
✔ Interface variables are public static final by default.
Example: Constant Variables in Interface
interface Game {
int PLAYERS = 2; // public static final
}
public class Main {
public static void main(String[] args) {
System.out.println(Game.PLAYERS); // 2
}
}
✔ PLAYERS is a constant (final).
✔ Cannot be modified (Game.PLAYERS = 3; gives an error).
5. Multiple Interfaces in One Class
✔ A class can implement multiple interfaces (unlike multiple inheritance with classes).
Example: Multiple Interfaces
interface Animal {
void eat();
}
interface Bird {
void fly();
}
class Sparrow implements Animal, Bird { // Multiple interfaces
public void eat() {
System.out.println("Sparrow eats grains");
}
public void fly() {
System.out.println("Sparrow flies high");
}
}
public class Main {
public static void main(String[] args) {
Sparrow s = new Sparrow();
s.eat();
s.fly();
}
}
Output:
Sparrow eats grains
Sparrow flies high
✔ Sparrow implements both Animal and Bird, showing multiple inheritance.
6. Java 8 Features in Interfaces
Java 8 introduced:
✔ Default Methods (can have a body in an interface).
✔ Static Methods (can be called using the interface name).
Example: Default Method
interface Vehicle {
default void speed() { // Default method
System.out.println("Vehicle has a speed limit");
}
}
class Car implements Vehicle { }
public class Main {
public static void main(String[] args) {
Car c = new Car();
c.speed(); // Vehicle has a speed limit
}
}
✔ speed() is inherited without overriding.
Example: Static Method in Interface
interface MathUtils {
static int square(int x) {
return x * x;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(MathUtils.square(4)); // 16
}
}
✔ Called using the interface name (MathUtils.square(4)).
7. Interface vs Abstract Class
8. Functional Interfaces (Java 8)
✔ A functional interface is an interface with only one abstract method.
✔ Used for lambda expressions.
✔ Example: Runnable, Comparable.
Example: Functional Interface with Lambda
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator c = (a, b) -> a + b; // Lambda expression
System.out.println(c.add(5, 3)); // 8
}
}
✔ Lambda simplifies interface implementation.
9. Marker Interfaces
✔ Interfaces without methods (e.g., Serializable, Cloneable).
✔ Used to mark classes for special behavior.
Summary
✔ Interfaces provide 100% abstraction (before Java 8).
✔ Multiple interfaces can be implemented by a class.
✔ All variables are public static final by default.
✔ Java 8 introduced default and static methods.
✔ Interfaces allow multiple inheritance, unlike classes
Packages in java
A package in Java is a container for classes, interfaces, and sub-packages that helps organize code and prevent naming conflicts. Packages are similar to folders in a file system.
1. Types of Packages
Java has two types of packages:
✔ Built-in Packages – Provided by Java (e.g., java.util, java.io).
✔ User-defined Packages – Created by developers to organize their code.
2. Creating a User-Defined Package
✔ Use the package keyword at the beginning of the file.
✔ The file should be saved inside a folder matching the package name.
Example: Creating a Package
Step 1: Create a Package (mypackage/Animal.java)
package mypackage; // Define package
public class Animal {
public void display() {
System.out.println("This is an animal");
}
}
✔ The class Animal belongs to package mypackage.
✔ Save the file inside a folder named mypackage.
Step 2: Use the Package (Main.java)
import mypackage.Animal; // Import package
public class Main {
public static void main(String[] args) {
Animal a = new Animal(); // Creating an object
a.display();
}
}
✔ import mypackage.Animal; allows access to the Animal class.
Output:
This is an animal
3. Using import to Access Packages
✔ import package_name.class_name; → Imports one specific class.
✔ import package_name.*; → Imports all classes in the package.
Example: Importing All Classes
import mypackage.*;
public class Main {
public static void main(String[] args) {
Animal a = new Animal();
a.display();
}
}
✔ import mypackage.*; imports all classes from mypackage.
4. Access Modifiers and Packages
✔ public → Accessible everywhere.
✔ protected → Accessible in same package & subclasses in other packages.
✔ (default) → Accessible only within the same package.
✔ private → Accessible only within the same class.
5. Sub-Packages in Java
✔ A package can contain sub-packages.
✔ Syntax: package parent_package.subpackage;
Example: Creating a Sub-Package
File: mypackage/animals/Dog.java
package mypackage.animals; // Sub-package
public class Dog {
public void bark() {
System.out.println("Dog barks");
}
}
✔ The class Dog belongs to mypackage.animals.
Using the Sub-Package (Main.java)
import mypackage.animals.Dog; // Import sub-package
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.bark();
}
}
✔ Output: Dog barks
6. Built-in Java Packages
Java provides many predefined packages, such as:
✔ No need to import java.lang (automatically included).
Example: Using java.util Package
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
System.out.println(list); // [Apple, Banana]
}
}
✔ ArrayList is part of java.util, so it must be imported.
7. Accessing a Package Without import
✔ Use fully qualified name instead of import.
public class Main {
public static void main(String[] args) {
mypackage.Animal a = new mypackage.Animal();
a.display();
}
}
✔ No need to use import, but the code is longer.
8. Compiling and Running Java Packages
Step 1: Compile the Package
javac -d . Animal.java
✔ -d . creates the package directory automatically.
Step 2: Compile the Main Class
javac -d . Main.java
✔ This ensures the main class recognizes the package.
Step 3: Run the Program
java Main
✔ Executes the program using the compiled package.
Summary
✔ Packages organize Java classes like folders in a file system.
✔ User-defined packages are created using the package keyword.
✔ import is used to access classes from other packages.
✔ Built-in packages include java.util, java.io, java.sql, etc.
✔ Packages help prevent naming conflicts and improve code modularity.
util Package
The java.util package is one of the most commonly used built-in Java packages. It provides utility classes for data structures, collections, date/time manipulation, random numbers, and more.
1. Commonly Used Classes in java.util
2. Using java.util Classes
✔ You must import java.util classes before using them:
import java.util.ArrayList;
import java.util.Scanner;
✔ Alternatively, you can import all classes in java.util:
import java.util.*;
3. Example: ArrayList (Dynamic Array)
✔ ArrayList allows dynamic resizing and fast access.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
System.out.println(list); // [Apple, Banana, Cherry]
list.remove("Banana");
System.out.println(list); // [Apple, Cherry]
}
}
✔ Output:
[Apple, Banana, Cherry]
[Apple, Cherry]
✔ ArrayList allows fast retrieval but slower insertion/deletion compared to LinkedList.
4. Example: HashMap (Key-Value Pairs)
✔ HashMap stores unique keys with associated values.
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 90);
scores.put("Charlie", 75);
System.out.println(scores.get("Alice")); // 85
}
}
✔ Output: 85
✔ Fast retrieval using keys.
✔ Keys must be unique.
5. Example: HashSet (Unique Elements)
✔ HashSet stores unique elements only and does not maintain order.
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(10); // Duplicate, ignored
System.out.println(set); // [10, 20]
}
}
✔ Output: [10, 20]
✔ No duplicate values allowed.
6. Example: Scanner (User Input)
✔ Scanner reads keyboard input from the user.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Enter your name: ");
String name = sc.nextLine();
System.out.println("Hello, " + name + "!");
sc.close(); // Close scanner
}
}
✔ Output:
Enter your name: John
Hello, John!
✔ nextLine() reads full input, while nextInt() reads integers.
7. Example: Random (Generating Random Numbers)
✔ Random generates pseudo-random numbers.
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random rand = new Random();
int randomNumber = rand.nextInt(100); // Random number between 0-99
System.out.println("Random Number: " + randomNumber);
}
}
✔ Output:
Random Number: 47
✔ nextInt(100) generates 0 to 99.
✔ nextDouble() generates decimal values between 0.0 and 1.0.
8. Example: Date (Current Date and Time)
✔ Date represents the current date/time.
import java.util.Date;
public class Main {
public static void main(String[] args) {
Date now = new Date();
System.out.println(now);
}
}
✔ Output (varies by time):
Mon Apr 01 10:30:00 IST 2025
9. Example: Calendar (Date Manipulation)
✔ Calendar allows date/time calculations.
import java.util.Calendar;
public class Main {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
System.out.println("Year: " + cal.get(Calendar.YEAR));
System.out.println("Month: " + (cal.get(Calendar.MONTH) + 1)); // 0-based
System.out.println("Day: " + cal.get(Calendar.DAY_OF_MONTH));
}
}
✔ Output:
Year: 2025
Month: 4
Day: 1
✔ MONTH starts from 0 (so add 1).
10. Example: Collections (Sorting & Searching)
✔ Collections provides utility methods for collections.
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
Collections.sort(numbers);
System.out.println(numbers); // [1, 2, 5, 8]
}
}
✔ Output: [1, 2, 5, 8]
✔ Collections.sort() sorts numbers in ascending order.
Summary
✔ java.util provides useful data structures, date/time utilities, random number generation, user input handling, and collection utilities.
✔ Common classes include ArrayList, HashMap, HashSet, Scanner, Random, Date, Calendar, and Collections.
✔ Collections framework (ArrayList, HashMap, etc.) is heavily used in Java programming