This guide contains the most frequently asked Java interview questions and answers for developers with 2–3 years of experience. It covers Core Java, OOP Concepts, Collections Framework, Exception Handling, Multithreading, JVM Architecture, Class Loading, Java 8 Features, and other key topics commonly asked in technical interviews.
Table of Contents
- Method Overloading vs Method Overriding in Java
- What is a ClassLoader in Java?
- What is inheritance in Java?
- Why can't we override a static method?
- What is Dynamic Method Dispatch?
- What is Java Classpath?
- What does the
volatilekeyword do? - When does
finallynot execute? - StringBuffer vs StringBuilder
- Vector vs ArrayList
- Why is String immutable?
- HashMap vs HashSet
- Producer–Consumer problem & benefits
- How to create an immutable class?
- Fail-fast vs Fail-safe iterators
- What is an interface? Features?
- SOLID principles
- What is the difference between
==andequals()? - What is the
instanceofoperator? superkeyword use cases- What is
finalize()? Why discouraged? - What are the main Collections Framework interfaces?
- What is a Thread? How to Create a Thread in Java?
- What is a Virtual Thread in Java? How to Create It? When to Use It?
- What is deadlock? How to avoid?
- What is synchronization?
- Shallow Copy vs Deep Copy in Java
- What Are Annotations in Java? How to Create a Custom Annotation?
- What is the Java Memory Model (JMM)?
- What is Reflection in Java?
- What Does
join()Do in Java? - Benefits of Java Stream API
- What is Try-With-Resources in Java?
- Comparable vs Comparator in Java
- What is a Marker Interface in Java?
- What is
clone()in Java? - What Are Default Methods in Interfaces?
- What Are Checked vs Unchecked Exceptions in Java?
- Difference between
throwandthrows - What is
Optionalin Java? - What is a functional interface?
- What Are Lambda Expressions in Java?
- When to Use Abstract Class and When to Use Interface in Java?
- What is Serialization in Java?
- Difference Between
HashMapandConcurrentHashMapin Java - Difference Between
ArrayListandLinkedListin Java - Difference Between
map()andflatMap()in Java Streams - What is a Race Condition in Java?
- What Are Atomic Classes in Java?
- JVM vs JRE vs JDK in Java
1) Method Overloading vs Method Over recognizer
Introduction
Method Overloading and Method Overriding are two important concepts in Java that support Polymorphism. Although their names sound similar, they serve different purposes and work differently.
Method Overloading
Definition
Method Overloading occurs when multiple methods in the same class have the same name but different parameter lists.
This is also known as Compile-Time Polymorphism because the compiler determines which method to call during compilation.
Rules for Method Overloading
✅ Method name must be the same
✅ Parameters must be different:
- Different number of parameters
- Different data types
- Different order of parameters
✅ Return type may be different
❌ Changing only the return type is not considered overloading
Example
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
Output
Calculator calc = new Calculator();
System.out.println(calc.add(10, 20)); // 30
System.out.println(calc.add(10.5, 20.5)); // 31.0
System.out.println(calc.add(10, 20, 30)); // 60
Advantages of Overloading
- Improves code readability
- Increases code reusability
- Allows methods to perform similar operations with different inputs
Method Overriding
Definition
Method Overriding occurs when a child class provides its own implementation of a method that already exists in the parent class.
This is also known as Runtime Polymorphism because the method call is resolved during execution.
Rules for Method Overriding
✅ Requires inheritance
✅ Method name must be the same
✅ Parameters must be exactly the same
✅ Return type must be same or covariant
✅ Access modifier cannot be more restrictive
❌ Static methods cannot be overridden
❌ Final methods cannot be overridden
❌ Private methods cannot be overridden
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 Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound();
}
}
Output
Dog barks
Why Runtime Polymorphism?
Animal animal = new Dog();
animal.sound();
Although the reference type is Animal, the actual object is Dog.
At runtime, Java determines which method implementation should be executed.
This process is called Dynamic Method Dispatch.
Comparison Table
| Feature | Method Overloading | Method Overriding |
|---|---|---|
| Definition | Same method name with different parameters | Same method implementation redefined in child class |
| Inheritance Required | No | Yes |
| Method Name | Same | Same |
| Parameters | Must be different | Must be same |
| Return Type | Can differ | Same or Covariant |
| Binding | Compile Time | Runtime |
| Polymorphism Type | Static Polymorphism | Dynamic Polymorphism |
| Performance | Faster | Slightly slower due to runtime resolution |
| Annotation | Not required | @Override recommended |
Interview Questions
1.1 Can we overload a method by changing only the return type?
No.
int add(int a, int b)
double add(int a, int b) // Compilation Error
The parameter list must be different.
1.2 Can static methods be overridden?
No.
Static methods belong to the class rather than objects.
class Parent {
static void show() {}
}
class Child extends Parent {
static void show() {}
}
This is called Method Hiding, not Overriding.
1.3 Can final methods be overridden?
No.
class Parent {
final void display() {
}
}
class Child extends Parent {
// Compilation Error
void display() {
}
}
1.4 Can private methods be overridden?
No.
Private methods are not inherited by child classes.
1.5 Can constructors be overloaded?
Yes.
class Employee {
Employee() {
}
Employee(int id) {
}
Employee(int id, String name) {
}
}
Constructors support overloading but not overriding.
Real-World Example
Consider a payment application.
Method Overloading
class PaymentService {
void pay(double amount) {
}
void pay(double amount, String couponCode) {
}
void pay(double amount, String couponCode, boolean useWallet) {
}
}
Same operation with different inputs.
Method Overriding
class Payment {
void processPayment() {
System.out.println("Generic Payment");
}
}
class CreditCardPayment extends Payment {
@Override
void processPayment() {
System.out.println("Credit Card Payment");
}
}
class UpiPayment extends Payment {
@Override
void processPayment() {
System.out.println("UPI Payment");
}
}
Same method with different implementations.
Key Takeaways
Method Overloading
- Same method name
- Different parameters
- Same class
- Compile-time polymorphism
- No inheritance required
Method Overriding
- Same method signature
- Child class redefines parent method
- Requires inheritance
- Runtime polymorphism
- Supports dynamic behavior
Easy Way to Remember
Overloading = Same Method + Different Inputs
Overriding = Same Method + Different Behavior
Example:
Overloading → One person speaking multiple languages.
Overriding → Son follows his own way instead of his father's way.
Both concepts are fundamental to writing flexible, reusable, and object-oriented Java applications.
2) What is a ClassLoader in Java?
Introduction
A ClassLoader in Java is a part of the Java Runtime Environment (JRE) responsible for loading Java classes into memory dynamically at runtime.
When a Java program starts, classes are not loaded into memory all at once. Instead, the JVM loads classes only when they are needed. This process is handled by the ClassLoader subsystem.
In simple terms:
A ClassLoader is a JVM component that loads
.classfiles into memory and makes them available for execution.
Why Do We Need a ClassLoader?
Consider the following code:
public class Main {
public static void main(String[] args) {
Employee emp = new Employee();
}
}
Before the JVM can create the Employee object:
- It must locate the
Employee.classfile. - Load the bytecode into memory.
- Verify the bytecode.
- Link the class.
- Initialize the class.
The ClassLoader performs these tasks automatically.
Class Loading Process
The JVM loads classes in three major phases:
1. Loading
The JVM reads the .class file and creates a corresponding Class object.
Class<?> cls = Employee.class;
At this stage, the bytecode is loaded into memory.
2. Linking
Linking consists of three steps:
Verification
Checks whether the bytecode is valid and secure.
Preparation
Allocates memory for static variables.
static int count;
Memory is allocated and initialized with default values.
Resolution
Converts symbolic references into direct memory references.
3. Initialization
Static variables and static blocks are executed.
class Employee {
static {
System.out.println("Class Initialized");
}
}
Output:
Class Initialized
Types of ClassLoaders in Java
Java uses a hierarchy of ClassLoaders.
Bootstrap ClassLoader
↑
Platform ClassLoader
↑
Application ClassLoader
1. Bootstrap ClassLoader
Responsibility
Loads core Java classes.
Examples:
java.lang.String
java.lang.Object
java.util.ArrayList
java.util.HashMap
Location
Loads classes from:
<JAVA_HOME>/lib
Example
System.out.println(String.class.getClassLoader());
Output:
null
Why null?
Bootstrap ClassLoader is implemented in native code and is not represented as a Java object.
2. Platform ClassLoader
Introduced in Java 9.
Responsibility
Loads Java platform modules and extension libraries.
Example
System.out.println(
java.sql.Driver.class.getClassLoader()
);
Typical Output:
jdk.internal.loader.ClassLoaders$PlatformClassLoader
3. Application ClassLoader
Also known as:
System ClassLoader
Responsibility
Loads classes available in:
Classpath
Examples:
- User-created classes
- Third-party libraries
- Maven dependencies
- Spring Boot classes
Example
System.out.println(
Employee.class.getClassLoader()
);
Output:
jdk.internal.loader.ClassLoaders$AppClassLoader
Parent Delegation Model
Java ClassLoaders follow the Parent Delegation Principle.
Before loading a class, a ClassLoader asks its parent to load it first.
Application ClassLoader
|
V
Platform ClassLoader
|
V
Bootstrap ClassLoader
Example
When loading:
String str = "Hello";
The Application ClassLoader asks:
- Platform ClassLoader
- Bootstrap ClassLoader
Bootstrap ClassLoader already knows about String.class, so it loads the class.
This prevents duplicate loading and improves security.
How to Check Which ClassLoader Loaded a Class?
public class Test {
public static void main(String[] args) {
System.out.println(
String.class.getClassLoader()
);
System.out.println(
Test.class.getClassLoader()
);
}
}
Output:
null
jdk.internal.loader.ClassLoaders$AppClassLoader
Custom ClassLoader
Java allows developers to create their own ClassLoaders.
Example use cases:
- Application servers
- Plugin architectures
- Hot deployment
- Dynamic module loading
- Security frameworks
Example:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
// Custom loading logic
return super.findClass(name);
}
}
Real-World Example
Spring Boot
When a Spring Boot application starts:
- JVM starts.
- Application ClassLoader loads Spring classes.
- Spring scans components.
- Classes are loaded dynamically.
- Beans are created.
Without ClassLoaders, dynamic frameworks like Spring would not work efficiently.
Interview Questions
2.1 What is a ClassLoader?
A ClassLoader is a JVM component responsible for loading Java classes into memory dynamically during runtime.
2.2 How many built-in ClassLoaders are there?
Three:
- Bootstrap ClassLoader
- Platform ClassLoader
- Application ClassLoader
2.3 What is Parent Delegation?
A mechanism where a ClassLoader delegates class-loading requests to its parent before attempting to load the class itself.
2.4 Why does String.class.getClassLoader() return null?
Because String is loaded by the Bootstrap ClassLoader, which is implemented in native code and not represented as a Java object.
2.5 Can we create a custom ClassLoader?
Yes.
By extending the ClassLoader class and overriding methods such as:
findClass()
loadClass()
ClassLoader vs JVM Memory Areas
| Component | Responsibility |
|---|---|
| ClassLoader | Loads classes into memory |
| Heap | Stores objects |
| Stack | Stores method calls and local variables |
| Method Area | Stores class metadata |
| Garbage Collector | Removes unused objects |
Key Takeaways
-
ClassLoader loads Java classes dynamically at runtime.
-
JVM uses lazy loading to improve performance.
-
Java provides three built-in ClassLoaders:
- Bootstrap
- Platform
- Application
-
ClassLoaders follow the Parent Delegation Model.
-
Custom ClassLoaders enable advanced features such as plugins and hot deployment.
-
Every Java application relies on ClassLoaders behind the scenes.
Easy Way to Remember
ClassLoader = Librarian of the JVM
Just as a librarian finds and brings the correct book
when requested, the ClassLoader finds and loads the
required class when the JVM needs it.
3) What is inheritance in Java?
Introduction
Inheritance is one of the four fundamental principles of Object-Oriented Programming (OOP).
Inheritance allows one class to acquire the properties and behaviors (fields and methods) of another class.
In simple terms:
Inheritance is a mechanism through which a child class can reuse the code of an existing parent class.
This promotes:
- Code Reusability
- Maintainability
- Extensibility
- Polymorphism
Real-Life Example
Consider a family relationship:
Parent
│
▼
Child
A child inherits characteristics from its parent.
Similarly in Java:
Animal
│
▼
Dog
The Dog class inherits properties and methods from the Animal class.
Syntax of Inheritance
Java uses the extends keyword.
class Parent {
// properties and methods
}
class Child extends Parent {
// additional properties and methods
}
Basic Example
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog is barking");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
dog.bark();
}
}
Output
Animal is eating
Dog is barking
How Inheritance Works
Diagram
+----------------+
| Animal |
+----------------+
| + eat() |
+----------------+
▲
│ extends
│
+----------------+
| Dog |
+----------------+
| + bark() |
+----------------+
The Dog class can use:
- Its own methods
- Methods inherited from
Animal
Why Use Inheritance?
Without inheritance:
class Dog {
void eat() {
System.out.println("Animal is eating");
}
void bark() {
System.out.println("Dog barking");
}
}
class Cat {
void eat() {
System.out.println("Animal is eating");
}
void meow() {
System.out.println("Cat meowing");
}
}
Problem:
- Duplicate code
- Difficult maintenance
With inheritance:
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
}
class Cat extends Animal {
}
Benefit:
- Reusable code
- Cleaner design
Types of Inheritance in Java
Java supports inheritance through classes and interfaces.
1. Single Inheritance
One child inherits from one parent.
Diagram
Animal
│
▼
Dog
Example
class Animal {
void eat() {
}
}
class Dog extends Animal {
void bark() {
}
}
2. Multilevel Inheritance
A class inherits from another child class.
Diagram
Animal
│
▼
Dog
│
▼
Puppy
Example
class Animal {
void eat() {
}
}
class Dog extends Animal {
void bark() {
}
}
class Puppy extends Dog {
void play() {
}
}
3. Hierarchical Inheritance
Multiple child classes inherit from the same parent.
Diagram
Animal
/ | \
/ | \
▼ ▼ ▼
Dog Cat Lion
Example
class Animal {
void eat() {
}
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Lion extends Animal {
}
4. Multiple Inheritance (Through Interfaces)
Java does not support multiple inheritance with classes.
❌ Not Allowed
class A {
}
class B {
}
class C extends A, B {
}
Reason:
The Diamond Problem.
Multiple Inheritance Using Interfaces
✅ Allowed
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
public void fly() {
System.out.println("Flying");
}
public void swim() {
System.out.println("Swimming");
}
}
The Diamond Problem
Imagine:
Animal
/ \
/ \
▼ ▼
Bird Fish
\ /
\ /
Duck
If both Bird and Fish have the same method:
void move()
Which implementation should Duck inherit?
To avoid this ambiguity, Java does not allow multiple inheritance with classes.
The super Keyword
The super keyword is used to access parent class members.
Access Parent Method
class Animal {
void sound() {
System.out.println("Animal Sound");
}
}
class Dog extends Animal {
void sound() {
super.sound();
System.out.println("Dog Bark");
}
}
Output:
Animal Sound
Dog Bark
Call Parent Constructor
class Animal {
Animal() {
System.out.println("Animal Constructor");
}
}
class Dog extends Animal {
Dog() {
super();
System.out.println("Dog Constructor");
}
}
Output:
Animal Constructor
Dog Constructor
Inheritance and Method Overriding
Inheritance enables Runtime Polymorphism through Method Overriding.
class Animal {
void sound() {
System.out.println("Animal Sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog Bark");
}
}
Real-World Example
Banking Application
Account
▲
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
SavingsAccount CurrentAccount LoanAccount
Parent Class
class Account {
void deposit() {
System.out.println("Deposit Money");
}
void withdraw() {
System.out.println("Withdraw Money");
}
}
Child Class
class SavingsAccount extends Account {
void calculateInterest() {
System.out.println("Calculating Interest");
}
}
Benefit:
- Common functionality remains in one place.
- Child classes add specialized behavior.
Advantages of Inheritance
Code Reusability
Write once, use many times.
Easy Maintenance
Changes in parent class are inherited automatically.
Better Code Organization
Common functionality stays in the parent class.
Supports Polymorphism
Allows dynamic method dispatch.
Extensibility
New child classes can be added easily.
Disadvantages of Inheritance
Tight Coupling
Child classes depend heavily on parent classes.
Reduced Flexibility
Changes in parent class may affect all child classes.
Deep Hierarchies Become Complex
Too many inheritance levels make maintenance difficult.
Interview Questions
3.1 What is Inheritance?
Inheritance is a mechanism that allows one class to acquire the properties and methods of another class.
3.2 Which keyword is used for inheritance?
extends
3.3 Does Java support multiple inheritance?
No, not through classes.
Java supports multiple inheritance using interfaces.
3.4 What is the advantage of inheritance?
- Code reuse
- Better maintainability
- Supports polymorphism
3.5 What is the difference between Inheritance and Composition?
Inheritance
IS-A Relationship
Example:
Dog IS-A Animal
Composition
HAS-A Relationship
Example:
Car HAS-A Engine
Inheritance vs Composition
| Feature | Inheritance | Composition |
|---|---|---|
| Relationship | IS-A | HAS-A |
| Coupling | Tight | Loose |
| Reusability | High | High |
| Flexibility | Less | More |
| Preferred in Modern Design | No | Yes |
Key Takeaways
-
Inheritance is an OOP concept that enables code reuse.
-
Java uses the
extendskeyword for inheritance. -
A child class inherits fields and methods from a parent class.
-
Java supports:
- Single Inheritance
- Multilevel Inheritance
- Hierarchical Inheritance
-
Multiple inheritance is supported through interfaces.
-
Inheritance is the foundation for Method Overriding and Runtime Polymorphism.
-
Modern application design often prefers Composition over deep inheritance hierarchies.
Easy Way to Remember
Inheritance = "IS-A" Relationship
Dog IS-A Animal
Car IS-A Vehicle
Manager IS-A Employee
Composition = "HAS-A" Relationship
Car HAS-A Engine
House HAS-A Room
Computer HAS-A CPU
A simple rule:
If one object "is a type of" another object, use Inheritance.
If one object "contains" another object, use Composition.
4) Why can’t we override a static method?
Static methods belong to the class, not to an object (instance). Method overriding is a runtime polymorphism feature that depends on the actual object being created.
Since static methods are resolved at compile time using the reference type, Java does not allow true overriding of static methods.
Example
class Parent {
static void show() {
System.out.println("Parent Static Method");
}
}
class Child extends Parent {
static void show() {
System.out.println("Child Static Method");
}
}
public class Test {
public static void main(String[] args) {
Parent p = new Child();
p.show();
}
}
Output
Parent Static Method
Why?
When the compiler sees:
p.show();
it checks the type of p, which is Parent, and binds the method call at compile time.
Equivalent to:
Parent.show();
Therefore, the Child version is not called.
What Happens Instead?
The static method in the child class hides the parent's static method rather than overriding it. This is called Method Hiding.
Key Interview Point
| Static Method | Instance Method |
|---|---|
| Belongs to class | Belongs to object |
| Resolved at compile time | Resolved at runtime |
| Cannot be overridden | Can be overridden |
| Supports method hiding | Supports runtime polymorphism |
One-Line Interview Answer
Static methods cannot be overridden because they belong to the class and are resolved at compile time, whereas overriding requires runtime polymorphism based on the actual object instance. Static methods are hidden, not overridden.
5) What is Dynamic Method Dispatch?
Dynamic Method Dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than at compile time.
It enables runtime polymorphism, where a superclass reference variable can refer to a subclass object, and the method that gets executed is determined by the actual object type.
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 Test {
public static void main(String[] args) {
Animal animal = new Dog(); // Upcasting
animal.sound();
}
}
Output
Dog barks
How It Works
In the statement:
Animal animal = new Dog();
- The reference type is
Animal. - The actual object type is
Dog.
When:
animal.sound();
is executed, the JVM checks the actual object type (Dog) at runtime and invokes:
Dog.sound();
This runtime decision-making process is called Dynamic Method Dispatch.
Key Points
- Supports Runtime Polymorphism.
- Applicable only to overridden instance methods.
- Method call is resolved at runtime.
- Based on the actual object type, not the reference type.
- Static, private, and final methods do not participate in Dynamic Method Dispatch.
Real-World Example
List<String> list = new ArrayList<>();
list.add("Java");
Although the reference type is List, the actual object is ArrayList. The JVM invokes the implementation provided by ArrayList at runtime.
Dynamic Method Dispatch vs Static Binding
| Dynamic Method Dispatch | Static Binding |
|---|---|
| Runtime decision | Compile-time decision |
| Uses overridden methods | Uses static, private, final methods |
| Supports polymorphism | Does not support polymorphism |
| Based on object type | Based on reference type |
One-Line Interview Answer
Dynamic Method Dispatch is the JVM mechanism that resolves calls to overridden methods at runtime based on the actual object type, enabling runtime polymorphism in Java.
6) What is Java Classpath?
Classpath is a parameter used by the Java Virtual Machine (JVM) and Java compiler (javac) to locate and load compiled .class files, JAR files, and other resources required by a Java application.
In simple terms, the classpath tells Java where to look for classes and libraries.
Why is Classpath Needed?
When a Java program uses a class, the JVM must know where that class is located.
For example:
import com.company.service.UserService;
The JVM searches the configured classpath to find:
com/company/service/UserService.class
If the class is not found, Java throws:
java.lang.ClassNotFoundException
or
java.lang.NoClassDefFoundError
Setting Classpath
Using Command Line
Windows
java -cp .;lib/* Main
Linux/Mac
java -cp .:lib/* Main
Where:
.= Current directorylib/*= All JAR files inside thelibfolder
Example
Project Structure:
project/
│
├── Main.class
└── lib/
└── mysql-connector.jar
Run the application:
java -cp .:lib/mysql-connector.jar Main
The JVM can now locate both:
Main.classmysql-connector.jar
Ways to Configure Classpath
1. Command Line
java -cp classes;lib/*
2. Environment Variable
Windows
set CLASSPATH=.;C:\libs\*
Linux/Mac
export CLASSPATH=.:/libs/*
3. Manifest File in JAR
Class-Path: lib/mysql.jar lib/log4j.jar
4. Build Tools
Modern tools manage classpaths automatically:
- Maven
- Gradle
- Spring Boot
Example Maven dependency:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
Classpath vs PATH
| Classpath | PATH |
|---|---|
| Used by JVM to find classes and JARs | Used by OS to find executables |
| Java-specific | Operating System specific |
Contains .class and .jar locations |
Contains executable locations |
Example: lib/mysql.jar |
Example: C:\Java\bin |
Common Errors
ClassNotFoundException
Occurs when JVM cannot find a class during runtime.
java.lang.ClassNotFoundException
NoClassDefFoundError
Occurs when a class was available during compilation but is missing at runtime.
java.lang.NoClassDefFoundError
Java 9+ Module System
Starting with Java 9, the Module Path was introduced as an alternative to the traditional Classpath for modular applications.
java --module-path mods
However, Classpath is still widely used in most Java applications.
Key Interview Points
- Classpath specifies where Java should search for classes and JAR files.
- Used by both
javacand JVM. - Includes directories, JAR files, and resource files.
- Incorrect classpath configuration leads to
ClassNotFoundExceptionandNoClassDefFoundError. - Build tools like Maven and Gradle manage classpaths automatically.
One-Line Interview Answer
Classpath is a JVM and compiler setting that specifies the locations of .class files, JAR files, and resources required for compiling and running a Java application.
7) What does the `volatile` keyword do?
The volatile keyword is used to indicate that a variable's value may be modified by multiple threads.
It ensures that:
- Visibility – Changes made by one thread are immediately visible to all other threads.
- Ordering – Prevents certain instruction reordering by the JVM and CPU.
However, volatile does not provide atomicity.
Why Do We Need volatile?
In a multithreaded environment, each thread may keep a local copy of variables in its CPU cache.
Without volatile:
Thread A updates a variable
↓
Main Memory
↓
Thread B may still read an old cached value
With volatile:
Thread A updates a variable
↓
Main Memory
↓
Thread B immediately sees the latest value
Example
Without volatile
class SharedData {
boolean running = true;
}
public class VolatileDemo {
public static void main(String[] args) {
SharedData data = new SharedData();
Thread t1 = new Thread(() -> {
while (data.running) {
// Busy waiting
}
System.out.println("Stopped");
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
data.running = false;
});
t1.start();
t2.start();
}
}
Problem
Thread t1 may never see the updated value because it can keep reading a cached copy.
Solution
class SharedData {
volatile boolean running = true;
}
Now, when Thread t2 updates running, Thread t1 immediately sees the change.
What volatile Guarantees
1. Visibility Guarantee
volatile boolean flag;
If one thread writes:
flag = true;
all other threads immediately see:
flag == true
2. Happens-Before Relationship
A write to a volatile variable happens-before every subsequent read of that variable.
volatile boolean ready = false;
ready = true; // Write
if (ready) { // Read
// Guaranteed to see latest value
}
3. Prevents Reordering
The JVM and CPU cannot reorder memory operations around a volatile variable in a way that would break visibility guarantees.
What volatile Does NOT Guarantee
No Atomicity
Consider:
volatile int count = 0;
count++;
The operation is actually:
Read count
Increment
Write count
Multiple threads can interfere between these steps.
Therefore:
count++;
is not thread-safe, even if count is volatile.
Incorrect
volatile int count = 0;
public void increment() {
count++;
}
Correct
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();
or
synchronized void increment() {
count++;
}
volatile vs synchronized
| volatile | synchronized |
|---|---|
| Provides visibility | Provides visibility + atomicity |
| Lightweight | Heavier due to locking |
| No thread blocking | Threads may block |
| No mutual exclusion | Provides mutual exclusion |
| Good for status flags | Good for critical sections |
Common Use Cases
1. Shutdown Flag
volatile boolean stop = false;
2. Configuration Refresh
volatile String configValue;
3. Singleton with Double-Checked Locking
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
Memory Visibility Example
class Example {
private volatile boolean flag = false;
public void writer() {
flag = true;
}
public void reader() {
if (flag) {
System.out.println("Updated");
}
}
}
The reader thread is guaranteed to see the latest value of flag.
Key Interview Points
volatileensures visibility of changes across threads.- Prevents instruction reordering around the variable.
- Does not provide atomicity.
- Suitable for flags, status indicators, and configuration values.
- Cannot replace
synchronizedwhen multiple operations must execute atomically.
One-Line Interview Answer
The volatile keyword ensures that changes made to a variable by one thread are immediately visible to all other threads and prevents instruction reordering, but it does not provide atomicity or thread synchronization.
8) When does `finally` not execute?
The finally block is designed to execute regardless of whether an exception occurs or not. It is commonly used for resource cleanup such as closing files, database connections, and streams.
However, there are a few exceptional situations where the finally block may not execute.
Normal Behavior
try {
System.out.println("Inside try");
} finally {
System.out.println("Inside finally");
}
Output
Inside try
Inside finally
The finally block executes even if:
- An exception occurs
- A
returnstatement is executed - A
breakorcontinuestatement is used
Case 1: System.exit() is Called
If the JVM is explicitly terminated using System.exit(), the finally block will not execute.
public class Test {
public static void main(String[] args) {
try {
System.out.println("Inside try");
System.exit(0);
} finally {
System.out.println("Inside finally");
}
}
}
Output
Inside try
The JVM shuts down immediately.
Case 2: JVM Crash or Forced Termination
If the JVM crashes due to:
- Native code failure
- JVM internal error
- Operating system crash
- Forceful process termination (
kill -9on Linux)
the finally block may never execute.
JVM Crash
↓
Program Ends Immediately
↓
finally Not Executed
Case 3: Power Failure / System Shutdown
If the machine loses power or the operating system shuts down unexpectedly before reaching the finally block, it cannot execute.
Power Failure
↓
Application Stops
↓
finally Skipped
Case 4: Infinite Loop Before Reaching finally
If control never exits the try block, the finally block is never reached.
try {
while (true) {
// Infinite loop
}
} finally {
System.out.println("finally");
}
Output
(No Output)
The loop never ends, so execution never reaches finally.
finally Executes Even with return
Many interviewers ask this question.
public int test() {
try {
return 10;
} finally {
System.out.println("finally");
}
}
Output
finally
The finally block executes before the method actually returns.
finally Executes Even with Exception
try {
int x = 10 / 0;
} finally {
System.out.println("finally");
}
Output
finally
Exception in thread "main" java.lang.ArithmeticException
The exception is propagated only after the finally block completes.
Common Interview Trick
try {
return 10;
} finally {
return 20;
}
Output
20
The finally return overrides the try return.
Best Practice: Avoid returning from a finally block.
Summary Table
| Scenario | Does finally Execute? |
|---|---|
| Normal execution | ✅ Yes |
| Exception occurs | ✅ Yes |
return statement |
✅ Yes |
break / continue |
✅ Yes |
System.exit() |
❌ No |
| JVM crash | ❌ No |
| Forceful process kill | ❌ No |
| Power failure | ❌ No |
Infinite loop in try |
❌ No (never reached) |
Key Interview Points
finallyalmost always executes.- Used for resource cleanup.
- Executes even when exceptions occur or methods return.
- Does not execute if the JVM terminates abruptly.
System.exit()is the most commonly cited interview answer.
One-Line Interview Answer
The finally block executes in almost all situations, except when the JVM terminates abruptly (e.g., System.exit(), JVM crash, forceful process termination, power failure) or when control never leaves the try block, such as an infinite loop.
9) StringBuffer vs StringBuilder
# StringBuffer vs StringBuilder in JavaBoth StringBuffer and StringBuilder are mutable classes used to create and modify strings without creating new objects repeatedly.
The main difference is thread safety.
Why Do We Need Them?
String objects are immutable.
String str = "Java";
str += " Programming";
A new String object is created every time the string changes, which impacts performance.
To avoid this, Java provides:
- StringBuffer
- StringBuilder
Both allow modifying the same object.
StringBuffer Example
StringBuffer sb = new StringBuffer("Java");
sb.append(" Programming");
System.out.println(sb);
Output
Java Programming
StringBuilder Example
StringBuilder sb = new StringBuilder("Java");
sb.append(" Programming");
System.out.println(sb);
Output
Java Programming
Key Difference: Synchronization
StringBuffer
All major methods are synchronized.
public synchronized StringBuffer append(String str)
This makes it:
- Thread-safe
- Slower due to locking overhead
StringBuilder
Methods are not synchronized.
public StringBuilder append(String str)
This makes it:
- Not thread-safe
- Faster
Comparison Table
| Feature | StringBuffer | StringBuilder |
|---|---|---|
| Introduced In | Java 1.0 | Java 5 |
| Thread Safe | ✅ Yes | ❌ No |
| Synchronized | ✅ Yes | ❌ No |
| Performance | Slower | Faster |
| Suitable For | Multi-threaded applications | Single-threaded applications |
| Mutable | ✅ Yes | ✅ Yes |
| Inheritance | Extends AbstractStringBuilder | Extends AbstractStringBuilder |
Performance Example
StringBuffer buffer = new StringBuffer();
for(int i = 0; i < 100000; i++) {
buffer.append("A");
}
StringBuilder builder = new StringBuilder();
for(int i = 0; i < 100000; i++) {
builder.append("A");
}
Typically:
StringBuilder > StringBuffer
because synchronization adds extra overhead.
Internal Hierarchy
Object
│
└── AbstractStringBuilder
│
┌─────┴─────┐
│ │
StringBuffer StringBuilder
Both share most of their implementation through AbstractStringBuilder.
Common Methods
append()
insert()
delete()
replace()
reverse()
length()
capacity()
Example:
StringBuilder sb = new StringBuilder("Java");
sb.append(" 8");
sb.insert(4, " SE");
sb.reverse();
When to Use Which?
Use StringBuilder
When:
- Single-threaded application
- Local method variables
- Better performance is required
Example:
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM EMPLOYEE");
Use StringBuffer
When:
- Multiple threads access the same object
- Thread safety is required
Example:
StringBuffer sharedBuffer = new StringBuffer();
Interview Trick Question
Is StringBuilder Faster Than StringBuffer?
Yes.
Because StringBuilder does not perform synchronization, it generally provides better performance.
String vs StringBuffer vs StringBuilder
| Feature | String | StringBuffer | StringBuilder |
|---|---|---|---|
| Mutable | ❌ No | ✅ Yes | ✅ Yes |
| Thread Safe | ✅ Yes (Immutable) | ✅ Yes | ❌ No |
| Performance | Slow for modifications | Medium | Fastest |
| Memory Usage | Higher for repeated changes | Lower | Lower |
| Use Case | Fixed text | Multi-threaded modifications | Single-threaded modifications |
Key Interview Points
- Both StringBuffer and StringBuilder are mutable.
- StringBuffer is synchronized and thread-safe.
- StringBuilder is not synchronized and faster.
- StringBuilder was introduced in Java 5 to improve performance.
- Prefer StringBuilder unless thread safety is specifically required.
One-Line Interview Answer
StringBuffer is a thread-safe, synchronized mutable string class, whereas StringBuilder is a non-synchronized mutable string class that provides better performance and is preferred in single-threaded environments.
10) Vector vs ArrayList
# Vector vs ArrayList in JavaBoth Vector and ArrayList are dynamic array implementations that store elements in insertion order and allow duplicate values.
The primary difference is thread safety and synchronization.
Similarities
Both:
- Implement the
Listinterface - Maintain insertion order
- Allow duplicate elements
- Allow random access using indexes
- Store heterogeneous objects (if generics are not used)
- Automatically grow when capacity is exceeded
Example:
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
List<String> vector = new Vector<>();
vector.add("Java");
vector.add("Spring");
ArrayList Example
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
System.out.println(list);
Output
[Java, Spring]
Vector Example
Vector<String> vector = new Vector<>();
vector.add("Java");
vector.add("Spring");
System.out.println(vector);
Output
[Java, Spring]
Key Difference: Synchronization
ArrayList
Not synchronized.
ArrayList<String> list = new ArrayList<>();
- Faster
- Not thread-safe
- Preferred in modern applications
Vector
Synchronized.
Vector<String> vector = new Vector<>();
- Thread-safe
- Slower due to locking overhead
- Legacy collection class
Comparison Table
| Feature | ArrayList | Vector |
|---|---|---|
| Introduced In | Java 1.2 | Java 1.0 |
| Thread Safe | ❌ No | ✅ Yes |
| Synchronized | ❌ No | ✅ Yes |
| Performance | Faster | Slower |
| Part of Collection Framework | ✅ Yes | Originally Legacy, later adapted |
| Capacity Growth | 50% increase | Doubles by default |
| Recommended Today | ✅ Yes | ❌ Rarely |
| Iterator Support | Iterator, ListIterator | Iterator, ListIterator, Enumeration |
Capacity Growth
ArrayList
When full, capacity increases approximately by:
New Capacity = Old Capacity + (Old Capacity / 2)
Example:
10 → 15 → 22 → 33 ...
Vector
When full, capacity typically doubles.
10 → 20 → 40 → 80 ...
Or a custom increment can be specified:
Vector<Integer> vector = new Vector<>(10, 5);
Growth:
10 → 15 → 20 → 25 ...
Iteration Mechanisms
ArrayList
Iterator<String> itr = list.iterator();
Vector
Supports both:
Iterator<String> itr = vector.iterator();
and legacy:
Enumeration<String> e = vector.elements();
Thread Safety Example
ArrayList (Not Safe)
ArrayList<Integer> list = new ArrayList<>();
Multiple threads modifying the list can cause:
ConcurrentModificationException
or inconsistent data.
Vector (Safe)
Vector<Integer> vector = new Vector<>();
Methods are synchronized:
public synchronized boolean add(E e)
Only one thread can modify the Vector at a time.
Modern Alternative
Instead of using Vector:
List<String> list =
Collections.synchronizedList(new ArrayList<>());
Or:
CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();
These are preferred in modern Java applications.
Internal Hierarchy
Iterable
│
Collection
│
List
│
┌───────┴────────┐
│ │
ArrayList Vector
Performance
For single-threaded applications:
ArrayList > Vector
because Vector synchronizes every operation.
Interview Trick Question
Is Vector Deprecated?
No.
Vector is not deprecated, but it is considered a legacy collection and is rarely used in modern applications.
ArrayList vs Vector vs LinkedList
| Feature | ArrayList | Vector | LinkedList |
|---|---|---|---|
| Thread Safe | ❌ No | ✅ Yes | ❌ No |
| Random Access | Fast | Fast | Slow |
| Insert/Delete Middle | Slow | Slow | Fast |
| Performance | Best | Slower | Moderate |
| Modern Usage | Most Common | Rare | Specific Use Cases |
Key Interview Points
- Both are dynamic arrays and maintain insertion order.
- ArrayList is not synchronized and offers better performance.
- Vector is synchronized and thread-safe.
- Vector is a legacy class introduced before the Collections Framework.
- ArrayList is the preferred choice in modern Java applications.
- Vector supports both Iterator and Enumeration.
One-Line Interview Answer
ArrayList is a non-synchronized, high-performance dynamic array implementation, whereas Vector is a synchronized, thread-safe legacy collection class with additional synchronization overhead.
11) Why is String immutable?
# Why is String Immutable in Java?A String in Java is immutable, which means once a String object is created, its value cannot be changed.
Any operation that appears to modify a String actually creates a new String object.
Example
String str = "Java";
str.concat(" Programming");
System.out.println(str);
Output
Java
The original String remains unchanged.
To store the modified value:
str = str.concat(" Programming");
Output:
Java Programming
How is String Made Immutable?
The String class is declared as:
public final class String
Key reasons:
finalclass → Cannot be subclassed.- Internal value storage is private.
- No setter methods are provided.
- Any modification creates a new object.
Simplified implementation:
public final class String {
private final char[] value;
}
Why Did Java Make String Immutable?
This is one of the most frequently asked Java interview questions.
There are several important reasons.
1. Security
Strings are widely used for:
- Database URLs
- File paths
- Network connections
- Usernames and passwords
- Class loading
Example:
String path = "/secure/config.txt";
If Strings were mutable:
path = "/hack/config.txt";
A malicious change could compromise application security.
Immutability prevents such modifications.
2. String Constant Pool Optimization
Java maintains a special memory area called the String Pool.
String s1 = "Java";
String s2 = "Java";
Both references point to the same object:
s1 ----\
--> "Java"
s2 ----/
This memory optimization is only possible because Strings are immutable.
If one reference could modify the value:
s1 = "Python";
it would affect all references sharing the same object.
3. Thread Safety
Immutable objects are naturally thread-safe.
String message = "Hello";
Multiple threads can safely access the same String object without synchronization.
Thread-1 → Reads "Hello"
Thread-2 → Reads "Hello"
Thread-3 → Reads "Hello"
No thread can modify the value.
4. HashCode Caching
Strings are commonly used as keys in:
HashMap
HashSet
Hashtable
Example:
Map<String, String> map = new HashMap<>();
Since Strings are immutable, their hash code never changes.
Java can cache the hash code:
private int hash;
This improves performance.
If Strings were mutable:
String key = "user";
and later changed to:
"user123"
the object could no longer be found in the HashMap.
5. Class Loading Safety
Class names are represented as Strings.
Class.forName("com.example.UserService");
If Strings were mutable, attackers could alter class names and load unintended classes.
Immutability prevents this risk.
Memory Example
String s1 = "Java";
String s2 = s1.concat(" 8");
Memory:
s1 --> "Java"
s2 --> "Java 8"
The original object remains unchanged.
Mutable Alternative
If frequent modifications are required:
StringBuilder sb = new StringBuilder("Java");
sb.append(" 8");
Output:
Java 8
StringBuilder and StringBuffer are mutable.
String vs StringBuilder
| Feature | String | StringBuilder |
|---|---|---|
| Mutable | ❌ No | ✅ Yes |
| Thread Safe | ✅ Yes (Immutable) | ❌ No |
| Performance for Modifications | Slower | Faster |
| Memory Optimization | String Pool | No String Pool |
| HashCode Stability | Fixed | Changes |
Interview Trick Question
Does concat() Modify the Existing String?
String s = "Java";
s.concat(" 8");
No.
A new String object is created, but the reference s still points to the original String.
Output:
Java
Key Interview Points
- String is immutable because its value cannot be changed after creation.
- Immutability provides security, thread safety, and memory optimization.
- Enables String Pool sharing.
- Allows hash code caching for better performance.
- Makes Strings safe for use as HashMap keys.
- Any modification creates a new String object.
One-Line Interview Answer
String is immutable in Java to provide security, thread safety, String Pool optimization, hash code caching, and reliable behavior when used as keys in collections such as HashMap.
12) HashMap vs HashSet
# HashMap vs HashSet in JavaBoth HashMap and HashSet are part of the Java Collections Framework and use a hash-based data structure internally for fast storage and retrieval.
The main difference is:
- HashMap stores key-value pairs
- HashSet stores only unique values
HashMap Overview
A HashMap stores data in the form of:
Key → Value
Example:
Map<Integer, String> map = new HashMap<>();
map.put(101, "John");
map.put(102, "David");
map.put(103, "Smith");
Output
101 → John
102 → David
103 → Smith
Characteristics
- Stores key-value pairs
- Keys must be unique
- Values can be duplicated
- Allows one null key
- Allows multiple null values
- Unordered
HashSet Overview
A HashSet stores only unique elements.
Example:
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("Java");
Output
[Java, Spring]
Duplicate values are automatically ignored.
Characteristics
- Stores only values
- No duplicate elements
- Allows one null value
- Unordered
Comparison Table
| Feature | HashMap | HashSet |
|---|---|---|
| Stores | Key-Value Pairs | Unique Values |
| Duplicate Keys | ❌ Not Allowed | N/A |
| Duplicate Values | ✅ Allowed | ❌ Not Allowed |
| Null Handling | One null key, multiple null values | One null value |
| Interface | Map | Set |
| Data Retrieval | By Key | By Value Search |
| Internal Structure | Hash Table | Uses HashMap Internally |
| Ordering | Not Guaranteed | Not Guaranteed |
Internal Working
HashMap
Map<Integer, String> map = new HashMap<>();
map.put(1, "Java");
Internally:
1 → Java
Stored as:
Node<K, V>
containing:
hash
key
value
next
HashSet
Set<String> set = new HashSet<>();
set.add("Java");
Internally, HashSet uses a HashMap:
private transient HashMap<E,Object> map;
When:
set.add("Java");
Internally:
map.put("Java", PRESENT);
Where:
private static final Object PRESENT = new Object();
Internal representation:
Java → PRESENT
Spring → PRESENT
Example: Duplicate Handling
HashMap
Map<Integer, String> map = new HashMap<>();
map.put(1, "Java");
map.put(1, "Spring");
Output
{1=Spring}
The new value replaces the old value because keys must be unique.
HashSet
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Java");
Output
[Java]
Duplicate values are ignored.
Time Complexity
| Operation | HashMap | HashSet |
|---|---|---|
| Insert | O(1) Average | O(1) Average |
| Search | O(1) Average | O(1) Average |
| Delete | O(1) Average | O(1) Average |
| Worst Case | O(n) | O(n) |
Since Java 8, heavy collisions use a Red-Black Tree, improving worst-case performance to:
O(log n)
for affected buckets.
Iteration Example
HashMap
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() +
" : " +
entry.getValue());
}
HashSet
for (String value : set) {
System.out.println(value);
}
When to Use HashMap?
Use HashMap when:
- Data must be stored as key-value pairs
- Fast lookup by key is needed
- Caching is required
Example:
Employee ID → Employee Object
Map<Integer, Employee> employees;
When to Use HashSet?
Use HashSet when:
- Only unique values are required
- Duplicate removal is needed
- Fast membership checking is required
Example:
Set<String> uniqueEmails;
Interview Trick Question
Does HashSet Use HashMap Internally?
Yes.
Internally:
private transient HashMap<E,Object> map;
Each element in a HashSet is stored as a key in an internal HashMap with a dummy value (PRESENT).
HashMap vs HashSet vs Hashtable
| Feature | HashMap | HashSet | Hashtable |
|---|---|---|---|
| Stores | Key-Value | Values Only | Key-Value |
| Thread Safe | ❌ No | ❌ No | ✅ Yes |
| Null Key | ✅ One | N/A | ❌ No |
| Null Value | ✅ Yes | ✅ One Null Element | ❌ No |
| Performance | Fast | Fast | Slower |
Key Interview Points
- HashMap stores key-value pairs; HashSet stores only unique values.
- HashMap allows duplicate values but unique keys.
- HashSet does not allow duplicate elements.
- HashSet is internally backed by a HashMap.
- Both provide O(1) average-time complexity for insert, search, and delete operations.
- Neither guarantees insertion order.
One-Line Interview Answer
HashMap stores data as unique keys and associated values, whereas HashSet stores only unique elements and internally uses a HashMap to maintain uniqueness.
13) Producer–Consumer problem & benefits
# Producer–Consumer Problem & Benefits in JavaThe Producer–Consumer Problem is a classic multithreading problem where:
- Producer threads generate data and place it into a shared buffer (queue).
- Consumer threads take data from the shared buffer and process it.
The challenge is to ensure that producers and consumers work safely without causing data inconsistency, race conditions, or buffer overflow/underflow.
Real-Life Example
Imagine an online food delivery system:
Restaurant (Producer)
↓
Order Queue
↓
Delivery Partner (Consumer)
- Restaurant prepares orders and adds them to the queue.
- Delivery partners pick orders from the queue.
- If the queue is full, the restaurant must wait.
- If the queue is empty, delivery partners must wait.
Problem Statement
Consider a shared buffer with capacity 5.
Buffer Capacity = 5
Scenario 1: Buffer Full
[1][2][3][4][5]
Producer tries to add:
[1][2][3][4][5][6]
❌ Not allowed.
Producer must wait until a consumer removes an item.
Scenario 2: Buffer Empty
[]
Consumer tries to remove an item.
❌ Not allowed.
Consumer must wait until a producer adds data.
Solution Using wait() and notify()
Shared Buffer
class Buffer {
private Queue<Integer> queue = new LinkedList<>();
private final int CAPACITY = 5;
public synchronized void produce(int value)
throws InterruptedException {
while (queue.size() == CAPACITY) {
wait();
}
queue.add(value);
System.out.println("Produced: " + value);
notifyAll();
}
public synchronized int consume()
throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notifyAll();
return value;
}
}
Producer Thread
class Producer implements Runnable {
private Buffer buffer;
Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
int value = 1;
while (true) {
buffer.produce(value++);
Thread.sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Consumer Thread
class Consumer implements Runnable {
private Buffer buffer;
Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
while (true) {
buffer.consume();
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Main Class
public class ProducerConsumerDemo {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Thread(new Producer(buffer)).start();
new Thread(new Consumer(buffer)).start();
}
}
Modern Java Solution
Instead of manually using:
wait()
notify()
notifyAll()
Java provides BlockingQueue.
Example
BlockingQueue<Integer> queue =
new ArrayBlockingQueue<>(5);
Producer:
queue.put(10);
Consumer:
int value = queue.take();
The queue automatically handles synchronization.
Workflow Diagram
Producer
│
▼
┌─────────────┐
│ Shared Queue│
└─────────────┘
▲
│
Consumer
Rules
Queue Full → Producer Waits
Queue Empty → Consumer Waits
Benefits of Producer–Consumer Pattern
1. Decouples Components
Producer and consumer work independently.
Producer → Queue → Consumer
Neither needs to know the internal implementation of the other.
2. Improves Throughput
Multiple producers and consumers can work concurrently.
Producer-1
Producer-2
Producer-3
↓
Shared Queue
↓
Consumer-1
Consumer-2
This improves overall system performance.
3. Better Resource Utilization
Threads remain busy processing tasks instead of waiting unnecessarily.
4. Handles Traffic Spikes
During sudden load increases:
Requests
↓
Queue
↓
Processing
The queue acts as a buffer and prevents system overload.
5. Scalability
Additional producers or consumers can be added easily.
1 Producer → 5 Consumers
5 Producers → 10 Consumers
without changing core business logic.
6. Prevents Data Loss
A bounded queue ensures:
- No buffer overflow
- No buffer underflow
- Controlled data processing
Real-World Applications
Message Queues
Apache Kafka
RabbitMQ
ActiveMQ
Amazon SQS
Web Applications
HTTP Request
↓
Queue
↓
Background Processing
Logging Systems
Application
↓
Logging Queue
↓
Log Writer Thread
Order Processing Systems
Customer Orders
↓
Queue
↓
Payment / Shipping Services
Interview Questions
Why use while instead of if with wait()?
while(queue.isEmpty()) {
wait();
}
Because after waking up, the condition must be checked again to handle:
- Spurious wakeups
- Multiple waiting threads
Which Java class is preferred today?
BlockingQueue
because it provides built-in synchronization and is simpler than using wait() and notify() manually.
Key Interview Points
- Producer creates data; Consumer processes data.
- Shared buffer/queue is used for communication.
- Synchronization prevents race conditions.
wait()is used when buffer is full or empty.notify()/notifyAll()wakes waiting threads.BlockingQueueis the preferred modern solution.- Widely used in messaging systems, order processing, logging, and task scheduling.
One-Line Interview Answer
The Producer–Consumer Problem is a synchronization pattern where producer threads add data to a shared buffer and consumer threads remove data from it, ensuring safe, efficient, and scalable communication between concurrent threads.
14) How to create an immutable class?
# How to Create an Immutable Class in Java?An immutable class is a class whose objects cannot be modified after they are created.
Once an object is initialized, its state remains unchanged throughout its lifetime.
Examples of immutable classes in Java:
String
Integer
Long
BigDecimal
LocalDate
Rules to Create an Immutable Class
To make a class immutable:
1. Declare the Class as final
This prevents inheritance and method overriding.
public final class Employee {
}
2. Make All Fields private and final
private final int id;
private final String name;
This ensures:
- Fields cannot be accessed directly.
- Fields can be assigned only once.
3. Initialize Fields Through Constructor
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
4. Do Not Provide Setter Methods
❌ Avoid:
public void setName(String name) {
this.name = name;
}
Without setters, state cannot be modified.
5. Return Defensive Copies for Mutable Objects
If a field contains a mutable object like:
Date
List
Map
Set
return a copy instead of the original object.
Simple Immutable Class Example
public final class Employee {
private final int id;
private final String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Usage
Employee emp = new Employee(101, "John");
System.out.println(emp.getName());
Output:
John
The object's state cannot be changed after creation.
Immutable Class with Mutable Field
Consider:
private final Date joiningDate;
Date is mutable.
Wrong Implementation
public Date getJoiningDate() {
return joiningDate;
}
Problem:
Date date = emp.getJoiningDate();
date.setTime(0);
The internal state of the object changes.
Correct Implementation (Defensive Copy)
import java.util.Date;
public final class Employee {
private final int id;
private final Date joiningDate;
public Employee(int id, Date joiningDate) {
this.id = id;
this.joiningDate =
new Date(joiningDate.getTime());
}
public int getId() {
return id;
}
public Date getJoiningDate() {
return new Date(joiningDate.getTime());
}
}
Why Defensive Copy?
Without defensive copying:
Outside Code
↓
Modifies Returned Object
↓
Internal State Changes
With defensive copying:
Outside Code
↓
Receives Copy
↓
Original Object Remains Safe
Immutable List Example
Wrong
private final List<String> skills;
public List<String> getSkills() {
return skills;
}
Client code can modify:
emp.getSkills().add("Spring");
Correct
import java.util.Collections;
public List<String> getSkills() {
return Collections.unmodifiableList(skills);
}
or
return new ArrayList<>(skills);
Benefits of Immutable Classes
1. Thread Safety
Immutable objects are naturally thread-safe.
Employee emp = new Employee(101, "John");
Multiple threads can access the object safely.
2. Simplicity
No synchronization required.
No Locks
No Race Conditions
3. Security
Object state cannot be modified accidentally or maliciously.
4. Safe HashMap Keys
Immutable objects make excellent keys.
Map<Employee, String> map = new HashMap<>();
Hash codes remain stable.
5. Easy Caching
Since state never changes:
Cache
Pool
Singleton
implementations become simpler.
Immutable Object Flow
Create Object
↓
Initialize State
↓
No Further Changes Allowed
↓
Read Only Access
Java Record (Java 16+)
Modern Java provides records that are inherently immutable.
public record Employee(
int id,
String name) {
}
Usage:
Employee emp =
new Employee(101, "John");
Records automatically generate:
- Constructor
- Getters
- equals()
- hashCode()
- toString()
Interview Trick Question
Is final Enough to Make a Class Immutable?
❌ No.
final class Employee {
private List<String> skills;
}
If the list can still be modified, the class is not truly immutable.
You must also protect mutable fields using defensive copies.
Immutable Class Checklist
✅ Class should be final
✅ Fields should be private and final
✅ No setter methods
✅ Initialize fields through constructor
✅ Return defensive copies for mutable fields
✅ Protect mutable collections
Key Interview Points
- Immutable objects cannot change after creation.
- Declare the class as
final. - Make fields
private final. - Do not provide setters.
- Use defensive copies for mutable fields.
- Immutable objects are naturally thread-safe.
- String is a classic example of an immutable class.
One-Line Interview Answer
To create an immutable class in Java, make the class final, declare all fields as private final, initialize them through the constructor, provide only getter methods, and use defensive copies for any mutable objects or collections.
15) Fail-fast vs Fail-safe iterators
# Fail-Fast vs Fail-Safe Iterators in JavaWhen iterating over a collection, modifying the collection simultaneously can lead to unexpected behavior.
Java provides two types of iterator behaviors:
- Fail-Fast Iterators
- Fail-Safe Iterators
The key difference is how they react when the underlying collection is modified during iteration.
Fail-Fast Iterator
A Fail-Fast Iterator immediately throws a:
ConcurrentModificationException
if it detects that the collection has been structurally modified after the iterator was created.
Examples:
ArrayList
HashMap
HashSet
LinkedList
TreeMap
TreeSet
Example
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String value = itr.next();
if (value.equals("Java")) {
list.add("Hibernate");
}
}
}
}
Output
Exception in thread "main"
java.util.ConcurrentModificationException
How Fail-Fast Works Internally
Collections maintain a modification count:
modCount
When an iterator is created:
expectedModCount = modCount;
During iteration:
if(expectedModCount != modCount)
throw ConcurrentModificationException;
Any structural modification changes modCount.
Structural Modifications
These modify collection size:
add()
remove()
clear()
Example:
list.add("Java");
list.remove("Java");
These trigger Fail-Fast behavior.
Safe Removal Using Iterator
Allowed:
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String value = itr.next();
if (value.equals("Java")) {
itr.remove();
}
}
No exception occurs because the iterator itself performs the modification.
Fail-Safe Iterator
A Fail-Safe Iterator works on a copy (snapshot) of the collection rather than the original collection.
Therefore, modifications during iteration do not throw exceptions.
Examples:
CopyOnWriteArrayList
ConcurrentHashMap
Example
import java.util.concurrent.CopyOnWriteArrayList;
public class Test {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();
list.add("Java");
list.add("Spring");
for (String value : list) {
if (value.equals("Java")) {
list.add("Hibernate");
}
System.out.println(value);
}
System.out.println(list);
}
}
Output
Java
Spring
[Java, Spring, Hibernate]
No exception occurs.
How Fail-Safe Works
Original Collection
│
▼
Create Snapshot Copy
│
▼
Iterator Reads Snapshot
Changes happen in the original collection.
The iterator continues reading from the snapshot.
Comparison Table
| Feature | Fail-Fast | Fail-Safe |
|---|---|---|
| Works On | Original Collection | Copy/Snapshot |
| Concurrent Modification | ❌ Not Allowed | ✅ Allowed |
| Exception Thrown | ConcurrentModificationException | No Exception |
| Memory Usage | Lower | Higher |
| Performance | Faster | Slightly Slower |
| Thread Safety | Not Thread Safe | Generally Used in Concurrent Collections |
| Collection Changes Visible During Iteration | No | No (Iterator sees snapshot) |
Examples of Each
Fail-Fast Collections
ArrayList
LinkedList
HashMap
HashSet
TreeMap
TreeSet
Vector
Fail-Safe Collections
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
ConcurrentSkipListMap
Visualization
Fail-Fast
ArrayList
│
Iterator Created
│
Collection Modified
│
ConcurrentModificationException
Fail-Safe
CopyOnWriteArrayList
│
Snapshot Created
│
Collection Modified
│
Iterator Continues Normally
Real-World Use Cases
Fail-Fast
Used when:
- Concurrent modifications should be detected immediately.
- Single-threaded applications.
- Data consistency is critical.
Example:
ArrayList
HashMap
Fail-Safe
Used when:
- Multiple threads read and modify data simultaneously.
- High-read, low-write systems.
Example:
CopyOnWriteArrayList
Common in:
- Caching systems
- Event listeners
- Configuration data
- Concurrent applications
Interview Trick Question
Does Fail-Fast Guarantee Detection of Every Modification?
❌ No.
The Java documentation states that Fail-Fast behavior is:
Best Effort Basis
It tries to detect concurrent modifications but does not provide a guaranteed mechanism in all cases.
Iterator vs Enumeration
| Feature | Iterator | Enumeration |
|---|---|---|
| Remove Support | ✅ Yes | ❌ No |
| Fail-Fast | ✅ Yes | ❌ No |
| Modern API | ✅ Yes | Legacy |
Key Interview Points
- Fail-Fast iterators throw
ConcurrentModificationExceptionwhen the collection is modified during iteration. - They work on the original collection.
- Fail-Safe iterators operate on a snapshot/copy of the collection.
- Fail-Safe iterators do not throw
ConcurrentModificationException. ArrayListandHashMapuse Fail-Fast iterators.CopyOnWriteArrayListandConcurrentHashMapuse Fail-Safe behavior.- Fail-Safe iterators require additional memory because they maintain a copy.
One-Line Interview Answer
Fail-Fast iterators work on the original collection and throw ConcurrentModificationException if the collection is modified during iteration, whereas Fail-Safe iterators work on a snapshot of the collection and allow concurrent modifications without throwing exceptions.
16) What is an interface? Features?
# What is an Interface in Java?An Interface is a blueprint of a class that defines a set of behaviors (methods) that implementing classes must provide.
It is used to achieve:
- Abstraction
- Multiple Inheritance
- Loose Coupling
- Polymorphism
An interface specifies what a class should do, not how it should do it.
Syntax
interface Vehicle {
void start();
void stop();
}
Implementation:
class Car implements Vehicle {
@Override
public void start() {
System.out.println("Car Started");
}
@Override
public void stop() {
System.out.println("Car Stopped");
}
}
Usage:
Vehicle vehicle = new Car();
vehicle.start();
vehicle.stop();
Output:
Car Started
Car Stopped
Key Features of Interface
1. Supports Abstraction
Interfaces hide implementation details and expose only behavior.
interface Payment {
void pay();
}
The user only knows:
pay();
not how payment is processed internally.
2. Supports Multiple Inheritance
Java does not allow multiple inheritance with classes:
class A {}
class B {}
// Not Allowed
class C extends A, B {}
But it allows multiple inheritance through interfaces:
interface A {
void methodA();
}
interface B {
void methodB();
}
class C implements A, B {
public void methodA() {}
public void methodB() {}
}
3. Enables Runtime Polymorphism
interface Animal {
void sound();
}
Implementations:
class Dog implements Animal {
public void sound() {
System.out.println("Bark");
}
}
class Cat implements Animal {
public void sound() {
System.out.println("Meow");
}
}
Usage:
Animal animal = new Dog();
animal.sound();
Output:
Bark
The actual method is decided at runtime.
4. Promotes Loose Coupling
Bad Design:
class PaymentService {
CreditCardPayment payment =
new CreditCardPayment();
}
Tightly coupled.
Better Design:
class PaymentService {
private Payment payment;
}
Any implementation can be injected:
CreditCardPayment
UPIPayment
PayPalPayment
This is heavily used in Spring Framework.
5. Supports Functional Interfaces
Java 8 introduced Functional Interfaces.
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
Used with Lambda Expressions:
Calculator c =
(a, b) -> a + b;
6. Default Methods (Java 8)
Before Java 8:
interface Vehicle {
void start();
}
After Java 8:
interface Vehicle {
default void start() {
System.out.println("Vehicle Started");
}
}
Implementing classes may use or override it.
7. Static Methods (Java 8)
interface Utility {
static void print() {
System.out.println("Utility Method");
}
}
Usage:
Utility.print();
8. Private Methods (Java 9)
interface Vehicle {
private void log() {
System.out.println("Logging");
}
}
Used internally within default methods.
Interface Rules
Variables
All interface variables are implicitly:
public static final
Example:
interface AppConfig {
int MAX_USERS = 100;
}
Equivalent to:
public static final int MAX_USERS = 100;
Methods
Abstract methods are implicitly:
public abstract
Example:
void execute();
Equivalent to:
public abstract void execute();
Why Use Interfaces?
1. Achieve Abstraction
Expose only behavior.
interface Payment {
void pay();
}
Hide implementation details.
2. Enable Loose Coupling
Switch implementations easily.
Payment payment = new UpiPayment();
Later:
payment = new CreditCardPayment();
No code changes in the client.
3. Support Dependency Injection
Spring commonly injects interfaces.
@Autowired
private UserService userService;
Implementation can be changed without affecting the client.
4. Improve Testability
Mock implementations can be created.
UserService mockService =
new MockUserService();
Useful in unit testing.
5. Standardize Behavior
Example:
List
Set
Map
Runnable
Comparable
Comparator
These define contracts that multiple classes implement.
Real-World Example
interface NotificationService {
void send(String message);
}
Implementations:
EmailNotification
SMSNotification
WhatsAppNotification
Client:
NotificationService service =
new EmailNotification();
service.send("Hello");
The client remains independent of the implementation.
Interface vs Abstract Class
| Feature | Interface | Abstract Class |
|---|---|---|
| Multiple Inheritance | ✅ Yes | ❌ No |
| Constructor | ❌ No | ✅ Yes |
| Instance Variables | ❌ No | ✅ Yes |
| Abstract Methods | ✅ Yes | ✅ Yes |
| Concrete Methods | ✅ (Java 8+) | ✅ Yes |
| State Management | ❌ No | ✅ Yes |
| Purpose | Contract | Partial Implementation |
Common Java Interfaces
Runnable
Callable
Comparable
Comparator
List
Set
Map
Serializable
Cloneable
AutoCloseable
Interview Trick Question
Can an Interface Have a Method Body?
✅ Yes.
Since Java 8:
default methods
static methods
Since Java 9:
private methods
Key Interview Points
- Interface defines a contract that implementing classes must follow.
- Used to achieve abstraction and multiple inheritance.
- Supports runtime polymorphism.
- Promotes loose coupling and dependency injection.
- All variables are
public static final. - Abstract methods are
public abstract. - Java 8 introduced default and static methods.
- Java 9 introduced private methods.
One-Line Interview Answer
An Interface is a contract that defines a set of behaviors without specifying their implementation, enabling abstraction, multiple inheritance, loose coupling, polymorphism, and flexible application design in Java.
17) SOLID principles
# SOLID Principles in JavaSOLID is a set of five object-oriented design principles introduced by Robert C. Martin (Uncle Bob) to create software that is:
- Easy to maintain
- Easy to extend
- Easy to test
- Flexible and scalable
- Less tightly coupled
What Does SOLID Stand For?
| Letter | Principle |
|---|---|
| S | Single Responsibility Principle (SRP) |
| O | Open/Closed Principle (OCP) |
| L | Liskov Substitution Principle (LSP) |
| I | Interface Segregation Principle (ISP) |
| D | Dependency Inversion Principle (DIP) |
1. Single Responsibility Principle (SRP)
Definition
A class should have only one reason to change.
In other words:
One Class = One Responsibility
Bad Example
class Employee {
public void calculateSalary() {
// Salary logic
}
public void saveToDatabase() {
// Database logic
}
public void generateReport() {
// Reporting logic
}
}
Problems:
- Salary change affects class.
- Database change affects class.
- Report change affects class.
Multiple responsibilities.
Good Example
class Employee {
private String name;
}
class SalaryService {
public void calculateSalary(Employee emp) {
}
}
class EmployeeRepository {
public void save(Employee emp) {
}
}
class ReportService {
public void generateReport(Employee emp) {
}
}
Each class has a single responsibility.
Benefit
✅ Easier maintenance
✅ Better readability
✅ Easier testing
2. Open/Closed Principle (OCP)
Definition
Software entities should be open for extension but closed for modification.
Extend Existing Code
Don't Modify Existing Code
Bad Example
class PaymentService {
public void pay(String type) {
if(type.equals("CARD")) {
// Card payment
}
else if(type.equals("UPI")) {
// UPI payment
}
}
}
Every new payment type requires modification.
Good Example
interface Payment {
void pay();
}
class CardPayment implements Payment {
public void pay() {
System.out.println("Card Payment");
}
}
class UpiPayment implements Payment {
public void pay() {
System.out.println("UPI Payment");
}
}
class PaymentService {
public void processPayment(Payment payment) {
payment.pay();
}
}
New payment methods can be added without changing existing code.
Benefit
✅ Easy extensibility
✅ Lower risk of bugs
✅ Better maintainability
3. Liskov Substitution Principle (LSP)
Definition
Subclasses should be replaceable with their parent classes without breaking the application.
Child Should Behave Like Parent
Bad Example
class Bird {
public void fly() {
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException();
}
}
Penguin cannot fly.
LSP is violated.
Good Example
interface Bird {
}
interface FlyingBird extends Bird {
void fly();
}
class Sparrow implements FlyingBird {
public void fly() {
System.out.println("Flying");
}
}
class Penguin implements Bird {
}
Each class behaves correctly.
Benefit
✅ Reliable inheritance
✅ Better polymorphism
✅ Fewer runtime surprises
4. Interface Segregation Principle (ISP)
Definition
Clients should not be forced to depend on methods they do not use.
Many Small Interfaces
Instead of One Large Interface
Bad Example
interface Worker {
void work();
void eat();
void sleep();
}
class Robot implements Worker {
public void work() {}
public void eat() {
throw new UnsupportedOperationException();
}
public void sleep() {
throw new UnsupportedOperationException();
}
}
Robot doesn't eat or sleep.
Good Example
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Human implements Workable, Eatable {
public void work() {}
public void eat() {}
}
class Robot implements Workable {
public void work() {}
}
Benefit
✅ Smaller interfaces
✅ Better flexibility
✅ Reduced coupling
5. Dependency Inversion Principle (DIP)
Definition
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Depend On Interfaces
Not Concrete Classes
Bad Example
class MySQLDatabase {
public void save() {
}
}
class UserService {
private MySQLDatabase db =
new MySQLDatabase();
}
Tightly coupled.
Changing database requires code changes.
Good Example
interface Database {
void save();
}
class MySQLDatabase
implements Database {
public void save() {
System.out.println("MySQL Save");
}
}
class OracleDatabase
implements Database {
public void save() {
System.out.println("Oracle Save");
}
}
class UserService {
private Database database;
public UserService(Database database) {
this.database = database;
}
}
Usage:
Database db =
new MySQLDatabase();
UserService service =
new UserService(db);
Benefit
✅ Loose coupling
✅ Easy testing
✅ Easy replacement of implementations
✅ Foundation of Spring Dependency Injection
Real-World Example in Spring Boot
@Service
public class UserService {
@Autowired
private UserRepository repository;
}
Here:
UserService
↓
UserRepository Interface
↓
JpaRepository Implementation
This follows DIP.
Quick Summary Table
| Principle | Meaning |
|---|---|
| SRP | One class should have one responsibility |
| OCP | Open for extension, closed for modification |
| LSP | Child objects should replace parent objects safely |
| ISP | Prefer small focused interfaces |
| DIP | Depend on abstractions, not implementations |
SOLID Benefits
Better Maintainability
Changes affect fewer classes.
Better Scalability
New features can be added easily.
Better Testability
Mock implementations become simple.
Loose Coupling
Components remain independent.
Cleaner Code
Improves readability and design.
Interview-Friendly Mnemonic
S → Single Responsibility
O → Open/Closed
L → Liskov Substitution
I → Interface Segregation
D → Dependency Inversion
Remember:
One Responsibility
Extend, Don't Modify
Replace Safely
Small Interfaces
Depend on Abstractions
One-Line Interview Answer
SOLID is a set of five object-oriented design principles (SRP, OCP, LSP, ISP, and DIP) that help developers build maintainable, scalable, loosely coupled, and extensible software systems.
18) What is the difference between `==` and `equals()`?
# What is the Difference Between `==` and `equals()` in Java?One of the most frequently asked Java interview questions is:
What is the difference between
==andequals()?
The short answer is:
==compares references (memory addresses) for objects.equals()compares contents (logical equality) of objects.
== Operator
The == operator checks whether two references point to the same object in memory.
Example
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2);
Output
false
Why?
s1 ----> "Java" (Object 1)
s2 ----> "Java" (Object 2)
Different objects, different memory locations.
Therefore:
s1 == s2
returns:
false
equals() Method
The equals() method checks whether two objects have the same content/state.
Example
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1.equals(s2));
Output
true
Because:
Content = "Java"
Content = "Java"
The values are identical.
Memory Visualization
String s1 = new String("Java");
String s2 = new String("Java");
Heap Memory
s1 ──► Object1("Java")
s2 ──► Object2("Java")
Comparison:
s1 == s2
Object1 == Object2
Result:
false
Comparison:
s1.equals(s2)
"Java".equals("Java")
Result:
true
Special Case: String Pool
String s1 = "Java";
String s2 = "Java";
Memory:
String Pool
s1 ──┐
├──► "Java"
s2 ──┘
Now:
System.out.println(s1 == s2);
Output:
true
because both references point to the same pooled object.
Primitive Types
For primitive values, == compares actual values.
int a = 10;
int b = 10;
System.out.println(a == b);
Output:
true
Because:
10 == 10
Custom Object Example
Without Overriding equals()
class Employee {
int id;
Employee(int id) {
this.id = id;
}
}
Employee e1 = new Employee(101);
Employee e2 = new Employee(101);
System.out.println(e1.equals(e2));
Output:
false
Why?
Default implementation from Object:
public boolean equals(Object obj) {
return (this == obj);
}
It compares references.
Overriding equals()
class Employee {
int id;
Employee(int id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Employee))
return false;
Employee other = (Employee) obj;
return this.id == other.id;
}
}
Now:
Employee e1 = new Employee(101);
Employee e2 = new Employee(101);
System.out.println(e1.equals(e2));
Output:
true
Comparison Table
| Feature | == |
equals() |
|---|---|---|
| Type | Operator | Method |
| Defined In | Java Language | Object Class |
| Compares | References (Objects) / Values (Primitives) | Logical Content |
| Can Be Overridden | ❌ No | ✅ Yes |
| Works With Primitives | ✅ Yes | ❌ No |
| Works With Objects | ✅ Yes | ✅ Yes |
| Used For | Identity Comparison | Equality Comparison |
Example Summary
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
Output:
false
true
Explanation:
== → Different Objects
equals() → Same Content
Common Interview Questions
Q1: Why Does String Override equals()?
Because logical comparison is more useful than reference comparison.
"Java".equals("Java")
should return:
true
even if objects are different.
Q2: What Happens If equals() Is Not Overridden?
The implementation from Object is used:
public boolean equals(Object obj) {
return this == obj;
}
In that case:
equals() behaves like ==
Q3: Should hashCode() Also Be Overridden?
✅ Yes.
Whenever equals() is overridden, hashCode() must also be overridden.
@Override
public int hashCode() {
return Objects.hash(id);
}
Required for:
HashMap
HashSet
Hashtable
Interview Trick Question
String s1 = "Java";
String s2 = "Java";
System.out.println(s1 == s2);
Output:
true
Because String literals are stored in the String Pool.
However:
String s1 = new String("Java");
String s2 = new String("Java");
Output:
false
because two separate objects are created.
Key Interview Points
==compares references for objects and values for primitives.equals()compares object content (logical equality).- String overrides
equals()to compare character sequences. - Default
equals()implementation behaves like==. - When overriding
equals(), always overridehashCode(). - Use
equals()when comparing object values.
One-Line Interview Answer
The == operator compares object references (or primitive values), whereas the equals() method compares the logical content of objects and can be overridden to define custom equality behavior.
19) What is the `instanceof` operator?
# What is the `instanceof` Operator in Java?The instanceof operator is used to check whether an object is an instance of a particular class, subclass, or interface at runtime.
It returns:
true
if the object belongs to the specified type, otherwise:
false
Syntax
object instanceof ClassName
Example:
String str = "Java";
System.out.println(str instanceof String);
Output:
true
Why Use instanceof?
It helps:
- Check an object's type at runtime.
- Prevent
ClassCastException. - Perform safe type casting.
- Implement polymorphic behavior.
Basic Example
String str = "Java";
System.out.println(str instanceof String);
System.out.println(str instanceof Object);
Output:
true
true
Explanation:
String extends Object
Therefore, a String object is also an Object.
Example with Inheritance
class Animal {
}
class Dog extends Animal {
}
Animal animal = new Dog();
System.out.println(animal instanceof Dog);
System.out.println(animal instanceof Animal);
System.out.println(animal instanceof Object);
Output:
true
true
true
Memory:
Animal animal = new Dog();
↑
Actual Object
instanceof checks the actual object type at runtime.
Example with Interface
interface Vehicle {
}
class Car implements Vehicle {
}
Car car = new Car();
System.out.println(car instanceof Vehicle);
Output:
true
Because:
Car implements Vehicle
Preventing ClassCastException
Unsafe Cast
Object obj = "Java";
Integer num = (Integer) obj;
Runtime Error:
ClassCastException
Safe Cast Using instanceof
Object obj = "Java";
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str);
}
Output:
Java
instanceof with null
String str = null;
System.out.println(str instanceof String);
Output:
false
Important Rule:
null instanceof AnyClass
always returns:
false
Java 16 Pattern Matching for instanceof
Before Java 16:
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
Java 16+:
if (obj instanceof String str) {
System.out.println(str.length());
}
Output:
4
Benefits:
- No explicit casting.
- Cleaner code.
- Less boilerplate.
Runtime Type Checking Example
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
public void identify(Animal animal) {
if (animal instanceof Dog) {
System.out.println("Dog");
}
else if (animal instanceof Cat) {
System.out.println("Cat");
}
}
Usage:
identify(new Dog());
identify(new Cat());
Output:
Dog
Cat
Common Use Case in equals()
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Employee))
return false;
Employee other = (Employee) obj;
return this.id == other.id;
}
Used to ensure safe type comparison.
Comparison with getClass()
Using instanceof
obj instanceof Employee
Returns:
true
for:
- Employee
- Any subclass of Employee
Using getClass()
obj.getClass() == Employee.class
Returns:
true
only for exact Employee objects.
Example
class Employee {
}
class Manager extends Employee {
}
Employee emp = new Manager();
emp instanceof Employee
Output:
true
emp.getClass() == Employee.class
Output:
false
because actual type is Manager.
Comparison Table
| Feature | instanceof |
getClass() |
|---|---|---|
| Checks Subclasses | ✅ Yes | ❌ No |
| Checks Interfaces | ✅ Yes | ❌ No |
| Runtime Type Check | ✅ Yes | ✅ Yes |
| Supports Pattern Matching | ✅ Yes | ❌ No |
| Exact Class Match | ❌ No | ✅ Yes |
Interview Trick Questions
Q1: What is the Output?
String str = null;
System.out.println(str instanceof String);
Output:
false
Q2: Can instanceof Check Interfaces?
✅ Yes.
car instanceof Vehicle
returns:
true
if Car implements Vehicle.
Q3: Does instanceof Work at Compile Time?
❌ No.
It performs a runtime type check.
Key Interview Points
instanceofchecks whether an object belongs to a specific class, subclass, or interface.- Returns
trueorfalse. - Used for safe casting and runtime type checking.
- Prevents
ClassCastException. null instanceof AnyClassalways returnsfalse.- Java 16 introduced pattern matching with
instanceof. - Works with inheritance and interfaces.
One-Line Interview Answer
The instanceof operator is used to determine at runtime whether an object is an instance of a specified class, subclass, or interface, returning true if the relationship exists and false otherwise.
20) `super` keyword use cases
# `super` Keyword Use Cases in JavaThe super keyword is a reference variable used to refer to the immediate parent class object.
It is primarily used in inheritance to access parent class members that are hidden or overridden in the child class.
What Does super Refer To?
Parent Class Object
Example:
class Animal {
}
class Dog extends Animal {
}
Inside Dog, the super keyword refers to the Animal part of the object.
Main Use Cases of super
The super keyword is commonly used for:
- Accessing parent class variables
- Calling parent class methods
- Calling parent class constructors
1. Access Parent Class Variable
When both parent and child classes have variables with the same name, super is used to access the parent's variable.
Example
class Animal {
String type = "Animal";
}
class Dog extends Animal {
String type = "Dog";
void display() {
System.out.println(type);
System.out.println(super.type);
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.display();
}
}
Output
Dog
Animal
Explanation
type
refers to:
Child Class Variable
while:
super.type
refers to:
Parent Class Variable
2. Call Parent Class Method
When a child class overrides a parent method, super can invoke the parent version.
Example
class Animal {
void sound() {
System.out.println("Animal Sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog Bark");
}
void display() {
sound();
super.sound();
}
}
Output
Dog Bark
Animal Sound
Explanation
sound();
calls:
Child Method
while:
super.sound();
calls:
Parent Method
3. Call Parent Class Constructor
The most common use of super is invoking the parent constructor.
Example
class Animal {
Animal() {
System.out.println("Animal Constructor");
}
}
class Dog extends Animal {
Dog() {
super();
System.out.println("Dog Constructor");
}
}
Output
Animal Constructor
Dog Constructor
Constructor Chaining
class Parent {
Parent() {
System.out.println("Parent");
}
}
class Child extends Parent {
Child() {
System.out.println("Child");
}
}
Output:
Parent
Child
Reason:
super();
is automatically inserted by the compiler as the first statement.
Equivalent code:
Child() {
super();
System.out.println("Child");
}
Parameterized Constructor Example
class Employee {
Employee(int id) {
System.out.println("Employee Id: " + id);
}
}
class Manager extends Employee {
Manager() {
super(101);
System.out.println("Manager Created");
}
}
Output
Employee Id: 101
Manager Created
Rules of super
Rule 1
super() must be the first statement in a constructor.
✅ Valid:
Child() {
super();
System.out.println("Hello");
}
❌ Invalid:
Child() {
System.out.println("Hello");
super();
}
Compilation Error:
Call to super must be first statement in constructor
Rule 2
super cannot be used in a static context.
❌ Invalid:
static void test() {
super.toString();
}
Compilation Error.
Reason:
super belongs to object context
Rule 3
super refers only to the immediate parent class.
Example:
GrandParent
↑
Parent
↑
Child
Inside Child:
super
refers only to:
Parent
not GrandParent.
this vs super
| Feature | this |
super |
|---|---|---|
| Refers To | Current Class Object | Parent Class Object |
| Access Current Variables | ✅ Yes | ❌ No |
| Access Parent Variables | ❌ No | ✅ Yes |
| Call Current Constructor | ✅ this() |
❌ No |
| Call Parent Constructor | ❌ No | ✅ super() |
Example: this and super
class Parent {
String name = "Parent";
}
class Child extends Parent {
String name = "Child";
void display() {
System.out.println(this.name);
System.out.println(super.name);
}
}
Output
Child
Parent
Real-World Example
class BaseController {
void log() {
System.out.println("Logging Request");
}
}
class UserController
extends BaseController {
void createUser() {
super.log();
System.out.println("Creating User");
}
}
Output:
Logging Request
Creating User
Used frequently in:
- Spring Framework
- Servlet APIs
- Custom Frameworks
- Template Method Pattern
Interview Trick Questions
Q1: Can We Call Both this() and super() in the Same Constructor?
❌ No.
Child() {
this();
super();
}
Compilation Error.
Reason:
Both must be the first statement.
Q2: Is super() Added Automatically?
✅ Yes.
If you do not explicitly call a parent constructor, the compiler inserts:
super();
automatically.
Q3: Can We Use super to Access Private Members?
❌ No.
Private members are not inherited.
private int id;
cannot be accessed using:
super.id;
Key Interview Points
superrefers to the immediate parent class object.- Used to access parent variables hidden by child variables.
- Used to call overridden parent methods.
- Used to invoke parent constructors.
super()must be the first statement in a constructor.- Cannot be used in static methods.
- Refers only to the immediate parent class.
One-Line Interview Answer
The super keyword is used to refer to the immediate parent class and is commonly used to access parent variables, invoke parent methods, and call parent class constructors in inheritance hierarchies.
21) What is `finalize()`? Why discouraged?
# What is `finalize()` in Java? Why Is It Discouraged?The finalize() method is a special method defined in the Object class that was historically used to perform cleanup operations before an object was garbage collected.
protected void finalize() throws Throwable
The Garbage Collector (GC) may invoke this method before reclaiming the object's memory.
Purpose of finalize()
Before Java introduced modern resource-management mechanisms, developers used finalize() to:
- Release native resources
- Close files
- Close database connections
- Perform cleanup activities
Example:
class Employee {
@Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
}
}
Example
class Test {
@Override
protected void finalize() throws Throwable {
System.out.println("Finalize Called");
}
public static void main(String[] args) {
Test t = new Test();
t = null;
System.gc();
}
}
Possible Output:
Finalize Called
How finalize() Works
Object Created
↓
Object Becomes Unreachable
↓
Garbage Collector Detects It
↓
finalize() May Be Invoked
↓
Object Memory Reclaimed
Important:
"May Be Invoked"
There is no guarantee that finalize() will execute.
Why Is finalize() Discouraged?
Starting from Java 9:
@Deprecated
protected void finalize()
It was deprecated and is strongly discouraged.
1. No Guarantee of Execution
Many developers mistakenly believe:
finalize()
will always run before object destruction.
This is false.
Example:
Employee emp = new Employee();
emp = null;
Even if the object becomes unreachable:
finalize()
may never execute.
Reason:
- GC may not run immediately.
- JVM may terminate before GC runs.
2. Unpredictable Timing
You cannot control when GC executes.
System.gc();
is only a request.
The JVM may ignore it.
Therefore:
Object Unreachable
↓
Unknown Time
↓
finalize() Called (Maybe)
3. Performance Overhead
Objects with a finalize() method require extra processing.
Normal object:
Object → Garbage Collected
Finalizable object:
Object
↓
Finalize Queue
↓
Finalizer Thread
↓
Garbage Collection
Additional work slows down GC.
4. Resource Leaks
Suppose a file is opened:
FileInputStream fis =
new FileInputStream("data.txt");
and cleanup depends on:
finalize()
If GC is delayed:
File Remains Open
This can exhaust system resources.
5. Exceptions Are Ignored
@Override
protected void finalize() throws Throwable {
throw new RuntimeException();
}
The JVM ignores exceptions thrown from finalize().
This makes debugging difficult.
6. Object Resurrection Problem
Inside finalize(), an object can make itself reachable again.
Example:
class Test {
static Test obj;
@Override
protected void finalize() {
obj = this;
}
}
Flow:
Object Eligible For GC
↓
finalize() Executes
↓
Object Becomes Reachable Again
This creates unpredictable behavior.
Modern Alternative: Try-With-Resources
Instead of:
finalize()
use:
try-with-resources
Example
try (FileInputStream fis =
new FileInputStream("data.txt")) {
// Read file
}
The resource is automatically closed.
Benefits:
Deterministic
Reliable
Fast
Safe
AutoCloseable Interface
Modern Java resource cleanup uses:
AutoCloseable
or
Closeable
Example:
class DatabaseConnection
implements AutoCloseable {
@Override
public void close() {
System.out.println("Connection Closed");
}
}
Usage:
try(DatabaseConnection db =
new DatabaseConnection()) {
// Use resource
}
Output:
Connection Closed
finalize() vs close()
| Feature | finalize() |
close() |
|---|---|---|
| Invocation | By GC | By Programmer / JVM |
| Guaranteed Execution | ❌ No | ✅ Yes |
| Performance | Slow | Fast |
| Deterministic | ❌ No | ✅ Yes |
| Deprecated | ✅ Yes | ❌ No |
| Resource Cleanup | Not Recommended | Recommended |
Deprecation Status
Java 9
finalize()
was officially deprecated.
Modern Recommendation
Use:
try-with-resources
and
AutoCloseable
instead.
Interview Trick Question
Does Calling System.gc() Guarantee finalize() Execution?
❌ No.
System.gc();
only requests garbage collection.
The JVM is free to ignore the request.
Can finalize() Be Called More Than Once?
❌ No.
The JVM guarantees that finalize() is invoked at most once per object.
Is finalize() Called Immediately When an Object Becomes Unreachable?
❌ No.
Execution timing is completely controlled by the Garbage Collector.
Key Interview Points
finalize()is a method of theObjectclass.- It was intended for cleanup before garbage collection.
- Invocation is not guaranteed.
- Execution timing is unpredictable.
- Causes performance overhead.
- Can lead to resource leaks and object resurrection issues.
- Deprecated since Java 9.
- Modern Java recommends
try-with-resourcesandAutoCloseable.
One-Line Interview Answer
finalize() is a deprecated method that the Garbage Collector may invoke before reclaiming an object’s memory, but because its execution is unpredictable and unreliable, modern Java applications use try-with-resources and AutoCloseable for resource cleanup instead.
22) What are the main Collections Framework interfaces?
# What Are the Main Collections Framework Interfaces in Java?The Java Collections Framework (JCF) is a set of interfaces and classes that provide ready-made data structures and algorithms for storing and manipulating groups of objects.
The framework is organized around a few core interfaces that define different ways of storing and accessing data.
Collections Framework Hierarchy
Iterable
│
Collection
│
┌──────────────┼──────────────┐
│ │ │
List Set Queue
│
SortedSet
│
NavigableSet
Map (Separate Hierarchy)
│
SortedMap
│
NavigableMap
Main Interfaces of the Collections Framework
The most important interfaces are:
- Collection
- List
- Set
- Queue
- Deque
- Map
- SortedSet
- NavigableSet
- SortedMap
- NavigableMap
1. Collection Interface
The root interface of the Collection hierarchy.
public interface Collection<E>
Provides common operations:
add()
remove()
contains()
size()
isEmpty()
clear()
Example:
Collection<String> collection =
new ArrayList<>();
collection.add("Java");
Important Methods
add(E e)
remove(Object o)
contains(Object o)
size()
iterator()
clear()
2. List Interface
A List is an ordered collection.
Characteristics:
- Maintains insertion order
- Allows duplicates
- Supports index-based access
Example:
List<String> list =
new ArrayList<>();
list.add("Java");
list.add("Spring");
list.add("Java");
Output:
[Java, Spring, Java]
Implementations
ArrayList
LinkedList
Vector
Stack
3. Set Interface
A Set stores unique elements.
Characteristics:
- No duplicates
- No index-based access
- Unordered (depending on implementation)
Example:
Set<String> set =
new HashSet<>();
set.add("Java");
set.add("Java");
Output:
[Java]
Implementations
HashSet
LinkedHashSet
TreeSet
4. Queue Interface
A Queue follows FIFO (First In First Out).
First Added
↓
First Removed
Example:
Queue<Integer> queue =
new LinkedList<>();
queue.offer(10);
queue.offer(20);
queue.poll();
Output:
10
Common Methods
offer()
poll()
peek()
Implementations
LinkedList
PriorityQueue
ArrayDeque
5. Deque Interface
Deque means:
Double Ended Queue
Elements can be inserted and removed from both ends.
Example:
Deque<Integer> deque =
new ArrayDeque<>();
deque.addFirst(10);
deque.addLast(20);
Operations
addFirst()
addLast()
removeFirst()
removeLast()
6. Map Interface
Map is not part of the Collection hierarchy.
It stores:
Key → Value
Characteristics:
- Unique keys
- Duplicate values allowed
Example:
Map<Integer, String> map =
new HashMap<>();
map.put(101, "John");
Output:
101 → John
Implementations
HashMap
LinkedHashMap
TreeMap
Hashtable
ConcurrentHashMap
7. SortedSet Interface
A Set that maintains sorted order.
Example:
SortedSet<Integer> set =
new TreeSet<>();
set.add(30);
set.add(10);
set.add(20);
Output:
[10, 20, 30]
Additional Methods
first()
last()
headSet()
tailSet()
8. NavigableSet Interface
Extends:
SortedSet
Provides navigation methods.
Example:
NavigableSet<Integer> set =
new TreeSet<>();
Methods:
higher()
lower()
ceiling()
floor()
Example
set.ceiling(15);
Output:
20
9. SortedMap Interface
A Map whose keys are automatically sorted.
Example:
SortedMap<Integer, String> map =
new TreeMap<>();
Output:
Keys Sorted Automatically
Methods
firstKey()
lastKey()
headMap()
tailMap()
10. NavigableMap Interface
Extends:
SortedMap
Provides advanced navigation.
Methods:
higherKey()
lowerKey()
ceilingKey()
floorKey()
Example:
NavigableMap<Integer, String> map =
new TreeMap<>();
Quick Comparison
| Interface | Stores | Duplicates | Ordering |
|---|---|---|---|
| Collection | Objects | Depends | Depends |
| List | Objects | ✅ Allowed | Insertion Order |
| Set | Objects | ❌ Not Allowed | Depends |
| Queue | Objects | ✅ Allowed | FIFO |
| Deque | Objects | ✅ Allowed | Double Ended |
| Map | Key-Value Pairs | Keys ❌ Values ✅ | Depends |
| SortedSet | Unique Objects | ❌ | Sorted |
| NavigableSet | Unique Objects | ❌ | Sorted |
| SortedMap | Key-Value Pairs | Unique Keys | Sorted Keys |
| NavigableMap | Key-Value Pairs | Unique Keys | Sorted Keys |
Most Common Implementations
| Interface | Common Implementation |
|---|---|
| List | ArrayList |
| Set | HashSet |
| SortedSet | TreeSet |
| Queue | LinkedList |
| Deque | ArrayDeque |
| Map | HashMap |
| SortedMap | TreeMap |
| NavigableMap | TreeMap |
Real-World Usage
List
List<Employee> employees;
Store ordered records.
Set
Set<String> uniqueEmails;
Remove duplicates.
Queue
Queue<Task> tasks;
Task scheduling.
Map
Map<Integer, Employee> employeeMap;
Fast lookup by key.
Interview Trick Question
Is Map Part of the Collection Interface?
❌ No.
Collection Hierarchy
↑
List
Set
Queue
Map
(Separate Hierarchy)
Map is part of the Collections Framework but does not extend the Collection interface.
Key Interview Points
Collectionis the root interface for List, Set, and Queue.Listmaintains insertion order and allows duplicates.Setstores unique elements.Queuefollows FIFO ordering.Dequesupports insertion/removal from both ends.Mapstores key-value pairs and is a separate hierarchy.SortedSetandSortedMapmaintain sorted order.NavigableSetandNavigableMapprovide advanced navigation methods.ArrayList,HashSet,LinkedList, andHashMapare the most commonly used implementations.
One-Line Interview Answer
The main Collections Framework interfaces are Collection, List, Set, Queue, Deque, and Map, along with their sorted and navigable variants, providing standardized ways to store, retrieve, and manipulate groups of objects in Java.
23) What is a Thread? How to Create a Thread in Java?
# What is a Thread? How to Create a Thread in Java?A Thread is the smallest unit of execution within a process.
It represents an independent path of execution that allows multiple tasks to run concurrently within the same application.
What is a Process?
A process is a running instance of a program.
Example:
Chrome Browser
MS Word
IntelliJ IDEA
Each running application is a process.
What is a Thread?
A thread is a lightweight sub-process.
Process
│
┌─┴───────────────┐
│ │
Thread-1 Thread-2
Multiple threads can execute simultaneously within a single process.
Real-Life Example
Consider a web browser:
Browser Process
│
┌────┼─────┐
│ │ │
UI Download Rendering
Each task runs in a separate thread.
This improves responsiveness and performance.
Benefits of Multithreading
1. Better CPU Utilization
Multiple tasks can run concurrently.
2. Improved Performance
Long-running tasks don't block the application.
3. Better User Experience
UI remains responsive while background work continues.
4. Resource Sharing
Threads share:
Heap Memory
Files
Database Connections
within the same process.
Ways to Create a Thread
Java provides two traditional ways:
- Extending the
Threadclass - Implementing the
Runnableinterface
Modern Java also supports:
- Callable & Future
- Executor Framework
- Virtual Threads (Java 21+)
Method 1: Extending the Thread Class
Create a class that extends Thread.
Example
class MyThread extends Thread {
@Override
public void run() {
System.out.println(
"Thread is running");
}
}
Usage:
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
Output:
Thread is running
Why start() Instead of run()?
Correct:
thread.start();
Wrong:
thread.run();
start()
Creates New Thread
Calls run()
run()
Normal Method Call
No New Thread Created
Execution Flow
main()
│
start()
│
New Thread Created
│
run()
Method 2: Implementing Runnable Interface
Recommended approach.
Example
class MyTask implements Runnable {
@Override
public void run() {
System.out.println(
"Runnable Thread Running");
}
}
Usage:
public class Test {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread =
new Thread(task);
thread.start();
}
}
Output:
Runnable Thread Running
Why Runnable Is Preferred?
Using inheritance:
class MyThread extends Thread
consumes your only inheritance option.
Java supports:
Single Inheritance
only.
With Runnable:
class Employee
extends Person
implements Runnable
you can still extend another class.
Thread Using Lambda Expression
Since Java 8:
Runnable task = () ->
System.out.println("Thread Running");
new Thread(task).start();
Anonymous Thread Example
new Thread(() -> {
System.out.println(
"Anonymous Thread");
}).start();
Multiple Threads Example
class Task extends Thread {
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(
Thread.currentThread().getName()
+ " : " + i);
}
}
}
public class Test {
public static void main(String[] args) {
Task t1 = new Task();
Task t2 = new Task();
t1.start();
t2.start();
}
}
Possible Output:
Thread-0 : 1
Thread-1 : 1
Thread-0 : 2
Thread-1 : 2
...
Order may vary because thread scheduling is handled by the JVM and OS.
Thread Lifecycle
NEW
↓
RUNNABLE
↓
RUNNING
↓
WAITING / BLOCKED
↓
TERMINATED
Modern Approach: Executor Framework
Instead of manually creating threads:
ExecutorService executor =
Executors.newFixedThreadPool(5);
executor.submit(() ->
System.out.println("Task"));
Benefits:
- Thread pooling
- Better performance
- Easier management
Java 21 Virtual Threads
Java 21 introduced lightweight threads.
Thread.startVirtualThread(() -> {
System.out.println("Virtual Thread");
});
Benefits:
Millions of Threads
Low Memory Usage
High Scalability
Especially useful for:
- Web Applications
- Microservices
- I/O Operations
Thread vs Process
| Feature | Process | Thread |
|---|---|---|
| Definition | Running Program | Smallest Unit of Execution |
| Memory | Separate Memory | Shared Memory |
| Creation Cost | High | Low |
| Communication | Complex | Easy |
| Performance | Slower | Faster |
Thread Class vs Runnable
| Feature | Thread Class | Runnable Interface |
|---|---|---|
| Inheritance Required | ✅ Yes | ❌ No |
| Supports Multiple Inheritance | ❌ No | ✅ Yes |
| Recommended | ❌ Less Preferred | ✅ Preferred |
| Reusability | Lower | Higher |
Interview Trick Questions
Q1: What Happens If We Call run() Directly?
thread.run();
No new thread is created.
Output executes in the main thread.
Q2: Which Method Actually Creates a New Thread?
start()
creates the new thread and internally invokes:
run()
Q3: Can We Start a Thread Twice?
thread.start();
thread.start();
❌ No.
Throws:
java.lang.IllegalThreadStateException
Key Interview Points
- A thread is the smallest unit of execution within a process.
- Multithreading improves performance and responsiveness.
- Threads can be created by extending
Threador implementingRunnable. Runnableis the preferred approach.- Always use
start()to create a new thread. - Calling
run()directly does not create a new thread. - Modern applications prefer
ExecutorService. - Java 21 introduces Virtual Threads for highly scalable concurrency.
One-Line Interview Answer
A Thread is the smallest unit of execution in a Java application, enabling concurrent task execution, and it can be created by extending the Thread class, implementing the Runnable interface, or using modern concurrency APIs such as ExecutorService and Virtual Threads.
24) What is a Virtual Thread in Java? How to Create It? When to Use It?
# What is a Virtual Thread in Java? How to Create It? When to Use It?Introduction
A Virtual Thread is a lightweight thread introduced as a standard feature in Java 21 (Project Loom).
Unlike traditional platform threads, virtual threads are managed by the JVM rather than being directly tied to operating system (OS) threads.
This allows Java applications to create millions of concurrent threads with very low memory overhead.
Why Were Virtual Threads Introduced?
Traditional Java threads are expensive because:
1 Thread = 1 OS Thread
Creating thousands of threads leads to:
- High memory consumption
- Context switching overhead
- Reduced scalability
Example:
for(int i = 0; i < 100000; i++) {
new Thread(task).start();
}
This can exhaust system resources.
Platform Thread vs Virtual Thread
Platform Thread (Traditional Thread)
Java Thread
│
▼
Operating System Thread
Characteristics:
- Heavyweight
- Expensive to create
- Limited scalability
Virtual Thread
Virtual Thread
│
▼
JVM Scheduler
│
▼
Few OS Threads
Characteristics:
- Lightweight
- Cheap to create
- Highly scalable
Key Features of Virtual Threads
1. Lightweight
Thousands or even millions of virtual threads can be created.
2. JVM Managed
Scheduling is handled by the JVM instead of the OS.
3. Better Scalability
Ideal for applications with many concurrent tasks.
4. Simpler Programming Model
Write code in a traditional blocking style without complex asynchronous programming.
How to Create a Virtual Thread?
Method 1: Using Thread.startVirtualThread()
public class Test {
public static void main(String[] args) {
Thread.startVirtualThread(() -> {
System.out.println(
"Virtual Thread Running");
});
}
}
Output:
Virtual Thread Running
Method 2: Using Thread Builder
Thread thread =
Thread.ofVirtual()
.start(() -> {
System.out.println(
"Virtual Thread");
});
Method 3: Create and Start Later
Thread thread =
Thread.ofVirtual()
.unstarted(() -> {
System.out.println(
"Virtual Thread");
});
thread.start();
Creating Multiple Virtual Threads
for (int i = 1; i <= 10000; i++) {
Thread.startVirtualThread(() -> {
System.out.println(
Thread.currentThread());
});
}
Creating 10,000 virtual threads is generally feasible.
Creating 10,000 platform threads usually is not.
Using Virtual Threads with ExecutorService
Recommended for enterprise applications.
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println(
"Task Executed");
});
}
Real-World Example
Suppose a web application receives:
100,000 Requests
Traditional Threads:
100,000 Requests
↓
100,000 OS Threads
↓
High Memory Usage
Virtual Threads:
100,000 Requests
↓
100,000 Virtual Threads
↓
Few OS Threads
↓
Efficient Execution
When to Use Virtual Threads?
1. Web Applications
Example:
Spring Boot
REST APIs
Microservices
Each request can run in its own virtual thread.
2. Database Operations
connection.executeQuery();
Database calls are usually blocking.
Virtual threads handle such blocking efficiently.
3. Network Calls
HttpClient
REST Calls
SOAP Calls
Useful when waiting for external services.
4. File I/O Operations
Read File
Write File
Upload File
Most I/O operations spend time waiting.
Virtual threads excel here.
5. High-Concurrency Systems
Examples:
- Chat Applications
- Payment Systems
- E-commerce Platforms
- API Gateways
When NOT to Use Virtual Threads?
CPU-Intensive Tasks
Example:
Image Processing
Encryption
Machine Learning
Large Computations
Reason:
CPU Bound Work
Virtual threads do not make CPU calculations faster.
Tight Computational Loops
while(true) {
calculate();
}
Virtual threads provide little benefit.
Virtual Threads vs Platform Threads
| Feature | Platform Thread | Virtual Thread |
|---|---|---|
| Introduced | Java 1.0 | Java 21 |
| Managed By | Operating System | JVM |
| Memory Usage | High | Very Low |
| Creation Cost | Expensive | Cheap |
| Scalability | Thousands | Millions |
| Best For | CPU-Bound Tasks | I/O-Bound Tasks |
| Blocking Operations | Costly | Efficient |
Example Comparison
Platform Thread
Thread thread =
new Thread(() -> {
System.out.println("Hello");
});
thread.start();
Virtual Thread
Thread.startVirtualThread(() -> {
System.out.println("Hello");
});
Much simpler and more scalable.
How Virtual Threads Work Internally
Virtual Thread
│
▼
JVM Scheduler
│
▼
Carrier Thread (OS Thread)
When a virtual thread blocks:
Database Call
File Read
Network Call
the JVM detaches it from the carrier thread and reuses the OS thread for other work.
This is the key reason virtual threads scale so well.
Benefits of Virtual Threads
High Scalability
Millions of Concurrent Tasks
Lower Memory Usage
Consumes far less memory than platform threads.
Simpler Code
Avoids callback-heavy or reactive programming for many use cases.
Better Resource Utilization
OS threads are reused efficiently.
Interview Trick Questions
Q1: Does a Virtual Thread Create a New OS Thread?
❌ No.
Multiple virtual threads share a small number of OS threads.
Q2: Are Virtual Threads Faster Than Platform Threads?
❌ Not necessarily.
They improve:
Scalability
Concurrency
Resource Usage
but not raw CPU speed.
Q3: Are Virtual Threads Suitable for Database Calls?
✅ Yes.
Database operations are typically I/O-bound and benefit greatly from virtual threads.
Q4: Which Java Version Introduced Virtual Threads?
Preview: Java 19
Preview: Java 20
Standard Feature: Java 21
Key Interview Points
- Virtual Threads were introduced as a standard feature in Java 21.
- They are lightweight threads managed by the JVM.
- Millions of virtual threads can run using a small number of OS threads.
- Ideal for I/O-bound workloads such as web requests, database access, file operations, and network calls.
- Not intended to speed up CPU-intensive computations.
- Can be created using
Thread.startVirtualThread()orExecutors.newVirtualThreadPerTaskExecutor(). - Virtual threads simplify concurrent programming while providing massive scalability.
One-Line Interview Answer
A Virtual Thread is a lightweight JVM-managed thread introduced in Java 21 that enables highly scalable concurrent applications by allowing millions of threads to run efficiently on a small number of operating system threads, making it ideal for I/O-bound workloads.
25) What is deadlock? How to avoid?
# What is Deadlock in Java? How to Avoid It?What is Deadlock?
A Deadlock is a situation where two or more threads are blocked forever because each thread is waiting for a resource (lock) held by another thread.
As a result:
Thread A waits for Thread B
Thread B waits for Thread A
Neither thread can proceed.
Simple Definition
Deadlock = Circular Waiting for Resources
Real-Life Example
Imagine:
Person A has Pen
Person B has Notebook
Now:
Person A waits for Notebook
Person B waits for Pen
Both keep waiting forever.
This is a deadlock.
Deadlock Example in Java
public class DeadlockDemo {
private static final Object lock1 =
new Object();
private static final Object lock2 =
new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println(
"Thread 1 acquired Lock1");
try {
Thread.sleep(100);
} catch (Exception e) {}
synchronized (lock2) {
System.out.println(
"Thread 1 acquired Lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println(
"Thread 2 acquired Lock2");
try {
Thread.sleep(100);
} catch (Exception e) {}
synchronized (lock1) {
System.out.println(
"Thread 2 acquired Lock1");
}
}
});
t1.start();
t2.start();
}
}
Possible Output
Thread 1 acquired Lock1
Thread 2 acquired Lock2
Application hangs forever.
Why Does Deadlock Occur?
Thread-1
Lock1 Acquired
Waiting For Lock2
Thread-2
Lock2 Acquired
Waiting For Lock1
Visualization:
Thread-1
│
▼
Lock1
│
Waiting For Lock2
▲
│
Lock2
│
Thread-2
Circular dependency causes deadlock.
Four Necessary Conditions for Deadlock
Deadlock occurs when all four conditions exist simultaneously.
1. Mutual Exclusion
Resource can be used by only one thread at a time.
Example:
synchronized(lock)
2. Hold and Wait
A thread holds one resource and waits for another.
Thread-1
Holds Lock1
Waits For Lock2
3. No Preemption
Resources cannot be forcibly taken away.
Only the owner thread can release them.
4. Circular Wait
Thread A → Resource B
Thread B → Resource A
Circular dependency exists.
How to Avoid Deadlock?
1. Lock Ordering (Most Common Solution)
Always acquire locks in the same order.
Wrong
Thread-1:
Lock1 → Lock2
Thread-2:
Lock2 → Lock1
Deadlock possible.
Correct
Thread-1:
Lock1 → Lock2
Thread-2:
Lock1 → Lock2
Deadlock impossible.
Example
synchronized(lock1) {
synchronized(lock2) {
// Work
}
}
All threads must follow the same sequence.
2. Use a Single Lock
Instead of multiple locks:
synchronized(lock) {
// Critical Section
}
No circular dependency.
3. Use tryLock()
The Lock interface provides:
tryLock()
which avoids waiting forever.
Example
ReentrantLock lock =
new ReentrantLock();
if(lock.tryLock()) {
try {
// Work
} finally {
lock.unlock();
}
}
If the lock is unavailable:
Thread Continues
No Deadlock
4. Use Timeout with tryLock()
lock.tryLock(
5,
TimeUnit.SECONDS);
The thread waits only for a limited time.
Example
if(lock.tryLock(
5,
TimeUnit.SECONDS)) {
try {
// Work
} finally {
lock.unlock();
}
}
else {
System.out.println(
"Lock Not Acquired");
}
5. Minimize Nested Locks
Bad:
synchronized(lock1) {
synchronized(lock2) {
synchronized(lock3) {
}
}
}
Many nested locks increase deadlock risk.
Better:
synchronized(lock1) {
// Small critical section
}
6. Avoid Unnecessary Synchronization
Only synchronize critical code.
Bad:
public synchronized void process() {
// Large block of code
}
Better:
public void process() {
synchronized(lock) {
// Critical section only
}
}
Detecting Deadlock
Using Thread Dump
Command:
jstack <pid>
Output may show:
Found one Java-level deadlock
Using JVisualVM
Monitor Threads
Detect Deadlocks
Using Java Mission Control (JMC)
Enterprise-grade monitoring tool.
Deadlock vs Starvation
| Feature | Deadlock | Starvation |
|---|---|---|
| Threads Block Forever | ✅ Yes | ❌ No |
| Resource Waiting | Circular | Resource Never Allocated |
| Recovery Possible | Difficult | Usually Possible |
| Cause | Circular Dependency | Unfair Scheduling |
Deadlock vs Livelock
Deadlock
No Thread Moves
Livelock
Threads Keep Running
But No Progress Is Made
Example:
Person A Moves Left
Person B Moves Right
Both Keep Adjusting Forever
Real-World Example
Banking Application
Thread-1:
Transfer Money
Account A → Account B
Locks:
Account A
Account B
Thread-2:
Transfer Money
Account B → Account A
Locks:
Account B
Account A
Deadlock can occur if lock ordering is inconsistent.
Interview Trick Questions
Q1: Can Deadlock Occur with a Single Lock?
❌ No.
Deadlock requires multiple resources and circular waiting.
Q2: Is synchronized Responsible for Deadlock?
❌ Not directly.
Incorrect lock usage causes deadlock.
Q3: What Is the Best Way to Avoid Deadlock?
✅ Acquire locks in a consistent order.
Example:
Lock1 → Lock2 → Lock3
for every thread.
Key Interview Points
- Deadlock occurs when two or more threads wait indefinitely for resources held by each other.
- Circular waiting is the primary cause.
- Four conditions are required: Mutual Exclusion, Hold and Wait, No Preemption, and Circular Wait.
- The most common prevention technique is consistent lock ordering.
tryLock()with timeout helps avoid indefinite waiting.- Deadlocks can be detected using
jstack, JVisualVM, and Java Mission Control. - Minimizing nested locks reduces deadlock risk.
One-Line Interview Answer
Deadlock is a situation where two or more threads wait indefinitely for resources held by each other, and it can be avoided by using consistent lock ordering, minimizing nested locks, and leveraging mechanisms like tryLock() with timeouts.
26) What is synchronization?
# What is Synchronization in Java?Definition
Synchronization is a mechanism in Java that controls access to shared resources by multiple threads.
It ensures that:
Only One Thread
Can Access Critical Code
At A Time
This prevents:
- Race Conditions
- Data Inconsistency
- Thread Interference
Why Do We Need Synchronization?
When multiple threads access and modify the same data simultaneously, the result may become unpredictable.
Example Without Synchronization
class Counter {
int count = 0;
public void increment() {
count++;
}
}
Multiple threads:
counter.increment();
Internally:
count++;
is actually:
1. Read count
2. Increment value
3. Write value back
These operations are not atomic.
Race Condition Example
Initial Value:
count = 0
Thread-1:
Read 0
Thread-2:
Read 0
Thread-1:
Increment → 1
Write → 1
Thread-2:
Increment → 1
Write → 1
Expected:
2
Actual:
1
This is called a:
Race Condition
What is a Critical Section?
A critical section is a piece of code that accesses shared resources.
Example:
count++;
Only one thread should execute it at a time.
Synchronization Solution
class Counter {
int count = 0;
public synchronized void increment() {
count++;
}
}
Now:
One Thread Enters
Others Wait
Result becomes consistent.
How Synchronization Works
Java uses:
Monitor Lock
or
Intrinsic Lock
Every object has an associated lock.
Example:
synchronized(this) {
}
A thread must acquire the lock before entering the synchronized block.
Types of Synchronization
Java supports:
- Synchronized Method
- Synchronized Block
- Static Synchronization
1. Synchronized Method
Entire method is locked.
public synchronized void increment() {
count++;
}
Equivalent to:
synchronized(this) {
count++;
}
Example
class Printer {
public synchronized void print() {
System.out.println(
Thread.currentThread().getName());
}
}
Only one thread can execute print() at a time.
2. Synchronized Block
Locks only a specific section.
Example
public void increment() {
synchronized(this) {
count++;
}
}
Why Preferred?
Bad:
public synchronized void process() {
// 100 lines of code
}
Entire method is locked.
Better:
public void process() {
// Non-critical code
synchronized(this) {
count++;
}
// Non-critical code
}
Only critical code is locked.
Improves performance.
3. Static Synchronization
Used for static members.
Locks the class object.
Example
public static synchronized void print() {
System.out.println("Printing");
}
Equivalent to:
synchronized(MyClass.class) {
}
Example: Synchronization in Action
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Test {
public static void main(String[] args)
throws Exception {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for(int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for(int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(
counter.getCount());
}
}
Output:
2000
Without synchronization, the result may be less than 2000.
Object Lock vs Class Lock
Object Lock
synchronized(this)
Locks a specific object instance.
Class Lock
synchronized(MyClass.class)
Locks the entire class.
Synchronization Visualization
Without Synchronization:
Thread-1 ──► Shared Data
Thread-2 ──► Shared Data
Thread-3 ──► Shared Data
Simultaneous Access
With Synchronization:
Thread-1 ──► Lock Acquired
│
▼
Shared Data
Thread-2 Waiting
Thread-3 Waiting
Advantages of Synchronization
Thread Safety
Prevents concurrent modifications.
Data Consistency
Ensures correct results.
Prevents Race Conditions
Shared data remains safe.
Memory Visibility
Changes made by one thread become visible to others.
Disadvantages of Synchronization
Performance Overhead
Lock acquisition and release take time.
Thread Contention
Threads may spend time waiting.
Deadlock Risk
Improper lock management can cause deadlocks.
Reduced Parallelism
Only one thread can enter the synchronized section.
Synchronization vs Volatile
| Feature | synchronized | volatile |
|---|---|---|
| Thread Safety | ✅ Yes | ❌ No |
| Mutual Exclusion | ✅ Yes | ❌ No |
| Visibility Guarantee | ✅ Yes | ✅ Yes |
| Atomic Operations | ✅ Yes | ❌ No |
| Locking Required | ✅ Yes | ❌ No |
Example
volatile boolean running = true;
Provides visibility but not synchronization.
volatile int count;
count++;
is still unsafe.
Modern Alternatives
Instead of heavy synchronization:
ReentrantLock
ReadWriteLock
StampedLock
AtomicInteger
ConcurrentHashMap
Example
AtomicInteger count =
new AtomicInteger();
count.incrementAndGet();
Thread-safe without explicit synchronization.
Interview Trick Questions
Q1: Can Multiple Threads Execute a Synchronized Method?
✅ Yes, if they are using different object instances.
Printer p1 = new Printer();
Printer p2 = new Printer();
Each object has its own lock.
Q2: Does Synchronization Improve Performance?
❌ No.
It improves correctness and thread safety, often at the cost of performance.
Q3: Does volatile Replace Synchronization?
❌ No.
volatile provides visibility but not atomicity.
Key Interview Points
- Synchronization ensures that only one thread accesses a critical section at a time.
- It prevents race conditions and data inconsistency.
- Implemented using intrinsic locks (monitors).
- Can be applied at method, block, or static level.
- Every Java object has an associated monitor lock.
- Synchronization provides both mutual exclusion and memory visibility.
- Excessive synchronization can reduce performance and cause deadlocks.
One-Line Interview Answer
Synchronization is a Java mechanism that controls concurrent access to shared resources by allowing only one thread at a time to execute a critical section, thereby ensuring thread safety and data consistency.
27) Shallow Copy vs Deep Copy in Java
One of the most frequently asked Java interview questions is:
What is the difference between Shallow Copy and Deep Copy?
The answer lies in how object references are copied.
What is Object Copying?
Object copying means creating a new object based on an existing object.
Example:
Employee emp1 = new Employee();
Create another object with the same data:
Employee emp2 = ?;
There are two ways:
- Shallow Copy
- Deep Copy
Shallow Copy
A Shallow Copy creates a new object, but copies the references of nested objects instead of creating new nested objects.
Parent Object → New Object
Nested Objects → Shared References
Example
class Address {
String city;
Address(String city) {
this.city = city;
}
}
class Employee implements Cloneable {
int id;
Address address;
Employee(int id, Address address) {
this.id = id;
this.address = address;
}
@Override
protected Object clone()
throws CloneNotSupportedException {
return super.clone();
}
}
Usage
Address address =
new Address("Hyderabad");
Employee emp1 =
new Employee(101, address);
Employee emp2 =
(Employee) emp1.clone();
Memory:
emp1 ─────┐
│
▼
Address
"Hyderabad"
▲
│
emp2 ─────┘
Both objects share the same Address object.
Problem with Shallow Copy
emp2.address.city = "Bangalore";
Now:
System.out.println(emp1.address.city);
Output:
Bangalore
Why?
Because both employees point to the same Address object.
Shallow Copy Visualization
Employee-1
│
▼
Address
▲
│
Employee-2
Shared nested object.
Deep Copy
A Deep Copy creates a new object and also creates copies of all nested objects.
Parent Object → New Object
Nested Objects → New Copies
No references are shared.
Example
class Address {
String city;
Address(String city) {
this.city = city;
}
}
class Employee {
int id;
Address address;
Employee(int id, Address address) {
this.id = id;
this.address = address;
}
public Employee(Employee other) {
this.id = other.id;
this.address =
new Address(other.address.city);
}
}
Usage
Employee emp1 =
new Employee(
101,
new Address("Hyderabad"));
Employee emp2 =
new Employee(emp1);
Memory:
emp1 ──► Address("Hyderabad")
emp2 ──► Address("Hyderabad")
Different Address objects.
Modifying Deep Copy
emp2.address.city = "Bangalore";
Now:
System.out.println(emp1.address.city);
Output:
Hyderabad
Reason:
Separate Address Objects
Deep Copy Visualization
emp1 ──► Address("Hyderabad")
emp2 ──► Address("Hyderabad")
No shared references.
Comparison Example
Shallow Copy
Employee emp2 =
(Employee) emp1.clone();
Address Object Shared
Deep Copy
Employee emp2 =
new Employee(emp1);
Address Object Copied
Using Cloneable for Deep Copy
@Override
protected Object clone()
throws CloneNotSupportedException {
Employee cloned =
(Employee) super.clone();
cloned.address =
new Address(this.address.city);
return cloned;
}
Now nested objects are copied too.
Shallow Copy vs Deep Copy
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Parent Object Copied | ✅ Yes | ✅ Yes |
| Nested Objects Copied | ❌ No | ✅ Yes |
| References Shared | ✅ Yes | ❌ No |
| Memory Usage | Low | Higher |
| Performance | Faster | Slower |
| Side Effects | Possible | No |
| Independence | Low | High |
Real-World Example
Shallow Copy
Employee
│
▼
Address
Two employees share one address.
Changing one affects the other.
Deep Copy
Employee-1 → Address-1
Employee-2 → Address-2
Changes remain independent.
Common Ways to Create Deep Copies
Copy Constructor
public Employee(Employee other) {
}
Most common approach.
Clone Method
@Override
protected Object clone()
Requires manual handling of nested objects.
Serialization
ObjectOutputStream
ObjectInputStream
Can create a complete deep copy of an object graph.
Serialization-Based Deep Copy Example
Employee copy =
SerializationUtils.clone(employee);
Useful for complex object structures.
Interview Trick Questions
Q1: Does Object.clone() Perform Deep Copy?
❌ No.
Default implementation performs:
Shallow Copy
only.
Q2: Which Copy Type Is Faster?
✅ Shallow Copy
Because it copies references only.
Q3: Which Copy Type Is Safer?
✅ Deep Copy
Because objects are completely independent.
Q4: Does String Need Deep Copy?
❌ Usually No.
String
is immutable.
Sharing references is safe.
Common Interview Scenario
Address address =
new Address("Delhi");
Employee e1 =
new Employee(101, address);
Employee e2 =
(Employee) e1.clone();
e2.address.city = "Mumbai";
Question:
What happens to e1?
Answer:
If clone() is shallow,
e1.address.city becomes Mumbai.
Key Interview Points
- Shallow copy creates a new object but shares references of nested objects.
- Deep copy creates a new object and copies all nested objects.
- Default
Object.clone()performs shallow copy. - Deep copy prevents unintended side effects.
- Copy constructors are a common way to implement deep copy.
- Deep copy consumes more memory but provides object independence.
- Immutable objects like String do not require deep copying.
One-Line Interview Answer
A shallow copy creates a new object while sharing references to nested objects, whereas a deep copy creates a completely independent copy by duplicating both the object and all objects it references.
28) What Are Annotations in Java? How to Create a Custom Annotation?
What Are Annotations?
Annotations are metadata that provide additional information about classes, methods, variables, parameters, or packages.
They do not directly affect program execution but are used by:
- Compiler
- JVM
- Frameworks
- Tools
to perform special processing.
Simple Definition
Annotation = Metadata About Code
Example:
@Override
public String toString() {
return "Employee";
}
Here:
@Override
is an annotation.
Why Are Annotations Used?
Annotations help:
- Reduce boilerplate code
- Configure applications
- Perform compile-time checks
- Enable runtime processing
- Support dependency injection
- Simplify framework development
Common Built-in Annotations
1. @Override
Indicates a method overrides a parent method.
class Animal {
void sound() {
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
Benefit:
Compiler verifies method overriding.
2. @Deprecated
Marks an element as obsolete.
@Deprecated
public void oldMethod() {
}
Usage:
oldMethod();
Compiler Warning:
Uses deprecated API
3. @SuppressWarnings
Suppresses compiler warnings.
@SuppressWarnings("unchecked")
List list = new ArrayList();
4. @FunctionalInterface
Indicates an interface contains exactly one abstract method.
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
Framework Annotations
Popular frameworks heavily use annotations.
Example:
@SpringBootApplication
@RestController
@Service
@Autowired
These annotations help frameworks generate behavior automatically.
Annotation Syntax
@AnnotationName
Example:
@Override
Annotation with Values
@author(name = "Amritanka")
Types of Annotations
Java supports:
- Marker Annotation
- Single Value Annotation
- Multi Value Annotation
1. Marker Annotation
Contains no members.
Example:
@Override
Custom Example:
@Important
2. Single Value Annotation
Contains one value.
@author(name = "John")
3. Multi Value Annotation
Contains multiple attributes.
@Employee(
id = 101,
name = "John"
)
How to Create a Custom Annotation?
Use:
@interface
keyword.
Example 1: Simple Marker Annotation
@interface Important {
}
Usage:
@Important
class Employee {
}
Example 2: Annotation with Fields
@interface Author {
String name();
}
Usage:
@Author(name = "Amritanka")
class Employee {
}
Example 3: Multiple Attributes
@interface EmployeeInfo {
int id();
String name();
String department();
}
Usage:
@EmployeeInfo(
id = 101,
name = "John",
department = "IT"
)
class Employee {
}
Annotation Members
Annotation members look like methods.
Example:
@interface Author {
String name();
int version();
}
Usage:
@Author(
name = "John",
version = 1
)
Default Values
You can provide default values.
@interface Author {
String name();
int version() default 1;
}
Usage:
@Author(name = "John")
Output:
version = 1
Meta Annotations
Meta annotations define how custom annotations behave.
Most important:
- @Target
- @Retention
- @Documented
- @Inherited
1. @Target
Specifies where the annotation can be applied.
Example:
@Target(ElementType.METHOD)
Annotation can be used only on methods.
Common Targets
TYPE
METHOD
FIELD
PARAMETER
CONSTRUCTOR
PACKAGE
Example:
@Target(ElementType.TYPE)
@MyAnnotation
class Employee {
}
2. @Retention
Specifies how long the annotation is retained.
SOURCE
@Retention(RetentionPolicy.SOURCE)
Available only during compilation.
Example:
@Override
CLASS
@Retention(RetentionPolicy.CLASS)
Stored in bytecode.
Not available at runtime.
RUNTIME
@Retention(RetentionPolicy.RUNTIME)
Available through Reflection.
Most custom framework annotations use this.
Example Custom Annotation
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Developer {
String name();
int experience();
}
Usage:
@Developer(
name = "Amritanka",
experience = 5
)
class Employee {
}
Reading Annotation Using Reflection
Class<Employee> clazz =
Employee.class;
Developer developer =
clazz.getAnnotation(
Developer.class);
System.out.println(
developer.name());
System.out.println(
developer.experience());
Output:
Amritanka
5
Complete Example
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EmployeeInfo {
int id();
String name();
}
@EmployeeInfo(
id = 101,
name = "John"
)
class Employee {
}
public class Test {
public static void main(String[] args) {
Class<Employee> clazz =
Employee.class;
EmployeeInfo info =
clazz.getAnnotation(
EmployeeInfo.class);
System.out.println(info.id());
System.out.println(info.name());
}
}
Output:
101
John
Real-World Example (Spring Boot)
@RestController
class UserController {
}
Spring scans this annotation at runtime and automatically creates a REST endpoint.
Similarly:
@Service
registers a service bean.
@Autowired
performs dependency injection.
Annotation Processing Flow
Source Code
│
▼
Annotation
│
▼
Compiler / Framework
│
▼
Additional Behavior
Example:
@Service
↓
Spring Creates Bean Automatically
Annotations vs Comments
| Feature | Annotation | Comment |
|---|---|---|
| Available at Runtime | ✅ Yes | ❌ No |
| Used by Compiler | ✅ Yes | ❌ No |
| Used by Frameworks | ✅ Yes | ❌ No |
| Affects Behavior | ✅ Indirectly | ❌ No |
| Metadata Support | ✅ Yes | ❌ No |
Interview Trick Questions
Q1: Can Annotations Change Program Logic?
❌ Not directly.
They provide metadata that frameworks or tools can use.
Q2: Which Retention Policy Is Needed for Reflection?
✅
RetentionPolicy.RUNTIME
Q3: How Do You Create a Custom Annotation?
Using:
@interface
keyword.
Q4: Can Annotation Members Have Parameters?
❌ No.
Members can only define return types.
Example:
String name();
Valid.
String name(String x);
Invalid.
Key Interview Points
- Annotations provide metadata about code.
- Common built-in annotations are
@Override,@Deprecated,@SuppressWarnings, and@FunctionalInterface. - Custom annotations are created using the
@interfacekeyword. @Targetspecifies where an annotation can be used.@Retentionspecifies how long an annotation is retained.RetentionPolicy.RUNTIMEis required for reflection-based processing.- Frameworks like Spring heavily rely on annotations for configuration and dependency injection.
- Annotations reduce boilerplate code and improve readability.
One-Line Interview Answer
Annotations are metadata used to provide additional information to the compiler, JVM, or frameworks, and custom annotations can be created using the @interface keyword along with meta-annotations such as @Target and @Retention.
29) What is the Java Memory Model (JMM)?
Definition
The Java Memory Model (JMM) is a specification that defines how threads interact with memory in Java.
It describes:
- How variables are stored in memory
- How threads read and write shared variables
- How changes made by one thread become visible to other threads
- Rules for synchronization and concurrency
Simple Definition
JMM defines the rules for
communication between threads
through shared memory.
Without JMM, multi-threaded programs could behave unpredictably across different CPUs, operating systems, and JVM implementations.
Why Do We Need JMM?
Modern computers have:
- Multiple CPUs/Cores
- CPU Caches
- Main Memory (RAM)
Each thread may work with its own cached copy of variables.
Example:
int counter = 0;
Thread-1 updates:
counter = 10;
Thread-2 may still see:
counter = 0;
because the updated value might remain in Thread-1's cache.
JMM defines rules to ensure correct visibility.
Memory Structure in JMM
Main Memory
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Thread-1 Thread-2 Thread-3
Working Working Working
Memory Memory Memory
Main Memory
Stores:
Instance Variables
Static Variables
Array Elements
Shared by all threads.
Working Memory
Each thread has its own working memory.
Contains:
Local Copies of Variables
CPU Cache Data
Threads operate on their local copies first.
Example of Visibility Problem
class SharedData {
boolean flag = false;
}
Thread-1:
flag = true;
Thread-2:
while(!flag) {
}
Expected:
Loop Stops
Possible Reality:
Loop Never Stops
because Thread-2 may not see the updated value.
This is called:
Visibility Problem
Key Responsibilities of JMM
JMM mainly addresses:
- Visibility
- Atomicity
- Ordering
1. Visibility
Problem
One thread updates a variable.
Other threads may not immediately see the update.
Example
boolean running = true;
Thread-1:
running = false;
Thread-2:
while(running) {
}
Without synchronization:
Thread-2 may never see false.
Solution
Use:
volatile
volatile boolean running = true;
Now updates become visible to all threads.
2. Atomicity
Atomic means:
Complete Operation
Or
No Operation
Example
count++;
Looks like one operation but actually:
1. Read
2. Increment
3. Write
Multiple threads can interfere.
Race Condition
Thread-1:
Read 0
Thread-2:
Read 0
Thread-1:
Write 1
Thread-2:
Write 1
Expected:
2
Actual:
1
Solution
Use:
synchronized
or
AtomicInteger
3. Ordering
Compilers and CPUs may reorder instructions for performance.
Example:
int a = 10;
boolean ready = true;
May internally execute as:
ready = true;
a = 10;
This is called:
Instruction Reordering
Why Is Reordering Dangerous?
Thread-1:
a = 10;
ready = true;
Thread-2:
if(ready) {
System.out.println(a);
}
Expected:
10
Possible output:
0
due to instruction reordering.
Happens-Before Relationship
The most important JMM concept.
Definition
A Happens-Before relationship guarantees that:
Changes made by Thread-A
Become Visible To
Thread-B
Important Happens-Before Rules
Rule 1: Program Order Rule
Within a thread:
a = 10;
b = 20;
Execution order is guaranteed.
Rule 2: Monitor Lock Rule
Unlock happens-before subsequent lock.
Example:
synchronized(lock) {
}
Changes become visible to the next thread acquiring the same lock.
Rule 3: Volatile Variable Rule
Write to volatile:
flag = true;
happens-before
if(flag)
read in another thread.
Rule 4: Thread Start Rule
thread.start();
happens-before actions inside the thread.
Rule 5: Thread Join Rule
thread.join();
guarantees visibility of completed thread actions.
JMM and volatile
Example:
volatile boolean running = true;
Benefits:
Visibility Guaranteed
Instruction Reordering Prevented
What Volatile Does NOT Provide
volatile int count;
count++;
Still unsafe.
Because:
Atomicity Not Guaranteed
JMM and synchronized
Example:
synchronized(lock) {
count++;
}
Provides:
Visibility
Atomicity
Ordering
All three.
JMM and Atomic Classes
AtomicInteger count =
new AtomicInteger();
count.incrementAndGet();
Provides thread-safe atomic operations without explicit locking.
JMM Visualization
Without Synchronization:
Thread-1 Cache
│
▼
counter = 10
Thread-2 Cache
│
▼
counter = 0
Inconsistent view.
With Synchronization / Volatile:
Thread-1
│
▼
Main Memory
▲
│
Thread-2
Both threads see the latest value.
Common Concurrency Problems Solved by JMM
Visibility Problem
Thread Cannot See Updates
Solved by:
volatile
synchronized
Race Condition
Multiple Threads Modify Data
Solved by:
synchronized
Atomic Classes
Locks
Instruction Reordering
Unexpected Execution Order
Solved by:
volatile
synchronized
Real-World Example
class Server {
private volatile boolean running = true;
public void stop() {
running = false;
}
}
Without volatile:
Server Thread May Never Stop
With volatile:
All Threads See Latest Value
JMM vs JVM Memory Areas
Many developers confuse these concepts.
JMM
Defines:
Thread Communication Rules
Visibility
Atomicity
Ordering
JVM Memory Areas
Defines:
Heap
Stack
Method Area
Metaspace
Comparison
| Feature | JMM | JVM Memory Areas |
|---|---|---|
| Purpose | Concurrency Rules | Memory Structure |
| Focus | Threads | Memory Layout |
| Deals With | Visibility & Synchronization | Heap, Stack, Metaspace |
| Part Of | Concurrency Model | JVM Architecture |
Interview Trick Questions
Q1: Is JMM a Physical Memory Structure?
❌ No.
It is a specification that defines memory interaction rules.
Q2: Does volatile Guarantee Atomicity?
❌ No.
count++;
remains unsafe even if count is volatile.
Q3: Which Keyword Provides Visibility and Atomicity?
✅
synchronized
Q4: What Are the Three Main JMM Guarantees?
Visibility
Atomicity
Ordering
Key Interview Points
- JMM defines how threads interact through memory.
- It ensures consistent behavior across JVMs and hardware architectures.
- JMM addresses three major concerns: Visibility, Atomicity, and Ordering.
- Each thread has its own working memory and accesses shared data through main memory.
volatileguarantees visibility and prevents instruction reordering.synchronizedguarantees visibility, atomicity, and ordering.- Happens-Before is the core concept used by JMM to guarantee memory consistency.
- JMM is different from JVM memory areas such as Heap and Stack.
One-Line Interview Answer
The Java Memory Model (JMM) is the specification that defines how threads interact with shared memory, ensuring visibility, atomicity, and ordering of operations so that multithreaded Java programs behave consistently across different JVMs and hardware platforms.
30) What is Reflection in Java?
Definition
Reflection is a powerful feature in Java that allows a program to inspect, analyze, and manipulate its own structure at runtime.
Using Reflection, we can:
- Inspect classes
- Access fields and methods
- Create objects dynamically
- Invoke methods dynamically
- Access private members
- Read annotations
without knowing the details at compile time.
Simple Definition
Reflection allows a Java program
to examine and modify classes,
methods, fields, and constructors
at runtime.
Why Do We Need Reflection?
Normally, Java code is accessed directly:
Employee emp = new Employee();
emp.display();
But sometimes:
- Class name is known only at runtime
- Methods need to be called dynamically
- Frameworks need to create objects automatically
Reflection solves these problems.
Reflection API
Java provides Reflection through:
java.lang.reflect
Important classes:
Class
Method
Field
Constructor
Modifier
Getting Class Information
Consider:
class Employee {
private int id;
public void display() {
System.out.println("Employee");
}
}
Method 1: Using .class
Class<Employee> clazz =
Employee.class;
Method 2: Using Object
Employee emp = new Employee();
Class<?> clazz =
emp.getClass();
Method 3: Using Class Name
Class<?> clazz =
Class.forName("Employee");
Useful when the class name is determined at runtime.
Getting Class Details
Class<?> clazz =
Employee.class;
System.out.println(
clazz.getName());
Output:
Employee
Getting Methods
Method[] methods =
clazz.getDeclaredMethods();
for(Method method : methods) {
System.out.println(
method.getName());
}
Output:
display
Getting Fields
Field[] fields =
clazz.getDeclaredFields();
for(Field field : fields) {
System.out.println(
field.getName());
}
Output:
id
Getting Constructors
Constructor<?>[] constructors =
clazz.getDeclaredConstructors();
Creating Objects Dynamically
Normal way:
Employee emp =
new Employee();
Reflection way:
Class<?> clazz =
Employee.class;
Object obj =
clazz.getDeclaredConstructor()
.newInstance();
Object is created at runtime.
Invoking Methods Dynamically
Class:
class Employee {
public void display() {
System.out.println(
"Hello Reflection");
}
}
Reflection:
Method method =
clazz.getMethod(
"display");
method.invoke(obj);
Output:
Hello Reflection
Accessing Private Fields
Class:
class Employee {
private String name =
"John";
}
Reflection:
Field field =
clazz.getDeclaredField(
"name");
field.setAccessible(true);
System.out.println(
field.get(obj));
Output:
John
Modifying Private Fields
field.setAccessible(true);
field.set(obj, "Amritanka");
Now:
System.out.println(
field.get(obj));
Output:
Amritanka
Accessing Private Methods
Method method =
clazz.getDeclaredMethod(
"privateMethod");
method.setAccessible(true);
method.invoke(obj);
Even private methods can be invoked using Reflection.
Reading Annotations
Custom Annotation:
@Retention(
RetentionPolicy.RUNTIME)
@interface Author {
String name();
}
Usage:
@Author(name = "Amritanka")
class Employee {
}
Reading annotation:
Author author =
clazz.getAnnotation(
Author.class);
System.out.println(
author.name());
Output:
Amritanka
Complete Example
class Employee {
private String name =
"John";
public void display() {
System.out.println(
"Employee Method");
}
}
public class Test {
public static void main(String[] args)
throws Exception {
Class<?> clazz =
Employee.class;
Object obj =
clazz.getDeclaredConstructor()
.newInstance();
Method method =
clazz.getMethod(
"display");
method.invoke(obj);
Field field =
clazz.getDeclaredField(
"name");
field.setAccessible(true);
System.out.println(
field.get(obj));
}
}
Output:
Employee Method
John
How Reflection Works
Class
│
▼
Reflection API
│
├── Fields
├── Methods
├── Constructors
└── Annotations
│
▼
Runtime Access & Manipulation
Real-World Uses of Reflection
Spring Framework
@Service
@RestController
@Autowired
Spring scans classes and creates beans using Reflection.
Hibernate
@Entity
Uses Reflection to map Java objects to database tables.
JUnit
@Test
Discovers and executes test methods dynamically.
Jackson
ObjectMapper
Uses Reflection to convert:
Java Object ↔ JSON
Advantages of Reflection
Dynamic Object Creation
Classes can be instantiated at runtime.
Dynamic Method Invocation
Methods can be called without compile-time knowledge.
Framework Development
Essential for frameworks like Spring and Hibernate.
Annotation Processing
Enables metadata-driven programming.
Disadvantages of Reflection
Performance Overhead
Slower than direct method calls.
Breaks Encapsulation
Can access private fields and methods.
Runtime Errors
Errors appear at runtime rather than compile time.
Example:
clazz.getMethod("invalidMethod");
Throws:
NoSuchMethodException
Security Concerns
Can expose internal implementation details.
Reflection vs Normal Access
| Feature | Normal Access | Reflection |
|---|---|---|
| Compile-Time Safety | ✅ Yes | ❌ No |
| Performance | Fast | Slower |
| Access Private Members | ❌ No | ✅ Yes |
| Dynamic Behavior | Limited | High |
| Framework Usage | Limited | Extensive |
Interview Trick Questions
Q1: Which Class Is the Entry Point of Reflection?
java.lang.Class
Q2: Can Reflection Access Private Members?
✅ Yes
Using:
setAccessible(true);
Q3: Is Reflection Faster Than Direct Method Calls?
❌ No
Reflection introduces runtime overhead.
Q4: Which Package Contains Reflection APIs?
java.lang.reflect
Key Interview Points
- Reflection allows runtime inspection and manipulation of Java classes.
- It is implemented using the
java.lang.reflectpackage. - Reflection can access fields, methods, constructors, and annotations.
- Objects can be created dynamically using Reflection.
- Private members can be accessed using
setAccessible(true). - Reflection is heavily used by Spring, Hibernate, JUnit, Jackson, and Mockito.
- Reflection provides flexibility but sacrifices some performance and compile-time safety.
One-Line Interview Answer
Reflection is a Java feature that enables a program to inspect and manipulate classes, methods, fields, constructors, and annotations at runtime, making it a core technology behind frameworks like Spring and Hibernate.
31) What Does `join()` Do in Java?
Definition
The join() method is used to make one thread wait for the completion of another thread.
In simple words:
join() tells the current thread
to wait until the target thread
finishes execution.
Why Do We Need join()?
By default, threads execute independently.
Example:
Thread t1 = new Thread(task);
t1.start();
System.out.println("Main Thread Finished");
Possible Output:
Main Thread Finished
Task Executed
The main thread does not wait for t1.
Sometimes we need:
Complete Child Thread First
Then Continue Main Thread
This is where join() is useful.
Simple Example
class MyTask extends Thread {
@Override
public void run() {
System.out.println(
"Task Started");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"Task Completed");
}
}
public class Test {
public static void main(String[] args)
throws Exception {
MyTask thread =
new MyTask();
thread.start();
thread.join();
System.out.println(
"Main Thread Completed");
}
}
Output:
Task Started
Task Completed
Main Thread Completed
What Happens Internally?
Main Thread
│
▼
thread.start()
│
▼
thread.join()
│
▼
WAIT
│
▼
Thread Finishes
│
▼
Main Thread Resumes
Without join()
thread.start();
System.out.println(
"Main Thread Completed");
Possible Output:
Main Thread Completed
Task Started
Task Completed
Order is unpredictable.
With join()
thread.start();
thread.join();
System.out.println(
"Main Thread Completed");
Output:
Task Started
Task Completed
Main Thread Completed
Guaranteed order.
Multiple Threads Example
Thread t1 = new Thread(() -> {
System.out.println("T1");
});
Thread t2 = new Thread(() -> {
System.out.println("T2");
});
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println("Done");
Output:
T1
T2
Done
join() with Timeout
Java provides:
join(long millis)
The current thread waits only for a specified time.
Example
thread.join(3000);
Meaning:
Wait Maximum 3 Seconds
If the thread completes earlier:
Continue Immediately
If not:
Resume After 3 Seconds
Example
thread.start();
thread.join(1000);
System.out.println(
"Main Continues");
The main thread waits for at most 1 second.
Overloaded Versions of join()
Wait Until Thread Completes
join()
Wait for Specific Milliseconds
join(long millis)
Wait for Milliseconds and Nanoseconds
join(long millis,
int nanos)
Real-World Example
Suppose:
Thread-1
Downloads File
Main Thread:
Process File
You must wait until download finishes.
downloadThread.start();
downloadThread.join();
processFile();
Without join():
Processing may start
before download completes.
Common Use Cases
Task Dependencies
Task-2 depends on Task-1
Data Processing Pipelines
Read File
↓
Process File
↓
Generate Report
Parallel Computation
Start multiple threads and wait for all results.
Application Shutdown
Wait for worker threads to finish.
Example: Waiting for Multiple Threads
Thread t1 =
new Thread(() -> {
System.out.println("T1 Done");
});
Thread t2 =
new Thread(() -> {
System.out.println("T2 Done");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(
"All Threads Finished");
Output:
T1 Done
T2 Done
All Threads Finished
Relationship with Thread States
Before join():
Main Thread → RUNNING
Worker Thread → RUNNING
During join():
Main Thread → WAITING
Worker Thread → RUNNING
After worker finishes:
Main Thread → RUNNABLE
join() vs sleep()
| Feature | join() | sleep() |
|---|---|---|
| Purpose | Wait for another thread | Pause current thread |
| Depends on Other Thread | ✅ Yes | ❌ No |
| Releases CPU | ✅ Yes | ✅ Yes |
| Wait Time | Until thread finishes | Fixed duration |
| Thread Coordination | ✅ Yes | ❌ No |
Example of sleep()
Thread.sleep(2000);
Meaning:
Pause Current Thread
For 2 Seconds
Example of join()
thread.join();
Meaning:
Wait Until Thread Completes
Internal Working
Internally, join() uses:
wait()
notifyAll()
The calling thread enters a waiting state until the target thread terminates.
Interview Trick Questions
Q1: Which Thread Waits in join()?
Example:
t1.join();
The thread that calls join() waits.
Typically:
Main Thread Waits
Q2: Does join() Stop the Target Thread?
❌ No.
It only pauses the calling thread.
Q3: Can join() Throw an Exception?
✅ Yes.
InterruptedException
must be handled.
Example:
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Q4: Does join() Guarantee Execution Order?
✅ Yes.
It ensures the calling thread waits until the target thread completes.
Key Interview Points
join()makes the current thread wait for another thread to finish.- It is commonly used for thread coordination.
- The calling thread enters the WAITING state.
join()does not stop or pause the target thread.- Overloaded versions support timeouts.
- Internally,
join()useswait()andnotifyAll(). - Frequently used when one task depends on the completion of another.
One-Line Interview Answer
The join() method is used to make the current thread wait until another thread completes its execution, ensuring proper coordination and execution order between threads.
32) Benefits of Java Stream API
What is Stream API?
The Java Stream API, introduced in Java 8, provides a functional approach to processing collections of data.
It allows developers to perform operations such as:
- Filtering
- Mapping
- Sorting
- Grouping
- Aggregation
in a concise and declarative manner.
Example:
List<String> names =
Arrays.asList(
"John",
"Alex",
"David");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
Output:
Alex
Why Was Stream API Introduced?
Before Java 8, collection processing required:
for loops
nested loops
manual filtering
temporary collections
Example:
List<String> result =
new ArrayList<>();
for(String name : names) {
if(name.startsWith("A")) {
result.add(name);
}
}
The Stream API makes the same operation simpler and more readable.
Major Benefits of Stream API
1. Less Boilerplate Code
Without Streams:
List<Integer> evenNumbers =
new ArrayList<>();
for(Integer number : numbers) {
if(number % 2 == 0) {
evenNumbers.add(number);
}
}
With Streams:
List<Integer> evenNumbers =
numbers.stream()
.filter(n -> n % 2 == 0)
.toList();
Benefits:
Less Code
Better Readability
2. Improved Readability
Streams describe:
WHAT to do
instead of:
HOW to do it
Example:
employees.stream()
.filter(Employee::isActive)
.map(Employee::getName)
.toList();
Easy to understand.
3. Functional Programming Support
Streams work seamlessly with:
Lambda Expressions
Method References
Functional Interfaces
Example:
names.stream()
.forEach(System.out::println);
Promotes a functional programming style.
4. Internal Iteration
Traditional Collection:
for(Employee e : employees) {
}
Developer controls iteration.
Stream:
employees.stream()
.forEach(System.out::println);
JVM controls iteration.
Benefits:
Cleaner Code
Better Optimization
5. Easy Data Transformation
Using map():
List<String> names =
employees.stream()
.map(Employee::getName)
.toList();
Transforms:
Employee → String
with minimal code.
6. Powerful Filtering
Using filter():
employees.stream()
.filter(emp ->
emp.getSalary() > 50000)
.toList();
Easy to express business rules.
7. Efficient Aggregation
Find total salary:
double totalSalary =
employees.stream()
.mapToDouble(
Employee::getSalary)
.sum();
Without loops.
8. Built-in Sorting
employees.stream()
.sorted(
Comparator.comparing(
Employee::getName))
.toList();
Readable and concise.
9. Easy Grouping
Using Collectors.groupingBy():
Map<String, List<Employee>> result =
employees.stream()
.collect(
Collectors.groupingBy(
Employee::getDepartment));
Output:
IT → Employees
HR → Employees
Finance → Employees
Very useful in reporting applications.
10. Easy Partitioning
Map<Boolean, List<Employee>> result =
employees.stream()
.collect(
Collectors.partitioningBy(
emp ->
emp.getSalary() > 50000));
Output:
true → High Salary Employees
false → Others
11. Supports Parallel Processing
One of the biggest advantages.
Traditional loop:
for(Employee e : employees) {
}
Runs sequentially.
Using Parallel Stream:
employees.parallelStream()
.forEach(System.out::println);
Uses multiple CPU cores automatically.
Benefits:
Better Performance
For Large Data Sets
Example
long count =
employees.parallelStream()
.filter(
Employee::isActive)
.count();
Multiple threads process data simultaneously.
12. Lazy Evaluation
Intermediate operations are not executed immediately.
Example:
Stream<Integer> stream =
numbers.stream()
.filter(n -> n > 10);
Nothing executes yet.
Execution starts only when a terminal operation is called.
Example:
stream.count();
Benefits:
Reduced Processing
Better Performance
13. Pipeline Processing
Streams allow chaining operations.
Example:
List<String> result =
employees.stream()
.filter(Employee::isActive)
.sorted(
Comparator.comparing(
Employee::getName))
.map(Employee::getName)
.toList();
Processing pipeline:
Filter
↓
Sort
↓
Map
↓
Collect
Readable and maintainable.
14. No Need for Temporary Collections
Without Streams:
List<Employee> active =
new ArrayList<>();
List<String> names =
new ArrayList<>();
Multiple temporary collections are created.
With Streams:
List<String> names =
employees.stream()
.filter(Employee::isActive)
.map(Employee::getName)
.toList();
Cleaner implementation.
15. Rich Built-in Operations
Common operations:
filter()
map()
flatMap()
sorted()
distinct()
limit()
skip()
count()
findFirst()
findAny()
reduce()
collect()
No need to write custom loops repeatedly.
Example of reduce()
Find sum:
int sum =
numbers.stream()
.reduce(
0,
Integer::sum);
Output:
Total Sum
Real-World Example
Find active employee names sorted alphabetically.
Without Streams:
List<String> names =
new ArrayList<>();
for(Employee emp : employees) {
if(emp.isActive()) {
names.add(
emp.getName());
}
}
Collections.sort(names);
With Streams:
List<String> names =
employees.stream()
.filter(Employee::isActive)
.map(Employee::getName)
.sorted()
.toList();
Much cleaner and easier to maintain.
Stream API vs Traditional Loops
| Feature | Traditional Loop | Stream API |
|---|---|---|
| Readability | Moderate | High |
| Boilerplate Code | More | Less |
| Functional Style | ❌ No | ✅ Yes |
| Parallel Processing | Manual | Built-in |
| Lazy Evaluation | ❌ No | ✅ Yes |
| Data Transformation | Verbose | Simple |
| Maintainability | Moderate | High |
When to Use Streams?
Use Streams when:
- Processing collections
- Filtering data
- Transforming objects
- Aggregating results
- Grouping records
- Sorting collections
- Parallel data processing
When NOT to Use Streams?
Avoid Streams when:
- Complex debugging is required
- Performance-critical code with tiny collections
- Heavy mutation of shared state
- Simple loops are more readable
Example:
for(int i = 0; i < 5; i++) {
System.out.println(i);
}
A loop is often clearer.
Interview Trick Questions
Q1: Does Stream Store Data?
❌ No.
A Stream processes data from a source such as:
List
Set
Array
File
Q2: Can a Stream Be Reused?
❌ No.
After a terminal operation:
stream.count();
the stream is closed.
Q3: What Is the Biggest Advantage of Streams?
Declarative Programming
Focus on:
What to do
instead of:
How to do it
Q4: Are Streams Faster Than Loops?
❌ Not always.
For small collections:
Loops may be faster.
For large datasets:
Parallel Streams can improve performance.
Key Interview Points
- Stream API was introduced in Java 8.
- It provides a functional and declarative approach to data processing.
- Reduces boilerplate code and improves readability.
- Supports filtering, mapping, sorting, grouping, and aggregation.
- Uses lazy evaluation for better performance.
- Supports parallel processing through
parallelStream(). - Enables pipeline-based data processing.
- Streams do not store data; they process data from a source.
- A stream cannot be reused after a terminal operation.
One-Line Interview Answer
The Java Stream API provides a functional, declarative, and efficient way to process collections by supporting operations such as filtering, mapping, sorting, grouping, aggregation, and parallel processing while reducing boilerplate code and improving readability.
33) What is Try-With-Resources in Java?
Definition
Try-With-Resources is a feature introduced in Java 7 that automatically closes resources after they are used.
Resources include:
- File streams
- Database connections
- Network sockets
- Readers/Writers
- Input/Output streams
It helps prevent resource leaks and reduces boilerplate code.
Simple Definition
Try-With-Resources automatically
closes resources when execution
exits the try block.
Why Was It Introduced?
Before Java 7, resources had to be closed manually.
Example:
BufferedReader reader = null;
try {
reader = new BufferedReader(
new FileReader("data.txt"));
System.out.println(
reader.readLine());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Problems:
- Verbose code
- Easy to forget
close() - Resource leaks possible
- Nested try-catch blocks
Try-With-Resources Solution
try (BufferedReader reader =
new BufferedReader(
new FileReader("data.txt"))) {
System.out.println(
reader.readLine());
}
catch (IOException e) {
e.printStackTrace();
}
The resource is closed automatically.
Syntax
try (Resource resource = new Resource()) {
// Use resource
} catch (Exception e) {
// Handle exception
}
Example: Reading a File
import java.io.*;
public class Test {
public static void main(String[] args) {
try (BufferedReader reader =
new BufferedReader(
new FileReader("test.txt"))) {
String line =
reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Resource closes automatically after execution.
How Does It Work?
Java internally converts:
try (BufferedReader reader = ...)
into logic similar to:
BufferedReader reader = ...;
try {
// Use resource
} finally {
if (reader != null) {
reader.close();
}
}
Which Resources Can Be Used?
A resource must implement:
AutoCloseable
or
Closeable
interface.
AutoCloseable Interface
public interface AutoCloseable {
void close() throws Exception;
}
Any class implementing this interface can be used inside Try-With-Resources.
Common Resources
FileInputStream
FileOutputStream
BufferedReader
BufferedWriter
Scanner
Connection
Statement
ResultSet
Socket
Example with Multiple Resources
try (
FileInputStream input =
new FileInputStream("input.txt");
FileOutputStream output =
new FileOutputStream("output.txt")
) {
// Process files
}
catch (IOException e) {
e.printStackTrace();
}
Resources are closed automatically.
Resource Closing Order
Resources are closed in reverse order of creation.
Example:
try (
ResourceA a = new ResourceA();
ResourceB b = new ResourceB();
)
Closing order:
b.close()
a.close()
Example
class ResourceA
implements AutoCloseable {
@Override
public void close() {
System.out.println("A Closed");
}
}
class ResourceB
implements AutoCloseable {
@Override
public void close() {
System.out.println("B Closed");
}
}
Usage:
try (
ResourceA a = new ResourceA();
ResourceB b = new ResourceB()
) {
System.out.println("Using Resources");
}
Output:
Using Resources
B Closed
A Closed
Custom Resource Example
class DatabaseConnection
implements AutoCloseable {
public void connect() {
System.out.println(
"Connected");
}
@Override
public void close() {
System.out.println(
"Connection Closed");
}
}
Usage:
try (DatabaseConnection db =
new DatabaseConnection()) {
db.connect();
}
Output:
Connected
Connection Closed
What Happens If an Exception Occurs?
Even if an exception occurs:
try (
BufferedReader reader =
new BufferedReader(
new FileReader("test.txt"))
) {
throw new RuntimeException();
}
The resource is still closed automatically.
Suppressed Exceptions
Consider:
try (
Resource resource =
new Resource()
) {
throw new RuntimeException(
"Main Exception");
}
If:
close()
also throws an exception,
Java preserves both exceptions.
The exception from close() becomes a:
Suppressed Exception
Accessing Suppressed Exceptions
catch (Exception e) {
Throwable[] suppressed =
e.getSuppressed();
for(Throwable t : suppressed) {
System.out.println(
t.getMessage());
}
}
Java 9 Enhancement
Before Java 9:
try (BufferedReader reader =
new BufferedReader(
new FileReader("test.txt"))) {
}
Java 9 allows:
BufferedReader reader =
new BufferedReader(
new FileReader("test.txt"));
try (reader) {
System.out.println(
reader.readLine());
}
Resource can be declared outside the try block.
Try-With-Resources vs Finally Block
| Feature | Try-With-Resources | Finally Block |
|---|---|---|
| Automatic Resource Closing | ✅ Yes | ❌ No |
| Boilerplate Code | Less | More |
| Resource Leak Risk | Low | High |
| Readability | Better | Moderate |
| Exception Handling | Cleaner | Complex |
| Java Version | Java 7+ | All Versions |
Real-World JDBC Example
Without Try-With-Resources:
Connection con = null;
try {
con = DriverManager.getConnection(...);
} finally {
if(con != null) {
con.close();
}
}
With Try-With-Resources:
try (
Connection con =
DriverManager.getConnection(...);
PreparedStatement ps =
con.prepareStatement(sql);
ResultSet rs =
ps.executeQuery()
) {
while(rs.next()) {
System.out.println(
rs.getString(1));
}
}
All resources are closed automatically.
Advantages
Automatic Resource Management
No need to manually call:
close()
Prevents Resource Leaks
Resources are always released.
Cleaner Code
Less boilerplate.
Better Exception Handling
Supports suppressed exceptions.
Improved Readability
Code is concise and easier to maintain.
Interview Trick Questions
Q1: Which Interface Must a Resource Implement?
AutoCloseable
or
Closeable
Q2: Does Try-With-Resources Execute close() If an Exception Occurs?
✅ Yes.
Resources are always closed.
Q3: In What Order Are Multiple Resources Closed?
Reverse order.
Example:
Resource3
Resource2
Resource1
Q4: When Was Try-With-Resources Introduced?
Java 7
Q5: Can We Use Existing Variables in Try-With-Resources?
✅ Yes (Java 9+).
BufferedReader reader = ...;
try(reader) {
}
Key Interview Points
- Try-With-Resources was introduced in Java 7.
- It automatically closes resources when execution exits the try block.
- Resources must implement
AutoCloseableorCloseable. - Multiple resources can be declared in a single try statement.
- Resources are closed in reverse order of creation.
- It prevents resource leaks and reduces boilerplate code.
- Java automatically handles suppressed exceptions.
- Java 9 allows using effectively final resources declared outside the try block.
One-Line Interview Answer
Try-With-Resources is a Java 7 feature that automatically closes resources implementing AutoCloseable or Closeable, ensuring efficient resource management, cleaner code, and prevention of resource leaks.
34) Comparable vs Comparator in Java
One of the most frequently asked Java interview questions is:
What is the difference between Comparable and Comparator?
Both are used for sorting objects, but they differ in how and where the sorting logic is defined.
Why Do We Need Comparable and Comparator?
Suppose we have a list of employees:
List<Employee> employees = List.of(
new Employee(103, "John"),
new Employee(101, "Alex"),
new Employee(102, "David")
);
Java doesn't know how to sort custom objects.
To define sorting rules, we use:
Comparable
Comparator
Comparable
Definition
Comparable is an interface present in:
java.lang.Comparable
It is used to define the natural ordering of objects.
Method
int compareTo(T obj)
Example
class Employee
implements Comparable<Employee> {
private int id;
Employee(int id) {
this.id = id;
}
@Override
public int compareTo(Employee other) {
return this.id - other.id;
}
}
Sorting
Collections.sort(employeeList);
Output:
Sorted By Employee ID
How compareTo() Works
obj1.compareTo(obj2)
Returns:
| Return Value | Meaning |
|---|---|
| Negative | Current object is smaller |
| Zero | Objects are equal |
| Positive | Current object is greater |
Example
10.compareTo(20)
Result:
Negative Value
20.compareTo(20)
Result:
0
30.compareTo(20)
Result:
Positive Value
Comparator
Definition
Comparator is an interface present in:
java.util.Comparator
It is used to define custom sorting logic outside the class.
Method
int compare(T o1, T o2)
Example
class Employee {
private int id;
private String name;
Employee(int id,
String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
}
Custom Comparator
class NameComparator
implements Comparator<Employee> {
@Override
public int compare(Employee e1,
Employee e2) {
return e1.getName()
.compareTo(
e2.getName());
}
}
Sorting
Collections.sort(
employees,
new NameComparator());
Output:
Sorted By Name
Comparable Example
class Employee
implements Comparable<Employee> {
private int id;
public Employee(int id) {
this.id = id;
}
@Override
public int compareTo(Employee other) {
return this.id - other.id;
}
}
Collections.sort(employees);
Output:
Sort By Employee ID
Comparator Example
class SalaryComparator
implements Comparator<Employee> {
@Override
public int compare(Employee e1,
Employee e2) {
return Double.compare(
e1.getSalary(),
e2.getSalary());
}
}
Collections.sort(
employees,
new SalaryComparator());
Output:
Sort By Salary
Java 8 Comparator Example
Instead of creating a separate class:
employees.sort(
(e1, e2) ->
e1.getName()
.compareTo(
e2.getName())
);
Using Method Reference:
employees.sort(
Comparator.comparing(
Employee::getName)
);
Cleaner and more readable.
Multiple Sorting Criteria
Suppose:
Sort By Department
Then By Salary
Java 8:
employees.sort(
Comparator.comparing(
Employee::getDepartment)
.thenComparing(
Employee::getSalary)
);
Comparable vs Comparator
| Feature | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo() | compare() |
| Number of Parameters | 1 | 2 |
| Sorting Logic Location | Inside Class | Outside Class |
| Natural Ordering | ✅ Yes | ❌ No |
| Multiple Sorting Criteria | ❌ Difficult | ✅ Easy |
| Modifies Existing Class | ✅ Yes | ❌ No |
| Reusable | Limited | High |
| Java 8 Lambda Support | ❌ No | ✅ Yes |
Memory Representation
Comparable
Employee Class
│
▼
compareTo()
Sorting logic is part of the class.
Comparator
Employee Class
NameComparator
SalaryComparator
AgeComparator
Sorting logic is separate.
Real-World Example
Consider:
Employee
Fields:
id
name
salary
department
Natural Ordering
Sort By ID
Use:
Comparable
Alternate Sorting
Sort By Name
Sort By Salary
Sort By Department
Use:
Comparator
Which One Should You Use?
Use Comparable When
- There is one natural sorting order.
- Sorting logic belongs to the class.
- Objects are commonly sorted the same way.
Example:
Employee By ID
Student By Roll Number
Use Comparator When
- Multiple sorting criteria exist.
- You cannot modify the source class.
- Sorting requirements change frequently.
Example:
Employee By Name
Employee By Salary
Employee By Department
Interview Trick Questions
Q1: Can a Class Have Both Comparable and Comparator?
✅ Yes.
Comparable
for natural ordering.
Comparator
for custom ordering.
Q2: Which Is More Flexible?
✅ Comparator
Because multiple comparator implementations can be created.
Q3: Which Interface Supports Lambda Expressions?
✅ Comparator
Example:
employees.sort(
(e1, e2) ->
e1.getName()
.compareTo(
e2.getName())
);
Q4: Which Method Does Collections.sort() Use?
Collections.sort(list);
Uses:
compareTo()
Collections.sort(list, comparator);
Uses:
compare()
Key Interview Points
Comparableis used for natural/default sorting and defines the sorting logic inside the class.Comparatoris used for custom sorting and defines the sorting logic outside the class.ComparablecontainscompareTo(), whileComparatorcontainscompare().- A class can implement only one natural ordering using
Comparable. - Multiple sorting strategies can be created using
Comparator. - Java 8 introduced lambda expressions and utility methods like
Comparator.comparing(). Comparatoris generally preferred when multiple sorting criteria are required.
One-Line Interview Answer
Comparable is used to define the natural ordering of objects through the compareTo() method inside the class, whereas Comparator is used to define custom sorting logic outside the class through the compare() method and supports multiple sorting strategies.
35) What is a Marker Interface in Java?
Definition
A Marker Interface is an interface that contains no methods and no fields.
It is used to provide metadata or special information to the JVM or framework about a class.
Example:
public interface Serializable {
}
The interface is empty, but implementing it gives special meaning to the class.
Simple Definition
A Marker Interface is an empty interface
used to mark a class with special behavior
or capabilities.
Why Is It Called a Marker Interface?
Because it simply:
Marks a Class
with additional information.
Example:
class Employee
implements Serializable {
}
Here, the class is marked as:
Serializable
Syntax
interface MarkerInterface {
}
Example:
interface Printable {
}
Implementing class:
class Report
implements Printable {
}
Common Marker Interfaces in Java
Serializable
java.io.Serializable
Used for object serialization.
Cloneable
java.lang.Cloneable
Indicates an object can be cloned.
Remote
java.rmi.Remote
Used in Java RMI applications.
RandomAccess
java.util.RandomAccess
Indicates fast random access support.
Example: Serializable Marker Interface
import java.io.Serializable;
class Employee
implements Serializable {
private int id;
private String name;
}
Now the object can be serialized.
Without Serializable
class Employee {
private int id;
}
Serialization attempt:
ObjectOutputStream out =
new ObjectOutputStream(...);
out.writeObject(employee);
Output:
java.io.NotSerializableException
Example: Cloneable Marker Interface
class Employee
implements Cloneable {
int id = 101;
@Override
protected Object clone()
throws CloneNotSupportedException {
return super.clone();
}
}
Usage:
Employee e1 =
new Employee();
Employee e2 =
(Employee) e1.clone();
Works successfully.
Without Cloneable
class Employee {
}
Attempt:
e1.clone();
Output:
CloneNotSupportedException
Custom Marker Interface Example
Create marker interface:
interface Auditable {
}
Implement it:
class Employee
implements Auditable {
}
Check at runtime:
Employee emp =
new Employee();
if(emp instanceof Auditable) {
System.out.println(
"Audit Enabled");
}
Output:
Audit Enabled
How Marker Interfaces Work
Class
│
▼
Implements Marker Interface
│
▼
JVM / Framework Detects It
│
▼
Special Behavior Enabled
Real-World Example
Serializable
class Employee
implements Serializable {
}
JVM understands:
Object Can Be Serialized
Cloneable
class Employee
implements Cloneable {
}
JVM understands:
Object Can Be Cloned
Marker Interface vs Normal Interface
Normal Interface
Contains methods.
interface Shape {
void draw();
}
Implementation:
class Circle
implements Shape {
public void draw() {
}
}
Marker Interface
Contains no methods.
interface Serializable {
}
Implementation:
class Employee
implements Serializable {
}
Only provides metadata.
Marker Interface vs Annotation
Modern Java often uses annotations instead of marker interfaces.
Marker Interface
interface Auditable {
}
Usage:
class Employee
implements Auditable {
}
Annotation
@interface Auditable {
}
Usage:
@Auditable
class Employee {
}
Comparison
| Feature | Marker Interface | Annotation |
|---|---|---|
| Introduced | Early Java | Java 5 |
| Methods Allowed | No | Can Have Attributes |
| Type Checking | ✅ Compile Time | ❌ Runtime Mostly |
| Metadata Support | Limited | Rich |
| Flexibility | Lower | Higher |
Advantages of Marker Interfaces
Compile-Time Type Safety
instanceof
checks are possible.
Easy Identification
Frameworks can easily identify classes.
JVM-Level Support
Used by core Java APIs like:
Serializable
Cloneable
Lightweight
No implementation required.
Disadvantages of Marker Interfaces
No Behavior
Cannot define methods.
Limited Metadata
Can only indicate presence or absence.
Annotations Are More Flexible
Modern applications prefer annotations.
Interview Trick Questions
Q1: Does a Marker Interface Contain Methods?
❌ No.
Example:
interface Serializable {
}
Q2: Name Common Marker Interfaces.
Serializable
Cloneable
Remote
RandomAccess
Q3: What Is the Main Purpose of a Marker Interface?
Provide Metadata
Or
Mark A Class For Special Processing
Q4: Which Is More Flexible—Marker Interface or Annotation?
✅ Annotation
Because annotations can store attributes and metadata.
Q5: Why Is Serializable a Marker Interface?
Because implementing it informs the JVM that:
Objects Of This Class
Can Be Serialized
Key Interview Points
- A Marker Interface is an empty interface with no methods or fields.
- It is used to mark a class for special processing or behavior.
- Common examples are
Serializable,Cloneable,Remote, andRandomAccess. - The JVM or frameworks check for the presence of the marker interface and apply special logic.
- Marker interfaces provide compile-time type checking.
- Modern Java often prefers annotations for metadata because they are more flexible.
- Marker interfaces are still widely used in core Java APIs.
One-Line Interview Answer
A Marker Interface is an empty interface that provides metadata about a class and signals the JVM or frameworks to apply special behavior, such as serialization (Serializable) or cloning (Cloneable).
36) What is `clone()` in Java?
Definition
The clone() method is used to create a copy of an existing object.
It belongs to the:
java.lang.Object
class and creates a duplicate of the current object.
Simple Definition
clone() creates a new object
with the same state as the
existing object.
Method Signature
protected Object clone()
throws CloneNotSupportedException
Defined in:
java.lang.Object
Why Use clone()?
Instead of manually copying fields:
Employee e2 =
new Employee();
e2.id = e1.id;
e2.name = e1.name;
We can create a copy using:
Employee e2 =
(Employee) e1.clone();
Requirements for Cloning
To use clone():
1. Implement Cloneable
class Employee
implements Cloneable {
}
2. Override clone()
@Override
public Object clone()
throws CloneNotSupportedException {
return super.clone();
}
Basic Example
class Employee
implements Cloneable {
int id;
String name;
Employee(int id,
String name) {
this.id = id;
this.name = name;
}
@Override
public Object clone()
throws CloneNotSupportedException {
return super.clone();
}
}
Usage
Employee e1 =
new Employee(
101,
"John");
Employee e2 =
(Employee) e1.clone();
System.out.println(
e2.id);
System.out.println(
e2.name);
Output:
101
John
Memory Representation
e1 ──► Employee
id=101
name=John
e2 ──► Employee
id=101
name=John
Different objects:
e1 == e2
Output:
false
What Happens Internally?
super.clone()
creates:
New Object
+
Copies All Field Values
This is called:
Field-by-Field Copy
Clone Creates Shallow Copy
By default:
clone()
creates a:
Shallow Copy
Example of Shallow Copy
class Address {
String city;
Address(String city) {
this.city = city;
}
}
class Employee
implements Cloneable {
String name;
Address address;
@Override
public Object clone()
throws CloneNotSupportedException {
return super.clone();
}
}
Usage:
Employee e2 =
(Employee) e1.clone();
Memory:
e1 ──────┐
▼
Address
e2 ──────┘
Both objects share the same Address object.
Problem with Shallow Copy
e2.address.city =
"Mumbai";
Now:
System.out.println(
e1.address.city);
Output:
Mumbai
Because both objects share the same nested object.
Deep Copy Using clone()
To avoid shared references:
@Override
public Object clone()
throws CloneNotSupportedException {
Employee cloned =
(Employee) super.clone();
cloned.address =
new Address(
this.address.city);
return cloned;
}
Now nested objects are copied as well.
Shallow Copy vs Deep Copy
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Parent Object Copied | ✅ Yes | ✅ Yes |
| Nested Objects Copied | ❌ No | ✅ Yes |
| References Shared | ✅ Yes | ❌ No |
| Memory Usage | Low | Higher |
| Side Effects | Possible | No |
What Happens If Cloneable Is Not Implemented?
Example:
class Employee {
}
Usage:
Employee e2 =
(Employee) e1.clone();
Output:
CloneNotSupportedException
Why Is Cloneable Needed?
Cloneable is a marker interface.
interface Cloneable {
}
It tells JVM:
This Object Supports Cloning
Example
class Employee
implements Cloneable {
}
Now:
super.clone()
works successfully.
Alternative Ways to Copy Objects
Many developers avoid clone() and prefer:
Copy Constructor
public Employee(Employee other) {
this.id = other.id;
this.name = other.name;
}
Usage:
Employee copy =
new Employee(original);
Builder Pattern
Employee copy =
Employee.builder()
.id(emp.getId())
.name(emp.getName())
.build();
Serialization-Based Copy
Useful for deep cloning complex objects.
Advantages of clone()
Quick Object Duplication
Creates a copy without manually copying fields.
Built Into Java
Available through Object class.
Useful for Prototype Pattern
Widely used in design patterns.
Disadvantages of clone()
Produces Shallow Copy
Nested objects are not copied automatically.
Requires Cloneable
Without it:
CloneNotSupportedException
Type Casting Required
(Employee) e1.clone();
Difficult for Complex Objects
Deep cloning requires additional code.
Real-World Example
Suppose:
Employee template =
new Employee(
101,
"Default User");
Create copies:
Employee emp1 =
(Employee) template.clone();
Employee emp2 =
(Employee) template.clone();
Useful when creating similar objects repeatedly.
clone() vs Copy Constructor
| Feature | clone() | Copy Constructor |
|---|---|---|
| Built Into Java | ✅ Yes | ❌ No |
| Uses Cloneable | ✅ Yes | ❌ No |
| Type Safe | ❌ No | ✅ Yes |
| Easy Deep Copy | ❌ No | ✅ Yes |
| Requires Casting | ✅ Yes | ❌ No |
| Modern Preference | Less | More |
Interview Trick Questions
Q1: Which Class Defines clone()?
java.lang.Object
Q2: What Type of Copy Does Default clone() Create?
Shallow Copy
Q3: What Happens If Cloneable Is Not Implemented?
CloneNotSupportedException
Q4: Is Cloneable a Marker Interface?
✅ Yes.
It contains no methods.
Q5: Why Do Many Developers Avoid clone()?
Because:
Complex
Produces Shallow Copy
Requires Casting
Copy constructors are usually preferred.
Key Interview Points
clone()is defined in thejava.lang.Objectclass.- It creates a copy of an existing object.
- The class must implement
Cloneableto support cloning. - Default
clone()performs a shallow copy. - Deep copy requires manual cloning of referenced objects.
- Without
Cloneable,CloneNotSupportedExceptionis thrown. Cloneableis a marker interface.- Modern Java applications often prefer copy constructors or builders over
clone().
One-Line Interview Answer
The clone() method creates a copy of an existing object, and by default it performs a shallow copy. To use it, a class must implement the Cloneable marker interface and override the clone() method.
37) What Are Default Methods in Interfaces?
Definition
Default Methods are methods in an interface that have a method body (implementation).
They were introduced in:
Java 8
using the default keyword.
Simple Definition
Default methods allow interfaces
to provide method implementations
without forcing implementing classes
to override them.
Why Were Default Methods Introduced?
Before Java 8, interfaces could contain only:
abstract methods
public static final variables
Example:
interface Vehicle {
void start();
}
If a new method was added:
interface Vehicle {
void start();
void stop();
}
All implementing classes would break because they must implement stop().
Default methods solve this problem.
Syntax
interface InterfaceName {
default void methodName() {
// implementation
}
}
Example
interface Vehicle {
default void start() {
System.out.println(
"Vehicle Started");
}
}
Implementing class:
class Car
implements Vehicle {
}
Usage:
Car car = new Car();
car.start();
Output:
Vehicle Started
Even though Car does not implement start().
Overriding Default Methods
Implementing classes can override default methods.
Example:
interface Vehicle {
default void start() {
System.out.println(
"Vehicle Started");
}
}
class Car
implements Vehicle {
@Override
public void start() {
System.out.println(
"Car Started");
}
}
Output:
Car Started
Real-World Motivation
Suppose Java already has:
interface List
with thousands of implementations.
When Java 8 introduced Streams:
stream()
parallelStream()
Adding abstract methods would break all implementations.
Instead Java added:
default Stream<E> stream()
allowing backward compatibility.
Example with Multiple Default Methods
interface Employee {
default void login() {
System.out.println(
"Employee Login");
}
default void logout() {
System.out.println(
"Employee Logout");
}
}
class Developer
implements Employee {
}
Usage:
Developer dev =
new Developer();
dev.login();
dev.logout();
Output:
Employee Login
Employee Logout
Default Method vs Abstract Method
Abstract Method
interface Vehicle {
void start();
}
Must be implemented.
Default Method
interface Vehicle {
default void start() {
System.out.println(
"Started");
}
}
Implementation is optional.
Multiple Inheritance Conflict
Consider:
interface A {
default void display() {
System.out.println("A");
}
}
interface B {
default void display() {
System.out.println("B");
}
}
class Test
implements A, B {
}
Compilation Error:
Duplicate default methods
Java cannot decide which method to use.
Resolving Conflict
The class must override the method.
class Test
implements A, B {
@Override
public void display() {
System.out.println(
"Custom Implementation");
}
}
Calling Specific Interface Default Method
You can invoke a specific interface's default method.
class Test
implements A, B {
@Override
public void display() {
A.super.display();
B.super.display();
}
}
Output:
A
B
Default Methods and Object Class
Suppose:
interface Vehicle {
default String toString() {
return "Vehicle";
}
}
Compilation Error.
Why?
Because:
toString()
already exists in:
java.lang.Object
Interface default methods cannot override Object class methods.
Practical Example
interface Logger {
default void log(String message) {
System.out.println(
"[LOG] " + message);
}
}
class Service
implements Logger {
}
Usage:
Service service =
new Service();
service.log("Application Started");
Output:
[LOG] Application Started
Advantages of Default Methods
Backward Compatibility
New methods can be added to interfaces without breaking existing implementations.
Code Reuse
Common implementation can be shared.
Reduced Boilerplate Code
Implementing classes need not write the same code repeatedly.
Interface Evolution
Interfaces can evolve over time safely.
Disadvantages of Default Methods
Multiple Inheritance Conflicts
Two interfaces may provide the same default method.
Increased Complexity
Interfaces now contain behavior as well as contracts.
Potential Design Issues
Too many default methods can make interfaces harder to maintain.
Default Methods vs Abstract Methods
| Feature | Default Method | Abstract Method |
|---|---|---|
| Has Body | ✅ Yes | ❌ No |
| Requires Override | ❌ No | ✅ Yes |
| Introduced In | Java 8 | Java 1 |
| Supports Backward Compatibility | ✅ Yes | ❌ No |
Uses default Keyword |
✅ Yes | ❌ No |
Default Methods vs Static Methods
Default Method
default void show() {
}
Called using object.
obj.show();
Static Method
static void show() {
}
Called using interface name.
InterfaceName.show();
Real-World Usage
Default methods are heavily used in Java APIs such as:
Collection
List
Map
Iterable
Comparator
Examples:
forEach()
removeIf()
stream()
parallelStream()
sort()
Interview Trick Questions
Q1: Can an Interface Have Implemented Methods?
✅ Yes.
Using:
default
or
static
methods.
Q2: When Were Default Methods Introduced?
Java 8
Q3: Why Were Default Methods Introduced?
Backward Compatibility
To allow adding new methods to interfaces without breaking existing implementations.
Q4: Can Default Methods Be Overridden?
✅ Yes.
Implementing classes can override them.
Q5: What Happens If Two Interfaces Have the Same Default Method?
Compilation Error
The implementing class must override the method.
Key Interview Points
- Default methods were introduced in Java 8.
- They allow interfaces to provide method implementations using the
defaultkeyword. - Implementing classes are not required to override default methods.
- Their primary purpose is to provide backward compatibility when evolving interfaces.
- Default methods can be overridden by implementing classes.
- Multiple interfaces with the same default method create ambiguity that must be resolved by overriding.
- Widely used in Java Collections and Stream APIs.
One-Line Interview Answer
Default methods are interface methods introduced in Java 8 that contain implementations using the default keyword, allowing interfaces to evolve without breaking existing implementations and enabling code reuse.
38) What Are Checked vs Unchecked Exceptions in Java?
Definition
Exceptions in Java are broadly classified into two categories:
1. Checked Exceptions
2. Unchecked Exceptions
The main difference is:
Checked Exceptions
→ Checked at Compile Time
Unchecked Exceptions
→ Checked at Runtime
Java Exception Hierarchy
Throwable
│
┌──────────────┴──────────────┐
│ │
Error Exception
│
┌───────────────┴───────────────┐
│ │
Checked Exceptions RuntimeException
│
Unchecked Exceptions
Checked Exceptions
Definition
A Checked Exception is an exception that the compiler checks at compile time.
The programmer must either:
try-catch
or
throws
the exception.
Otherwise, the code will not compile.
Examples of Checked Exceptions
IOException
SQLException
FileNotFoundException
ClassNotFoundException
InterruptedException
ParseException
Example
import java.io.FileReader;
public class Test {
public static void main(String[] args) {
FileReader file =
new FileReader(
"data.txt");
}
}
Compilation Error:
Unhandled exception:
FileNotFoundException
Solution 1: Using try-catch
try {
FileReader file =
new FileReader(
"data.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Solution 2: Using throws
public static void main(String[] args)
throws FileNotFoundException {
FileReader file =
new FileReader(
"data.txt");
}
Why Are They Called Checked Exceptions?
Because the compiler checks:
Have you handled this exception?
before allowing compilation.
Unchecked Exceptions
Definition
An Unchecked Exception is an exception that occurs at runtime and is not checked by the compiler.
Handling is optional.
Examples of Unchecked Exceptions
NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException
NumberFormatException
IllegalArgumentException
ClassCastException
All inherit from:
RuntimeException
Example
public class Test {
public static void main(String[] args) {
int result =
10 / 0;
}
}
Compiles successfully.
Runtime Output:
ArithmeticException
Example: NullPointerException
String name = null;
System.out.println(
name.length());
Output:
NullPointerException
Compiler does not complain.
Why Are They Called Unchecked Exceptions?
Because the compiler does not verify whether they are handled.
No Compile-Time Check
Checked Exception Example
import java.io.*;
public class Test {
public static void main(String[] args) {
try {
FileReader file =
new FileReader(
"abc.txt");
} catch (FileNotFoundException e) {
System.out.println(
"File Not Found");
}
}
}
Unchecked Exception Example
public class Test {
public static void main(String[] args) {
String str = null;
System.out.println(
str.length());
}
}
Compiles successfully but fails at runtime.
Memory Trick
Checked Exception
Compiler Checks
Examples:
IOException
SQLException
InterruptedException
Unchecked Exception
Programmer Checks
Examples:
NullPointerException
ArithmeticException
NumberFormatException
Checked vs Unchecked Exceptions
| Feature | Checked Exception | Unchecked Exception |
|---|---|---|
| Checked By Compiler | ✅ Yes | ❌ No |
| Checked At | Compile Time | Runtime |
| Must Handle | ✅ Yes | ❌ No |
| Parent Class | Exception | RuntimeException |
| Compilation Error If Ignored | ✅ Yes | ❌ No |
| Represents | Recoverable Conditions | Programming Errors |
Examples Comparison
Checked
FileReader file =
new FileReader(
"test.txt");
Compiler forces handling.
Unchecked
int result =
10 / 0;
Compiler allows execution.
Runtime failure occurs.
Why Did Java Introduce Checked Exceptions?
Checked exceptions represent:
Recoverable Problems
Examples:
File Missing
Database Down
Network Failure
The caller can potentially recover.
Why Are Unchecked Exceptions Not Forced?
Unchecked exceptions usually indicate:
Programming Bugs
Examples:
Null Pointer
Invalid Index
Division By Zero
These should be fixed in code rather than recovered from.
Custom Checked Exception
class InvalidAgeException
extends Exception {
public InvalidAgeException(
String message) {
super(message);
}
}
Usage:
throw new InvalidAgeException(
"Age Must Be 18+");
Must be handled.
Custom Unchecked Exception
class InvalidAgeException
extends RuntimeException {
public InvalidAgeException(
String message) {
super(message);
}
}
Handling is optional.
Best Practices
Use Checked Exceptions When
The caller can recover.
Examples:
File Not Found
Database Connection Failure
Network Timeout
Use Unchecked Exceptions When
The problem is due to programming mistakes.
Examples:
Null Values
Invalid Arguments
Incorrect State
Real-World Examples
Checked Exception
Connection connection =
DriverManager.getConnection(url);
May throw:
SQLException
Caller can retry or show an error message.
Unchecked Exception
String name = null;
name.length();
Throws:
NullPointerException
Usually indicates a bug.
Interview Trick Questions
Q1: Are All Exceptions Checked?
❌ No.
Exceptions extending:
RuntimeException
are unchecked.
Q2: Is RuntimeException Checked?
❌ No.
It is an unchecked exception.
Q3: Is IOException Checked?
✅ Yes.
Must be handled or declared.
Q4: Can We Catch Unchecked Exceptions?
✅ Yes.
try {
} catch (RuntimeException e) {
}
Q5: Which Is Better?
Neither.
Use:
Checked Exceptions
→ Recoverable Problems
Unchecked Exceptions
→ Programming Errors
Key Interview Points
- Checked exceptions are verified by the compiler and must be handled or declared.
- Unchecked exceptions occur at runtime and are not checked by the compiler.
- Checked exceptions extend
Exception(excludingRuntimeException). - Unchecked exceptions extend
RuntimeException. - Checked exceptions represent recoverable conditions.
- Unchecked exceptions usually indicate programming mistakes.
- Examples of checked exceptions include
IOException,SQLException, andInterruptedException. - Examples of unchecked exceptions include
NullPointerException,ArithmeticException, andArrayIndexOutOfBoundsException.
One-Line Interview Answer
Checked exceptions are compile-time exceptions that must be handled or declared, whereas unchecked exceptions are runtime exceptions that are not enforced by the compiler and typically represent programming errors.
39) Difference between `throw` and `throws`
One of the most frequently asked Java interview questions is:
What is the difference between
throwandthrows?
Although both are related to exception handling, they serve completely different purposes.
Quick Definition
throw
Used to explicitly throw an exception object.
throw new Exception();
throws
Used in a method signature to declare exceptions that a method may throw.
public void readFile()
throws IOException {
}
Simple Explanation
throw
→ Actually throws an exception
throws
→ Declares that a method may throw exceptions
throw Keyword
Purpose
Used to explicitly create and throw an exception.
Syntax
throw new ExceptionType(
"Error Message");
Example
public class Test {
public static void main(String[] args) {
throw new RuntimeException(
"Something went wrong");
}
}
Output:
Exception in thread "main"
java.lang.RuntimeException:
Something went wrong
Throwing a Checked Exception
throw new IOException(
"File Not Found");
Compilation Error:
Unhandled exception:
IOException
Must be handled or declared.
Example
public static void main(String[] args)
throws IOException {
throw new IOException(
"File Missing");
}
Output:
java.io.IOException:
File Missing
throws Keyword
Purpose
Used to declare exceptions that a method may throw.
Syntax
returnType methodName()
throws ExceptionType {
}
Example
public void readFile()
throws IOException {
FileReader file =
new FileReader(
"data.txt");
}
Meaning:
This Method May Throw IOException
The caller must handle it.
Example
public void readFile()
throws IOException {
}
Caller:
try {
readFile();
} catch (IOException e) {
e.printStackTrace();
}
How throw Works
if(age < 18) {
throw new IllegalArgumentException(
"Age Must Be 18+");
}
Output:
Exception Generated Immediately
How throws Works
public void validateAge(int age)
throws Exception {
}
No exception is generated here.
It only informs the caller.
Example Using Both Together
public void validateAge(int age)
throws Exception {
if(age < 18) {
throw new Exception(
"Invalid Age");
}
}
Explanation:
throws
→ Declares Exception
throw
→ Actually Throws Exception
Real-World Example
public void withdraw(
double amount)
throws Exception {
if(amount > balance) {
throw new Exception(
"Insufficient Balance");
}
}
Here:
throws Exception
declares the possibility.
throw new Exception(...)
actually throws the exception.
Multiple Exceptions with throws
public void process()
throws IOException,
SQLException,
ClassNotFoundException {
}
A method can declare multiple exceptions.
Can We Throw Multiple Exceptions?
❌ No.
Only one exception object can be thrown at a time.
Example:
throw new IOException();
or
throw new SQLException();
Custom Exception Example
Custom Exception
class InvalidAgeException
extends Exception {
public InvalidAgeException(
String message) {
super(message);
}
}
Using throw
if(age < 18) {
throw new InvalidAgeException(
"Age Must Be 18+");
}
Using throws
public void validateAge(int age)
throws InvalidAgeException {
}
throw vs throws
| Feature | throw |
throws |
|---|---|---|
| Purpose | Throws an exception | Declares an exception |
| Used With | Exception Object | Exception Class |
| Location | Inside Method | Method Signature |
| Number Allowed | One Exception Object | Multiple Exceptions |
| Generates Exception | ✅ Yes | ❌ No |
| Compile-Time Declaration | ❌ No | ✅ Yes |
| Followed By | new Exception() |
ExceptionClass |
Example Comparison
Using throw
throw new ArithmeticException(
"Division By Zero");
Actually creates and throws exception.
Using throws
public void divide()
throws ArithmeticException {
}
Only declares possibility.
Flow Diagram
Method
│
▼
throws IOException
│
▼
Method Executes
│
▼
throw new IOException()
│
▼
Exception Propagates
Common Interview Examples
Example 1
public static void main(String[] args)
throws Exception {
throw new Exception(
"Demo");
}
Output:
Exception: Demo
Example 2
public static void test()
throws IOException {
}
Output:
No Exception Thrown
Only declaration exists.
Interview Trick Questions
Q1: Can throw Throw Multiple Exceptions?
❌ No.
Only one exception object at a time.
Q2: Can throws Declare Multiple Exceptions?
✅ Yes.
throws IOException,
SQLException
Q3: Is throws Mandatory for Unchecked Exceptions?
❌ No.
Example:
NullPointerException
ArithmeticException
do not require declaration.
Q4: Which Keyword Actually Creates the Exception?
✅ throw
Example:
throw new Exception();
Q5: Which Keyword Is Used in Method Signature?
✅ throws
Example:
public void process()
throws IOException {
}
Key Interview Points
throwis used to explicitly throw an exception object.throwsis used to declare exceptions in a method signature.throwis followed by an exception object.throwsis followed by exception class names.throwactually generates an exception.throwsonly informs callers about possible exceptions.- A method can declare multiple exceptions using
throws. - Only one exception object can be thrown at a time using
throw.
One-Line Interview Answer
throw is used to explicitly create and throw an exception object inside a method, whereas throws is used in a method signature to declare the exceptions that the method may throw.
40) What is `Optional` in Java?
Definition
Optional is a container object introduced in Java 8 that may or may not contain a non-null value.
It is used to avoid:
NullPointerException (NPE)
and makes null handling more explicit and readable.
Simple Definition
Optional is a wrapper object
that represents the presence
or absence of a value.
Why Was Optional Introduced?
Before Java 8, methods often returned:
null
Example:
public String getName() {
return null;
}
Usage:
String name =
getName();
System.out.println(
name.length());
Output:
NullPointerException
Solution Using Optional
public Optional<String> getName() {
return Optional.of(
"John");
}
Usage:
Optional<String> name =
getName();
name.ifPresent(
System.out::println);
Safer and more expressive.
Package
java.util.Optional
Creating Optional Objects
1. Optional.of()
Used when value is definitely not null.
Optional<String> name =
Optional.of("John");
Example
System.out.println(
name.get());
Output:
John
If Null Is Passed
Optional.of(null);
Output:
NullPointerException
2. Optional.ofNullable()
Used when value may be null.
Optional<String> name =
Optional.ofNullable(null);
No exception occurs.
Example
Optional<String> name =
Optional.ofNullable(
getName());
3. Optional.empty()
Creates an empty Optional.
Optional<String> name =
Optional.empty();
Represents:
No Value Present
Checking Value Presence
isPresent()
Optional<String> name =
Optional.of("John");
if(name.isPresent()) {
System.out.println(
name.get());
}
Output:
John
isEmpty() (Java 11)
if(name.isEmpty()) {
System.out.println(
"No Value");
}
Retrieving Values
get()
Returns value if present.
Optional<String> name =
Optional.of("John");
System.out.println(
name.get());
Output:
John
Danger
Optional.empty().get();
Output:
NoSuchElementException
Avoid using get() without checking.
orElse()
Returns default value if empty.
Optional<String> name =
Optional.empty();
String result =
name.orElse("Guest");
System.out.println(result);
Output:
Guest
orElseGet()
Uses a supplier to generate default value.
String result =
name.orElseGet(
() -> "Guest");
Difference
orElse()
Always evaluates the default value.
orElseGet()
Evaluates only when Optional is empty.
orElseThrow()
Throws exception if value is absent.
String name =
optional.orElseThrow(
() ->
new RuntimeException(
"Name Not Found"));
ifPresent()
Executes code only if value exists.
Optional<String> name =
Optional.of("John");
name.ifPresent(
System.out::println);
Output:
John
Example
Without Optional:
String name =
getName();
if(name != null) {
System.out.println(
name.toUpperCase());
}
With Optional:
Optional.ofNullable(
getName())
.ifPresent(
value ->
System.out.println(
value.toUpperCase()));
Cleaner and safer.
Transforming Values
map()
Transforms contained value.
Optional<String> name =
Optional.of("john");
Optional<String> upperCase =
name.map(
String::toUpperCase);
Output:
JOHN
Example
Optional<String> result =
Optional.of("John")
.map(String::toUpperCase)
.map(String::trim);
filter()
Filters values.
Optional<String> name =
Optional.of("John");
Optional<String> result =
name.filter(
n ->
n.startsWith("J"));
Output:
Optional[John]
Example
Optional<String> result =
Optional.of("John")
.filter(
n ->
n.length() > 5);
Output:
Optional.empty
flatMap()
Used when mapping functions already return Optional.
Without flatMap:
Optional<Optional<String>>
With flatMap:
Optional<String>
Example:
optional.flatMap(
this::findName);
Real-World Example
public Optional<Employee>
findEmployee(int id) {
return employeeRepository
.findById(id);
}
Usage:
Employee employee =
findEmployee(101)
.orElseThrow(
() ->
new RuntimeException(
"Employee Not Found"));
Optional Processing Pipeline
Optional.of("John")
.map(String::toUpperCase)
.filter(
name ->
name.startsWith("J"))
.ifPresent(
System.out::println);
Output:
JOHN
Advantages of Optional
Avoids NullPointerException
Most common benefit.
Better API Design
Method signature clearly indicates:
Value May Be Missing
Improved Readability
Eliminates repetitive null checks.
Functional Programming Support
Works seamlessly with:
map()
filter()
flatMap()
Encourages Explicit Null Handling
Developers are forced to think about missing values.
Disadvantages of Optional
Small Performance Overhead
Extra wrapper object.
Not Intended for Fields
Avoid:
class Employee {
Optional<String> name;
}
Use normal fields instead.
Not Serializable
Generally unsuitable for entity classes.
Optional vs Null
| Feature | Optional | Null |
|---|---|---|
| Null Safety | ✅ Yes | ❌ No |
| Readability | High | Low |
| NPE Risk | Low | High |
| Functional Operations | ✅ Yes | ❌ No |
| Explicit Missing Value | ✅ Yes | ❌ No |
Best Practices
Return Optional
Good:
public Optional<Employee>
findEmployee(int id)
Avoid Using Optional as Parameter
Bad:
public void save(
Optional<Employee> employee)
Prefer:
public void save(
Employee employee)
Avoid get()
Bad:
optional.get();
Prefer:
optional.orElse(...)
or
optional.orElseThrow(...)
Interview Trick Questions
Q1: When Was Optional Introduced?
Java 8
Q2: What Problem Does Optional Solve?
NullPointerException
Q3: Difference Between of() and ofNullable()?
of()
Optional.of(null)
Throws:
NullPointerException
ofNullable()
Optional.ofNullable(null)
Creates:
Optional.empty
Q4: Is Optional a Replacement for Every Null?
❌ No.
Mostly intended for:
Method Return Types
Q5: What Happens If get() Is Called on an Empty Optional?
NoSuchElementException
Key Interview Points
Optionalwas introduced in Java 8 to reduceNullPointerException.- It is a container object that may or may not contain a value.
- Common factory methods are
of(),ofNullable(), andempty(). - Common retrieval methods are
get(),orElse(),orElseGet(), andorElseThrow(). - Functional methods include
map(),filter(),flatMap(), andifPresent(). - Optional improves API design by explicitly indicating that a value may be absent.
- It is primarily recommended for method return types.
- Avoid using
get()without checking value presence.
One-Line Interview Answer
Optional is a Java 8 container class that represents the presence or absence of a value, helping developers avoid NullPointerException and write cleaner, more expressive code.
41) What is a functional interface?
Definition
A Functional Interface is an interface that contains exactly one abstract method.
It was introduced in:
Java 8
to support:
Lambda Expressions
Method References
Functional Programming
Simple Definition
A Functional Interface is an interface
with exactly one abstract method,
which can be implemented using
a lambda expression.
Why Was Functional Interface Introduced?
Before Java 8:
Runnable runnable =
new Runnable() {
@Override
public void run() {
System.out.println(
"Running");
}
};
Required a lot of boilerplate code.
With Java 8:
Runnable runnable =
() -> System.out.println(
"Running");
Cleaner and more readable.
Syntax
@FunctionalInterface
interface InterfaceName {
void method();
}
Example
@FunctionalInterface
interface Greeting {
void sayHello();
}
Implementation using lambda:
Greeting greeting =
() -> System.out.println(
"Hello");
Usage:
greeting.sayHello();
Output:
Hello
Rules of Functional Interface
A functional interface must have:
Exactly One Abstract Method
Valid Example
@FunctionalInterface
interface Calculator {
int add(
int a,
int b);
}
Invalid Example
@FunctionalInterface
interface Calculator {
int add(
int a,
int b);
int subtract(
int a,
int b);
}
Compilation Error:
Multiple abstract methods found
What About Default Methods?
Functional interfaces can have:
default methods
Example:
@FunctionalInterface
interface Greeting {
void sayHello();
default void welcome() {
System.out.println(
"Welcome");
}
}
Valid Functional Interface.
What About Static Methods?
Allowed.
Example:
@FunctionalInterface
interface Greeting {
void sayHello();
static void info() {
System.out.println(
"Greeting Interface");
}
}
Still valid.
What About Object Class Methods?
Methods inherited from:
java.lang.Object
do not count as abstract methods.
Example:
@FunctionalInterface
interface Test {
void execute();
String toString();
}
Still valid.
@FunctionalInterface Annotation
@FunctionalInterface
is optional but recommended.
Benefits:
Compile-Time Validation
Better Readability
Self Documentation
Example:
@FunctionalInterface
interface Printer {
void print();
}
If another abstract method is added:
void scan();
Compilation Error occurs immediately.
Functional Interface Without Annotation
Valid:
interface Printer {
void print();
}
Still a functional interface because it has one abstract method.
Lambda Expression Example
Functional Interface:
@FunctionalInterface
interface Calculator {
int add(
int a,
int b);
}
Implementation:
Calculator calculator =
(a, b) -> a + b;
Usage:
System.out.println(
calculator.add(10, 20));
Output:
30
Method Reference Example
Functional Interface:
@FunctionalInterface
interface Message {
void print(
String msg);
}
Implementation:
Message message =
System.out::println;
Usage:
message.print("Hello");
Output:
Hello
Common Built-in Functional Interfaces
Java provides several functional interfaces in:
java.util.function
Predicate
Used for testing conditions.
Predicate<Integer> even =
n -> n % 2 == 0;
System.out.println(
even.test(10));
Output:
true
Function
Transforms one value into another.
Function<String, Integer> length =
str -> str.length();
System.out.println(
length.apply("Java"));
Output:
4
Consumer
Consumes data and returns nothing.
Consumer<String> printer =
System.out::println;
printer.accept("Hello");
Output:
Hello
Supplier
Produces data.
Supplier<String> supplier =
() -> "Java";
System.out.println(
supplier.get());
Output:
Java
Runnable
One of the oldest functional interfaces.
Runnable task =
() -> System.out.println(
"Task Running");
Custom Functional Interface Example
@FunctionalInterface
interface EmployeeValidator {
boolean validate(
String employeeId);
}
Implementation:
EmployeeValidator validator =
id -> id.startsWith("EMP");
Usage:
System.out.println(
validator.validate(
"EMP101"));
Output:
true
Real-World Example
Sorting employees:
employees.sort(
(e1, e2) ->
e1.getName()
.compareTo(
e2.getName()));
Lambda expression works because:
Comparator
is a functional interface.
Functional Interface vs Normal Interface
| Feature | Functional Interface | Normal Interface |
|---|---|---|
| Abstract Methods | Exactly 1 | Multiple Allowed |
| Lambda Support | ✅ Yes | ❌ No |
| Method Reference Support | ✅ Yes | ❌ No |
| @FunctionalInterface | Recommended | Not Applicable |
| Java 8 Feature | ✅ Yes | ❌ No |
Advantages of Functional Interfaces
Enables Lambda Expressions
Less code and better readability.
Supports Functional Programming
Makes code more declarative.
Improves Maintainability
Reduces anonymous class usage.
Better Integration with Streams
Works seamlessly with:
Stream API
Encourages Reusable Behavior
Functions can be passed as arguments.
Common Functional Interfaces in Java
| Interface | Method |
|---|---|
| Runnable | run() |
| Callable | call() |
| Comparator | compare() |
| Predicate | test() |
| Function | apply() |
| Consumer | accept() |
| Supplier | get() |
Interview Trick Questions
Q1: Can a Functional Interface Have Default Methods?
✅ Yes.
default void method() {}
does not count as an abstract method.
Q2: Can a Functional Interface Have Static Methods?
✅ Yes.
Q3: Is @FunctionalInterface Mandatory?
❌ No.
It is optional but recommended.
Q4: Can a Functional Interface Extend Another Interface?
✅ Yes.
Provided the resulting interface still has only one abstract method.
Q5: Is Comparator a Functional Interface?
✅ Yes.
Because it contains one abstract method:
compare()
Key Interview Points
- A Functional Interface contains exactly one abstract method.
- Introduced in Java 8 to support lambda expressions and method references.
- The
@FunctionalInterfaceannotation provides compile-time validation. - Functional interfaces can contain default, static, and private methods.
- Common examples are
Runnable,Callable,Comparator,Predicate,Function,Consumer, andSupplier. - They are the foundation of Java's functional programming features and Stream API.
- Lambda expressions can only be assigned to functional interfaces.
One-Line Interview Answer
A Functional Interface is an interface with exactly one abstract method, introduced in Java 8 to support lambda expressions, method references, and functional programming concepts.
42) What Are Lambda Expressions in Java?
Definition
A Lambda Expression is a short and concise way to represent an anonymous function in Java.
It was introduced in:
Java 8
primarily to support:
Functional Programming
Functional Interfaces
Stream API
Simple Definition
A Lambda Expression is an
anonymous function that can be
passed around like an object.
It allows you to write behavior without creating a separate class.
Why Were Lambda Expressions Introduced?
Before Java 8, implementing an interface required:
Anonymous Inner Classes
Example:
Runnable task =
new Runnable() {
@Override
public void run() {
System.out.println(
"Task Running");
}
};
A lot of boilerplate code.
Lambda Solution
Runnable task =
() -> System.out.println(
"Task Running");
Cleaner and easier to read.
Syntax
(parameters) -> expression
or
(parameters) -> {
// multiple statements
}
Basic Example
Without Lambda:
Runnable task =
new Runnable() {
@Override
public void run() {
System.out.println(
"Hello Java");
}
};
With Lambda:
Runnable task =
() -> System.out.println(
"Hello Java");
Anatomy of a Lambda Expression
Example:
(a, b) -> a + b
Parts:
(a, b) → Parameters
->
Lambda Operator
a + b → Body
Lambda with No Parameters
() -> System.out.println(
"Hello");
Lambda with One Parameter
name -> System.out.println(
name);
Lambda with Multiple Parameters
(a, b) -> a + b
Lambda with Multiple Statements
(a, b) -> {
System.out.println(
"Calculating");
return a + b;
}
Functional Interface Requirement
Lambda expressions work only with:
Functional Interfaces
A functional interface contains:
Exactly One Abstract Method
Example:
@FunctionalInterface
interface Calculator {
int add(
int a,
int b);
}
Using Lambda:
Calculator calculator =
(a, b) -> a + b;
Usage:
System.out.println(
calculator.add(
10,
20));
Output:
30
Example with Runnable
Runnable task =
() -> {
System.out.println(
"Running...");
};
task.run();
Output:
Running...
Example with Comparator
Before Java 8:
Collections.sort(
employees,
new Comparator<Employee>() {
@Override
public int compare(
Employee e1,
Employee e2) {
return e1.getName()
.compareTo(
e2.getName());
}
});
With Lambda:
employees.sort(
(e1, e2) ->
e1.getName()
.compareTo(
e2.getName())
);
Much cleaner.
Type Inference
Java automatically determines parameter types.
Example:
(a, b) -> a + b
Compiler infers types.
Equivalent to:
(int a,
int b) -> a + b
Lambda with Return Value
Calculator calculator =
(a, b) -> a + b;
Output:
Returns Sum
Lambda with Block Body
Calculator calculator =
(a, b) -> {
int result =
a + b;
return result;
};
Lambda and Streams
One of the most common uses.
Example:
List<String> names =
List.of(
"John",
"Alex",
"David");
Filter names:
names.stream()
.filter(
name ->
name.startsWith("A"))
.forEach(
System.out::println);
Output:
Alex
Lambda and Predicate
Predicate<Integer> even =
n -> n % 2 == 0;
Usage:
System.out.println(
even.test(10));
Output:
true
Lambda and Function
Function<String, Integer> length =
str -> str.length();
Usage:
System.out.println(
length.apply(
"Java"));
Output:
4
Lambda and Consumer
Consumer<String> printer =
msg -> System.out.println(
msg);
Usage:
printer.accept(
"Hello");
Output:
Hello
Lambda and Supplier
Supplier<String> supplier =
() -> "Java";
Usage:
System.out.println(
supplier.get());
Output:
Java
Method Reference vs Lambda
Lambda:
name ->
System.out.println(name)
Method Reference:
System.out::println
Both are equivalent.
Variable Capture
Lambda can access:
Final Variables
or
Effectively Final Variables
Example:
int number = 10;
Runnable task =
() -> System.out.println(
number);
Valid.
Invalid:
int number = 10;
number++;
Runnable task =
() -> System.out.println(
number);
Compilation Error.
Advantages of Lambda Expressions
Less Boilerplate Code
Reduces anonymous inner class code.
Improved Readability
Code becomes cleaner and easier to understand.
Functional Programming Support
Enables functional-style programming.
Better Collection Processing
Works seamlessly with Stream API.
Easier Parallel Processing
Used extensively with:
parallelStream()
Disadvantages of Lambda Expressions
Debugging Can Be Harder
Stack traces may be less intuitive.
Overuse Reduces Readability
Complex lambdas can become difficult to maintain.
Not Suitable for Large Logic Blocks
For complex logic, regular methods are often better.
Anonymous Class vs Lambda
| Feature | Anonymous Class | Lambda Expression |
|---|---|---|
| Boilerplate Code | More | Less |
| Readability | Moderate | High |
| Introduced In | Java 1.1 | Java 8 |
| Functional Interface Required | ❌ No | ✅ Yes |
| Performance | Slightly Lower | Better |
| Supports Functional Programming | ❌ No | ✅ Yes |
Real-World Example
Sorting Employees:
employees.sort(
(e1, e2) ->
Double.compare(
e1.getSalary(),
e2.getSalary()));
Filtering Active Employees:
employees.stream()
.filter(
emp ->
emp.isActive())
.forEach(
System.out::println);
Interview Trick Questions
Q1: Can Lambda Expressions Work Without Functional Interfaces?
❌ No.
They require:
Exactly One Abstract Method
Q2: Are Lambda Expressions Objects?
✅ Yes.
They are treated as instances of functional interfaces.
Q3: Can Lambda Expressions Access Local Variables?
✅ Yes.
Only if they are:
Final
or
Effectively Final
Q4: Can a Lambda Expression Have Multiple Statements?
✅ Yes.
Using:
{
...
}
block syntax.
Q5: What Is the Main Purpose of Lambda Expressions?
Reduce Boilerplate Code
And
Enable Functional Programming
Key Interview Points
- Lambda Expressions were introduced in Java 8.
- They provide a concise way to represent anonymous functions.
- They work only with functional interfaces.
- Syntax uses the
->operator. - They reduce boilerplate code compared to anonymous inner classes.
- Lambda expressions are heavily used with Stream API and collections.
- They support functional programming concepts.
- Local variables accessed inside lambdas must be final or effectively final.
One-Line Interview Answer
A Lambda Expression is a concise way to represent an anonymous function in Java, introduced in Java 8, primarily used with functional interfaces to reduce boilerplate code and enable functional programming.
43) When to Use Abstract Class and When to Use Interface in Java?
One of the most frequently asked Java interview questions is:
When should we use an Abstract Class and when should we use an Interface?
The answer depends on whether you need:
Shared State + Common Implementation
OR
A Contract / Capability
Quick Rule
Use Abstract Class When
Classes are closely related
Need common state (fields)
Need common implementation
Need constructor support
Example:
Vehicle
├── Car
├── Bike
└── Truck
All vehicles share common properties.
Use Interface When
Need a contract
Need multiple inheritance
Unrelated classes share behavior
Example:
Flyable
Can be implemented by:
Bird
Aeroplane
Drone
Abstract Class
Definition
An abstract class is a class that:
abstract class Vehicle {
}
cannot be instantiated and may contain:
- Abstract methods
- Concrete methods
- Instance variables
- Constructors
Example
abstract class Vehicle {
protected String brand;
public Vehicle(String brand) {
this.brand = brand;
}
abstract void start();
public void displayBrand() {
System.out.println(brand);
}
}
class Car
extends Vehicle {
public Car(String brand) {
super(brand);
}
@Override
void start() {
System.out.println(
"Car Started");
}
}
Why Use Abstract Class?
Because:
All Vehicles Have
Common State
Common Behavior
Example:
brand
speed
fuelType
These belong in a parent abstract class.
Interface
Definition
An interface defines a contract that implementing classes must follow.
interface Flyable {
}
Example
interface Flyable {
void fly();
}
class Bird
implements Flyable {
@Override
public void fly() {
System.out.println(
"Bird Flying");
}
}
class Drone
implements Flyable {
@Override
public void fly() {
System.out.println(
"Drone Flying");
}
}
Why Use Interface?
Because:
Bird IS NOT A Drone
Drone IS NOT A Plane
But all can:
Fly
Interface models capability.
Real-World Example
Abstract Class
abstract class Employee {
protected int employeeId;
protected String name;
public void login() {
System.out.println(
"Employee Login");
}
abstract double calculateSalary();
}
class Developer
extends Employee {
@Override
double calculateSalary() {
return 100000;
}
}
Common employee behavior exists.
Interface Example
interface TaxPayer {
void payTax();
}
Implemented by:
Employee
Company
Freelancer
These classes are unrelated but share a capability.
When to Use Abstract Class
Use an abstract class when:
1. Classes Are Closely Related
Example:
Vehicle
Car
Bike
Truck
2. Need Common Fields
protected String name;
protected int id;
Interfaces cannot maintain instance state.
3. Need Constructors
public Vehicle() {
}
Interfaces cannot have constructors.
4. Need Partial Implementation
abstract void start();
void stop() {
}
Some methods common, some customizable.
5. Need Access Modifiers
protected
private
public
Supported in abstract classes.
When to Use Interface
Use an interface when:
1. Need a Contract
Example:
interface PaymentProcessor {
void processPayment();
}
2. Need Multiple Inheritance
Java doesn't support:
class A extends B, C
But supports:
class A
implements X, Y, Z
3. Unrelated Classes Share Behavior
Example:
Bird
Airplane
Drone
All:
Fly
4. Framework/API Design
Spring uses interfaces extensively.
Example:
JpaRepository
CrudRepository
Runnable
Callable
Comparator
5. Need Loose Coupling
Programming to interfaces:
PaymentService service =
new CreditCardPayment();
instead of:
CreditCardPayment service =
new CreditCardPayment();
Abstract Class vs Interface
| Feature | Abstract Class | Interface |
|---|---|---|
| Inheritance Keyword | extends | implements |
| Multiple Inheritance | ❌ No | ✅ Yes |
| Constructors | ✅ Yes | ❌ No |
| Instance Variables | ✅ Yes | ❌ No |
| Abstract Methods | ✅ Yes | ✅ Yes |
| Concrete Methods | ✅ Yes | ✅ Yes (Default Methods) |
| Access Modifiers | All | Mostly Public |
| State Management | ✅ Yes | ❌ No |
| Best For | Common Base Class | Contract / Capability |
Design Perspective
Abstract Class
Represents:
IS-A Relationship
Example:
Car IS-A Vehicle
Bike IS-A Vehicle
Interface
Represents:
CAN-DO Relationship
Example:
Bird CAN Fly
Drone CAN Fly
Real Interview Example
Abstract Class
abstract class Animal {
void eat() {
System.out.println(
"Eating");
}
abstract void sound();
}
class Dog
extends Animal {
@Override
void sound() {
System.out.println(
"Bark");
}
}
Interface
interface Swimmable {
void swim();
}
class Fish
implements Swimmable {
@Override
public void swim() {
System.out.println(
"Swimming");
}
}
Can We Use Both Together?
Yes.
This is common in enterprise applications.
Example:
abstract class Employee {
protected int id;
}
interface TaxPayer {
void payTax();
}
class Developer
extends Employee
implements TaxPayer {
@Override
public void payTax() {
}
}
Best of both worlds.
Interview Trick Questions
Q1: Can an Abstract Class Have Constructors?
✅ Yes.
Q2: Can an Interface Have Constructors?
❌ No.
Q3: Can an Interface Have Method Implementations?
✅ Yes.
Since Java 8:
default methods
static methods
Q4: Which Supports Multiple Inheritance?
✅ Interface
Q5: Which Should Be Preferred in Enterprise Applications?
Generally:
Interface for Contracts
Abstract Class for Shared Implementation
Key Interview Points
- Use an abstract class when related classes share common state and behavior.
- Use an interface when defining a contract or capability.
- Abstract classes support constructors, instance variables, and partial implementations.
- Interfaces support multiple inheritance and loose coupling.
- Abstract classes represent an IS-A relationship.
- Interfaces represent a CAN-DO relationship.
- Enterprise applications often use both together.
- Prefer interfaces for flexibility and abstraction; use abstract classes for code reuse and shared state.
One-Line Interview Answer
Use an abstract class when multiple related classes share common state and implementation, and use an interface when you want to define a contract or capability that can be implemented by unrelated classes while supporting multiple inheritance and loose coupling.
44) What is Serialization in Java?
Definition
Serialization is the process of converting a Java object into a stream of bytes so that it can be:
- Saved to a file
- Sent over a network
- Stored in a database
- Transferred between JVMs
Simple Definition
Serialization converts an object
into a byte stream so it can be
stored or transmitted.
Opposite Process
The reverse process is called:
Deserialization
which converts the byte stream back into an object.
Diagram
Java Object
│
▼
Serialization
│
▼
Byte Stream
│
▼
Stored / Sent
│
▼
Deserialization
│
▼
Java Object
Why Do We Need Serialization?
Common use cases:
Save Object State
Employee Object
→ File
Network Communication
Client
→ Object
→ Server
Caching
Object
→ Redis
→ Retrieve Later
Distributed Systems
JVM 1
→ Serialize
→ Network
→ JVM 2
Serializable Interface
To make a class serializable, it must implement:
java.io.Serializable
Example:
import java.io.Serializable;
class Employee
implements Serializable {
private int id;
private String name;
}
What is Serializable?
public interface Serializable {
}
It is a:
Marker Interface
because it contains no methods.
It simply tells JVM:
This Object Can Be Serialized
Serialization Example
Employee Class
import java.io.Serializable;
class Employee
implements Serializable {
private int id;
private String name;
public Employee(
int id,
String name) {
this.id = id;
this.name = name;
}
}
Serialize Object
import java.io.*;
public class Test {
public static void main(String[] args)
throws Exception {
Employee employee =
new Employee(
101,
"John");
FileOutputStream file =
new FileOutputStream(
"employee.ser");
ObjectOutputStream out =
new ObjectOutputStream(
file);
out.writeObject(employee);
out.close();
System.out.println(
"Object Serialized");
}
}
Output:
Object Serialized
Deserialization Example
import java.io.*;
public class Test {
public static void main(String[] args)
throws Exception {
FileInputStream file =
new FileInputStream(
"employee.ser");
ObjectInputStream in =
new ObjectInputStream(
file);
Employee employee =
(Employee) in.readObject();
in.close();
System.out.println(
"Object Deserialized");
}
}
Output:
Object Deserialized
Important Classes
Serialization
ObjectOutputStream
Methods:
writeObject()
Deserialization
ObjectInputStream
Methods:
readObject()
serialVersionUID
A serialized object contains a version identifier.
Example:
private static final long
serialVersionUID = 1L;
Why Is It Needed?
Suppose:
Version 1:
class Employee
implements Serializable {
int id;
}
Object serialized.
Later:
Version 2:
class Employee
implements Serializable {
int id;
String name;
}
Without matching versions:
InvalidClassException
may occur during deserialization.
Best Practice
Always define:
private static final long
serialVersionUID = 1L;
transient Keyword
Sometimes fields should not be serialized.
Example:
class Employee
implements Serializable {
private String name;
transient String password;
}
During serialization:
password
is ignored.
Example
Employee employee =
new Employee();
employee.name =
"John";
employee.password =
"secret123";
After deserialization:
name = John
password = null
static Fields and Serialization
Static fields belong to the class, not the object.
Example:
static String companyName;
They are not serialized.
Serialization Memory View
Before Serialization:
Employee Object
├── id = 101
├── name = John
└── salary = 50000
After Serialization:
101
John
50000
stored as bytes.
What Happens If Serializable Is Not Implemented?
Example:
class Employee {
}
Serialization:
out.writeObject(employee);
Output:
NotSerializableException
Advantages of Serialization
Persistent Storage
Save object state.
Network Transfer
Send objects across systems.
Easy Data Sharing
Between applications and JVMs.
Supports Distributed Computing
Useful in microservices and clustering.
Disadvantages of Serialization
Performance Overhead
Converting objects to bytes takes time.
Security Risks
Serialized data can be tampered with.
Versioning Problems
Changes in class structure may break deserialization.
Increased Memory Usage
Serialized data consumes storage.
Serialization vs Deserialization
| Feature | Serialization | Deserialization |
|---|---|---|
| Purpose | Object → Bytes | Bytes → Object |
| Stream Used | ObjectOutputStream | ObjectInputStream |
| Method | writeObject() | readObject() |
| Direction | JVM → Storage | Storage → JVM |
Real-World Examples
HTTP Session Replication
Web servers serialize session objects.
Distributed Caching
Objects stored in:
Redis
Hazelcast
Ehcache
Messaging Systems
Objects transferred through:
Kafka
RabbitMQ
ActiveMQ
RMI (Remote Method Invocation)
Java objects sent across JVMs.
Interview Trick Questions
Q1: What Interface Is Required for Serialization?
Serializable
Q2: Is Serializable a Marker Interface?
✅ Yes.
Contains no methods.
Q3: What Happens If Serializable Is Not Implemented?
NotSerializableException
Q4: Are Static Fields Serialized?
❌ No.
Static fields belong to the class.
Q5: Are Transient Fields Serialized?
❌ No.
They are skipped.
Q6: What Is serialVersionUID?
Version Identifier
used during deserialization to verify compatibility.
Q7: What Is the Opposite of Serialization?
Deserialization
Key Interview Points
- Serialization converts an object into a byte stream.
- Deserialization converts a byte stream back into an object.
- A class must implement
Serializableto support serialization. Serializableis a marker interface.ObjectOutputStreamis used for serialization.ObjectInputStreamis used for deserialization.transientfields are not serialized.staticfields are not serialized.serialVersionUIDhelps maintain version compatibility.- If a class does not implement
Serializable, aNotSerializableExceptionis thrown.
One-Line Interview Answer
Serialization is the process of converting a Java object into a byte stream for storage or transmission, while deserialization reconstructs the object from that byte stream.
45) Difference Between `HashMap` and `ConcurrentHashMap` in Java
One of the most frequently asked Java concurrency interview questions is:
What is the difference between
HashMapandConcurrentHashMap?
The key difference is:
HashMap
→ Not Thread-Safe
ConcurrentHashMap
→ Thread-Safe
Quick Definition
HashMap
A non-synchronized implementation of the Map interface.
Map<String, Integer> map =
new HashMap<>();
Best suited for:
Single-Threaded Applications
ConcurrentHashMap
A thread-safe implementation of the Map interface designed for concurrent access.
Map<String, Integer> map =
new ConcurrentHashMap<>();
Best suited for:
Multi-Threaded Applications
Why HashMap Is Not Thread-Safe?
Consider:
HashMap<Integer, String> map =
new HashMap<>();
Two threads execute:
Thread-1:
map.put(1, "A");
Thread-2:
map.put(2, "B");
At the same time.
Result:
Race Condition
Data Corruption
Lost Updates
Infinite Loop (older JDKs)
may occur.
Example
Map<Integer, String> map =
new HashMap<>();
Runnable task = () -> {
for(int i = 0; i < 1000; i++) {
map.put(i, "Value");
}
};
Running multiple threads may produce unpredictable results.
How ConcurrentHashMap Solves This?
Map<Integer, String> map =
new ConcurrentHashMap<>();
Internally it uses:
Fine-Grained Synchronization
CAS Operations
Locking Only Required Portions
allowing multiple threads to work safely.
Example
Map<Integer, String> map =
new ConcurrentHashMap<>();
Runnable task = () -> {
for(int i = 0; i < 1000; i++) {
map.put(i, "Value");
}
};
Multiple threads can update safely.
Internal Working
HashMap
Array
+
Linked List
+
Red-Black Tree (Java 8+)
Memory:
Bucket Array
│
├── Node
├── Node
└── Node
No synchronization.
ConcurrentHashMap Internal Working
Java 7
Used:
Segment-Based Locking
Structure:
Map
├── Segment 1
├── Segment 2
├── Segment 3
└── Segment N
Only one segment locked at a time.
Java 8+
Uses:
CAS (Compare-And-Swap)
+
Synchronized Blocks
+
Bucket-Level Locking
No segment architecture.
This significantly improves performance.
Null Key and Null Value Support
HashMap
Allows:
1 Null Key
Multiple Null Values
Example:
map.put(null, "Admin");
map.put(1, null);
Valid.
ConcurrentHashMap
Does NOT allow:
null key
null value
Example:
map.put(null, "Admin");
Throws:
NullPointerException
Why Null Is Not Allowed?
Suppose:
map.get(key)
returns:
null
Question:
Key Not Found?
OR
Value Is Null?
In concurrent environments this ambiguity is dangerous.
Therefore:
ConcurrentHashMap
disallows null.
Iteration Behavior
HashMap Iterator
Fail-Fast.
Example:
for(String value :
map.values()) {
map.put(5, "New");
}
Output:
ConcurrentModificationException
ConcurrentHashMap Iterator
Fail-Safe (Weakly Consistent).
Example:
for(String value :
map.values()) {
map.put(5, "New");
}
No exception.
Iteration continues safely.
Performance Comparison
Single Thread
HashMap
is usually faster.
Reason:
No Synchronization Overhead
Multiple Threads
ConcurrentHashMap
performs much better.
Reason:
Optimized Concurrent Access
Atomic Operations
ConcurrentHashMap provides atomic methods.
putIfAbsent()
map.putIfAbsent(
"A",
100);
Adds value only if key is absent.
replace()
map.replace(
"A",
200);
Atomically replaces value.
computeIfAbsent()
map.computeIfAbsent(
"A",
key -> 100);
Thread-safe computation.
HashMap Does Not Provide Thread-Safe Atomic Methods
Example:
if(!map.containsKey(key)) {
map.put(key, value);
}
Not safe in multithreaded environments.
ConcurrentHashMap Version
map.putIfAbsent(
key,
value);
Safe and atomic.
Example: Cache Implementation
Bad:
Map<String, User> cache =
new HashMap<>();
Used by many threads.
Risk:
Race Conditions
Good:
Map<String, User> cache =
new ConcurrentHashMap<>();
Safe for concurrent access.
HashMap vs Hashtable vs ConcurrentHashMap
| Feature | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| Thread Safe | ❌ No | ✅ Yes | ✅ Yes |
| Performance | High | Low | High |
| Null Key | ✅ Yes | ❌ No | ❌ No |
| Null Value | ✅ Yes | ❌ No | ❌ No |
| Synchronization | None | Entire Map | Fine-Grained |
| Iterator | Fail-Fast | Fail-Fast | Fail-Safe |
| Recommended | Single Thread | Legacy | Multi-Threaded |
HashMap vs ConcurrentHashMap
| Feature | HashMap | ConcurrentHashMap |
|---|---|---|
| Thread Safe | ❌ No | ✅ Yes |
| Synchronization | None | Fine-Grained |
| Null Key Allowed | ✅ Yes | ❌ No |
| Null Value Allowed | ✅ Yes | ❌ No |
| Iterator Type | Fail-Fast | Fail-Safe |
| Concurrent Reads | Unsafe | Safe |
| Concurrent Writes | Unsafe | Safe |
| Atomic Operations | ❌ No | ✅ Yes |
| Performance in Multithreading | Poor | Excellent |
Real-World Usage
Use HashMap
When:
Single Thread
Local Variables
Temporary Data
Example:
Map<Integer, String> students =
new HashMap<>();
Use ConcurrentHashMap
When:
Shared Cache
Session Store
Multi-Threaded Services
Executor Framework
Microservices
Example:
Map<String, User> cache =
new ConcurrentHashMap<>();
Interview Trick Questions
Q1: Is HashMap Thread-Safe?
❌ No.
Q2: Is ConcurrentHashMap Synchronized?
✅ Yes, internally.
But it uses:
Fine-Grained Locking
instead of locking the entire map.
Q3: Can ConcurrentHashMap Store Null Keys?
❌ No.
Throws:
NullPointerException
Q4: Which Iterator Is Fail-Fast?
HashMap
Q5: Which Iterator Is Fail-Safe?
ConcurrentHashMap
Q6: Which Is Faster in Single-Threaded Applications?
HashMap
Q7: Which Is Recommended for Multi-Threaded Applications?
ConcurrentHashMap
Key Interview Points
HashMapis not thread-safe, whileConcurrentHashMapis thread-safe.ConcurrentHashMapuses fine-grained locking and CAS operations for better concurrency.HashMapallows one null key and multiple null values.ConcurrentHashMapdoes not allow null keys or values.HashMapiterators are fail-fast.ConcurrentHashMapiterators are fail-safe (weakly consistent).ConcurrentHashMapprovides atomic operations likeputIfAbsent()andcomputeIfAbsent().- Use
HashMapfor single-threaded applications andConcurrentHashMapfor multi-threaded applications.
One-Line Interview Answer
HashMap is a non-thread-safe map suitable for single-threaded environments, whereas ConcurrentHashMap is a thread-safe, high-performance map designed for concurrent access using fine-grained locking and atomic operations.
46) Difference Between `ArrayList` and `LinkedList` in Java
One of the most frequently asked Java Collection interview questions is:
What is the difference between
ArrayListandLinkedList?
Both implement the:
List
interface, but they use different internal data structures and have different performance characteristics.
Quick Definition
ArrayList
Stores elements using a:
Dynamic Array
List<String> list =
new ArrayList<>();
Best for:
Fast Random Access
Frequent Reads
LinkedList
Stores elements using a:
Doubly Linked List
List<String> list =
new LinkedList<>();
Best for:
Frequent Insertions
Frequent Deletions
Internal Data Structure
ArrayList
Index
0 1 2 3
┌───┬───┬───┬───┐
│ A │ B │ C │ D │
└───┴───┴───┴───┘
Elements are stored in contiguous memory locations.
LinkedList
NULL
│
▼
[A] ⇄ [B] ⇄ [C] ⇄ [D]
Each node contains:
Data
Previous Pointer
Next Pointer
Memory Representation
ArrayList
Array
├── A
├── B
├── C
└── D
LinkedList
Node
├── Data
├── Prev
└── Next
Extra memory is required for pointers.
Adding Elements
ArrayList
list.add("A");
Average Complexity:
O(1)
If internal array becomes full:
New Bigger Array Created
Elements Copied
Cost:
O(n)
LinkedList
list.add("A");
Complexity:
O(1)
No resizing required.
Accessing Elements
ArrayList
list.get(500);
Complexity:
O(1)
Direct index access.
Memory:
Address = Base + Index
LinkedList
list.get(500);
Complexity:
O(n)
Must traverse nodes.
Example:
Head
↓
A → B → C → D
To reach D, all previous nodes are visited.
Insertion in Middle
Suppose:
list.add(2, "X");
ArrayList
Before:
A B C D
After:
A B X C D
Elements must shift.
Complexity:
O(n)
LinkedList
Only links change.
Before:
A ⇄ B ⇄ C
After:
A ⇄ B ⇄ X ⇄ C
Complexity:
O(1)
(After reaching the position)
Deletion
ArrayList
list.remove(2);
Remaining elements shift left.
Complexity:
O(n)
LinkedList
Only node references are updated.
Complexity:
O(1)
(After locating the node)
Iteration Performance
ArrayList
Excellent cache locality.
Fast Iteration
LinkedList
Poor cache locality.
Slower Iteration
Because nodes are scattered in memory.
Memory Usage
ArrayList
Stores only:
Data
Memory efficient.
LinkedList
Stores:
Data
Previous Pointer
Next Pointer
Consumes more memory.
Example
ArrayList
List<String> list =
new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Access:
System.out.println(
list.get(1));
Output:
B
Fast operation.
LinkedList Example
List<String> list =
new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
Access:
System.out.println(
list.get(1));
Output:
B
But internally traversal occurs.
Queue Operations
LinkedList
Also implements:
Deque
Queue
Supports:
addFirst()
addLast()
removeFirst()
removeLast()
Efficiently.
Example:
LinkedList<String> list =
new LinkedList<>();
list.addFirst("A");
list.addLast("B");
ArrayList Does Not Support Queue Operations Efficiently
Removing first element:
list.remove(0);
Requires shifting.
Complexity:
O(n)
Performance Comparison
| Operation | ArrayList | LinkedList |
|---|---|---|
| Add at End | O(1) | O(1) |
| Add at Beginning | O(n) | O(1) |
| Insert in Middle | O(n) | O(1)* |
| Remove in Middle | O(n) | O(1)* |
| Get by Index | O(1) | O(n) |
| Search | O(n) | O(n) |
| Iteration | Faster | Slower |
| Memory Usage | Less | More |
* After locating the node.
ArrayList vs LinkedList
| Feature | ArrayList | LinkedList |
|---|---|---|
| Internal Structure | Dynamic Array | Doubly Linked List |
| Random Access | Fast | Slow |
| Insertions | Slower | Faster |
| Deletions | Slower | Faster |
| Memory Consumption | Low | High |
| Cache Locality | Better | Poor |
| Iteration Speed | Faster | Slower |
| Queue Operations | Not Efficient | Efficient |
Real-World Usage
Use ArrayList When
Frequent Reads
Random Access Needed
Rare Insertions/Deletions
Examples:
Employee List
Product List
Customer List
Use LinkedList When
Frequent Insertions
Frequent Deletions
Queue/Deque Operations
Examples:
Task Scheduler
Browser History
Undo/Redo Feature
Interview Trick Questions
Q1: Which Is Faster for get(index)?
✅ ArrayList
O(1)
Q2: Which Uses More Memory?
✅ LinkedList
Stores extra references.
Q3: Which Is Better for Frequent Insertions?
✅ LinkedList
Q4: Which Is Better for Frequent Reads?
✅ ArrayList
Q5: Which Provides Better Cache Performance?
✅ ArrayList
Q6: Which Implements Deque?
✅ LinkedList
Q7: Which Is Used Most Often in Real Applications?
✅ ArrayList
Because read operations are usually more frequent than insertions and deletions.
Key Interview Points
ArrayListuses a dynamic array, whileLinkedListuses a doubly linked list.ArrayListprovides O(1) random access using indexes.LinkedListrequires O(n) traversal to access an element by index.- Insertions and deletions are generally faster in
LinkedList. ArrayListconsumes less memory and offers better cache locality.LinkedListconsumes more memory due to node references.ArrayListis preferred for read-heavy applications.LinkedListis useful for queue, deque, and frequent insertion/deletion scenarios.
One-Line Interview Answer
ArrayList is backed by a dynamic array and provides fast random access, while LinkedList is backed by a doubly linked list and provides faster insertions and deletions but slower element access.
47) Difference Between `map()` and `flatMap()` in Java Streams
One of the most frequently asked Java 8 Stream API interview questions is:
What is the difference between
map()andflatMap()?
The key difference is:
map()
→ Transforms each element into another element
flatMap()
→ Transforms and flattens nested structures into a single stream
Quick Definition
map()
Applies a function to each element and returns a new stream.
stream.map(Function)
flatMap()
Applies a function that returns a stream and then flattens all resulting streams into a single stream.
stream.flatMap(Function)
Simple Analogy
Suppose we have:
Student → List of Subjects
Using map():
[
[Java, SQL],
[Spring, AWS]
]
(Stream of Lists)
Using flatMap():
[
Java,
SQL,
Spring,
AWS
]
(Single Flat Stream)
map() Example
Convert Names to Uppercase
List<String> names =
List.of(
"john",
"alex",
"david");
List<String> result =
names.stream()
.map(String::toUpperCase)
.toList();
Output:
[JOHN, ALEX, DAVID]
How map() Works
john → JOHN
alex → ALEX
david → DAVID
One input becomes one output.
map() Visualization
Input Stream
↓
[A, B, C]
map()
↓
[a, b, c]
Output Stream
Another map() Example
List<Integer> numbers =
List.of(1, 2, 3, 4);
List<Integer> squares =
numbers.stream()
.map(
n -> n * n)
.toList();
Output:
[1, 4, 9, 16]
flatMap() Example
Suppose:
List<List<String>> subjects =
List.of(
List.of(
"Java",
"SQL"),
List.of(
"Spring",
"AWS"));
Using map():
subjects.stream()
.map(
list -> list.stream());
Result:
Stream<Stream<String>>
Nested streams.
Using flatMap():
List<String> result =
subjects.stream()
.flatMap(
list ->
list.stream())
.toList();
Output:
[Java, SQL, Spring, AWS]
How flatMap() Works
Before:
[
[Java, SQL],
[Spring, AWS]
]
After:
[
Java,
SQL,
Spring,
AWS
]
All nested collections are flattened.
flatMap() Visualization
Input
↓
[
[A,B],
[C,D]
]
flatMap()
↓
A
B
C
D
Output
↓
[A,B,C,D]
map() vs flatMap() Return Types
map()
Stream<T>
→
Stream<R>
Example:
String
→
Integer
names.stream()
.map(String::length)
Output:
[4, 5, 5]
flatMap()
Stream<T>
→
Stream<Stream<R>>
becomes:
Stream<R>
after flattening.
Example:
students.stream()
.flatMap(
student ->
student.getSubjects()
.stream())
Real-World Example
Employee Skills
class Employee {
private List<String> skills;
}
Data:
Employee1
→ [Java, SQL]
Employee2
→ [Spring, AWS]
Using map():
employees.stream()
.map(
Employee::getSkills)
Output:
[
[Java, SQL],
[Spring, AWS]
]
Using flatMap():
employees.stream()
.flatMap(
employee ->
employee.getSkills()
.stream())
Output:
[
Java,
SQL,
Spring,
AWS
]
Common Interview Example
Split Words into Characters
List<String> words =
List.of(
"Java",
"SQL");
Using map():
words.stream()
.map(
word ->
word.split(""));
Output:
[
[J,a,v,a],
[S,Q,L]
]
Using flatMap():
words.stream()
.flatMap(
word ->
Arrays.stream(
word.split("")))
.toList();
Output:
[J, a, v, a, S, Q, L]
Another Example
Extract Email Addresses
List<Customer> customers;
Each customer has:
List<String> emails;
Using map():
customers.stream()
.map(
Customer::getEmails)
Result:
List<List<String>>
Using flatMap():
customers.stream()
.flatMap(
customer ->
customer.getEmails()
.stream())
Result:
List<String>
Performance
map()
Used when:
One Input
→
One Output
flatMap()
Used when:
One Input
→
Multiple Outputs
and nested collections need flattening.
map() vs flatMap()
| Feature | map() | flatMap() |
|---|---|---|
| Purpose | Transform Elements | Transform + Flatten |
| Output | Stream |
Stream |
| Input → Output | One-to-One | One-to-Many |
| Nested Collections | Not Flattened | Flattened |
| Common Use | Convert Values | Flatten Lists/Streams |
| Complexity | Simple Transformation | Nested Structure Processing |
Visual Comparison
map()
Input:
[A, B, C]
map()
↓
[a, b, c]
flatMap()
Input:
[
[A,B],
[C,D]
]
flatMap()
↓
[A,B,C,D]
When to Use map()
Use when:
Transforming Data
Converting Types
Formatting Values
Calculating Values
Examples:
String → Uppercase
String → Length
Employee → Name
When to Use flatMap()
Use when:
Working With Nested Lists
Working With Nested Streams
Flattening Data Structures
Combining Multiple Streams
Examples:
List<List<String>>
List<Set<String>>
List<Employee Skills>
Interview Trick Questions
Q1: Does map() Flatten Collections?
❌ No.
It preserves nesting.
Q2: Does flatMap() Flatten Collections?
✅ Yes.
Q3: Which Produces Stream<Stream<T>>?
map()
when the mapping function returns a stream.
Q4: Which Produces a Single Stream?
flatMap()
Q5: Which Is Used More With Nested Collections?
flatMap()
Key Interview Points
map()transforms each element into another element.flatMap()transforms elements and flattens nested structures.map()performs one-to-one mapping.flatMap()performs one-to-many mapping and merges results.map()may create nested streams when the mapping function returns a stream.flatMap()converts nested streams into a single stream.- Use
map()for simple transformations. - Use
flatMap()when working with nested collections or streams.
One-Line Interview Answer
map() is used to transform each stream element into another value, while flatMap() is used to transform elements that produce multiple values (or streams) and flatten them into a single stream.
48) What is a Race Condition in Java?
Definition
A Race Condition occurs when two or more threads access and modify shared data concurrently, and the final result depends on the order in which the threads execute.
In other words:
Multiple Threads
+
Shared Resource
+
Uncontrolled Access
=
Race Condition
Simple Definition
A race condition occurs when
multiple threads compete to
read or modify shared data,
leading to unpredictable results.
Why Is It Called a Race Condition?
Because multiple threads are:
"Racing"
to access or update the same resource.
The thread that executes first affects the final outcome.
Real-Life Example
Imagine a bank account:
Balance = ₹1000
Two threads attempt to withdraw:
Thread 1 → Withdraw ₹500
Thread 2 → Withdraw ₹700
at the same time.
Without proper synchronization:
Incorrect Balance
Negative Balance
Lost Updates
can occur.
Basic Example
class Counter {
int count = 0;
public void increment() {
count++;
}
}
Two threads:
Counter counter =
new Counter();
Thread t1 =
new Thread(() -> {
for(int i = 0;
i < 1000;
i++) {
counter.increment();
}
});
Thread t2 =
new Thread(() -> {
for(int i = 0;
i < 1000;
i++) {
counter.increment();
}
});
Expected:
2000
Actual:
1875
1923
1991
Output may vary each run.
This is a race condition.
Why Does It Happen?
The statement:
count++;
looks like one operation but actually consists of:
1. Read count
2. Increment value
3. Write value back
Suppose:
count = 5
Thread 1:
Read 5
Thread 2:
Read 5
Thread 1:
Increment → 6
Write → 6
Thread 2:
Increment → 6
Write → 6
Expected:
7
Actual:
6
One update is lost.
Race Condition Visualization
Initial Count = 5
Thread 1
Read 5
│
▼
Thread 2
Read 5
│
▼
Thread 1
Write 6
│
▼
Thread 2
Write 6
Final Value = 6
Symptoms of Race Conditions
Incorrect Results
Wrong Counter Values
Lost Updates
Data Overwritten
Inconsistent State
Application Behavior Changes
Difficult Debugging
Works Sometimes
Fails Sometimes
Common Places Where Race Conditions Occur
Shared Variables
count++;
Collections
HashMap
ArrayList
HashSet
used by multiple threads.
Banking Systems
Account Balance Updates
Inventory Systems
Stock Quantity Updates
Session Management
Shared User Sessions
Example with HashMap
Map<Integer, String> map =
new HashMap<>();
Two threads:
map.put(1, "A");
and
map.put(2, "B");
simultaneously.
Possible results:
Data Corruption
Lost Entries
Unexpected Behavior
How to Prevent Race Conditions?
1. Synchronization
synchronized void increment() {
count++;
}
Only one thread can execute at a time.
Example:
class Counter {
int count = 0;
synchronized void increment() {
count++;
}
}
Now:
Thread-Safe
2. Atomic Classes
Java provides:
AtomicInteger
AtomicLong
AtomicBoolean
Example:
AtomicInteger count =
new AtomicInteger();
count.incrementAndGet();
Thread-safe without explicit synchronization.
3. Locks
Using:
ReentrantLock
Example:
Lock lock =
new ReentrantLock();
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
4. Concurrent Collections
Instead of:
HashMap
use:
ConcurrentHashMap
Instead of:
ArrayList
use:
CopyOnWriteArrayList
5. Immutability
Immutable objects cannot be modified.
Example:
String
LocalDate
BigInteger
No race condition possible.
Synchronization Example
Without Synchronization:
count++;
Output:
Unpredictable
With Synchronization:
synchronized void increment() {
count++;
}
Output:
Correct Every Time
Race Condition vs Deadlock
| Feature | Race Condition | Deadlock |
|---|---|---|
| Problem | Incorrect Data | Threads Stuck |
| Cause | Concurrent Modification | Lock Dependency |
| Application Runs | Yes | No |
| Result | Wrong Output | No Progress |
| Fix | Synchronization | Proper Lock Ordering |
Race Condition vs Data Race
These terms are often used interchangeably, but technically:
Data Race
Occurs when:
Two Threads Access Same Variable
At Least One Write
No Synchronization
Race Condition
A broader problem where outcome depends on thread timing.
Real-World Example
Ticket Booking System
Available Tickets:
1
Two users book simultaneously.
Thread 1:
Reads 1
Thread 2:
Reads 1
Both reserve the ticket.
Result:
2 Tickets Sold
for:
1 Available Seat
Classic race condition.
Interview Trick Questions
Q1: Is count++ Thread-Safe?
❌ No.
Because it consists of:
Read
Modify
Write
operations.
Q2: Can Race Conditions Occur in Single-Threaded Programs?
❌ No.
Race conditions require:
Concurrent Execution
Q3: Which Keyword Helps Prevent Race Conditions?
synchronized
Q4: Which Class Can Replace int for Thread-Safe Counters?
AtomicInteger
Q5: Is HashMap Safe Against Race Conditions?
❌ No.
Use:
ConcurrentHashMap
instead.
Key Interview Points
- A race condition occurs when multiple threads access shared data concurrently and the result depends on execution timing.
- It commonly occurs with shared mutable variables.
- Operations like
count++are not atomic and can lead to race conditions. - Symptoms include incorrect results, lost updates, and inconsistent application state.
- Race conditions can be prevented using synchronization, locks, atomic classes, concurrent collections, and immutability.
AtomicIntegeris a common solution for thread-safe counters.ConcurrentHashMaphelps avoid race conditions in shared maps.- Race conditions are one of the most common concurrency bugs in Java.
One-Line Interview Answer
A race condition occurs when multiple threads simultaneously access and modify shared data, causing the program's outcome to depend on the unpredictable order of thread execution, which can lead to inconsistent or incorrect results.
49) What Are Atomic Classes in Java?
Definition
Atomic Classes are thread-safe classes provided by Java that allow atomic (indivisible) operations on single variables without using explicit synchronization.
They are available in:
java.util.concurrent.atomic
package.
Simple Definition
Atomic Classes provide
lock-free, thread-safe operations
on variables using atomic actions.
What Does Atomic Mean?
An operation is atomic if it:
Completes Entirely
OR
Does Not Happen At All
No thread can see the operation in a partially completed state.
Why Do We Need Atomic Classes?
Consider:
int count = 0;
count++;
This looks like one operation but internally it is:
1. Read count
2. Increment value
3. Write value back
In a multi-threaded environment:
Thread 1 → Read 5
Thread 2 → Read 5
Thread 1 → Write 6
Thread 2 → Write 6
Expected:
7
Actual:
6
This is a:
Race Condition
Traditional Solution
synchronized void increment() {
count++;
}
Works correctly but introduces:
Locking Overhead
Thread Blocking
Reduced Performance
Better Solution
AtomicInteger count =
new AtomicInteger(0);
count.incrementAndGet();
Thread-safe without explicit synchronization.
Package
java.util.concurrent.atomic
Common Atomic Classes
| Class | Purpose |
|---|---|
| AtomicInteger | Atomic integer operations |
| AtomicLong | Atomic long operations |
| AtomicBoolean | Atomic boolean operations |
| AtomicReference |
Atomic object reference |
| AtomicIntegerArray | Atomic integer array |
| AtomicLongArray | Atomic long array |
| AtomicStampedReference | Prevent ABA problem |
| AtomicMarkableReference | Atomic reference with mark |
AtomicInteger Example
Without AtomicInteger
int count = 0;
count++;
Not thread-safe.
With AtomicInteger
AtomicInteger count =
new AtomicInteger(0);
Increment:
count.incrementAndGet();
Example:
AtomicInteger counter =
new AtomicInteger(0);
counter.incrementAndGet();
System.out.println(
counter.get());
Output:
1
Common AtomicInteger Methods
get()
Returns current value.
counter.get();
set()
Updates value.
counter.set(100);
incrementAndGet()
Increment then return value.
counter.incrementAndGet();
Output:
1
getAndIncrement()
Return current value then increment.
counter.getAndIncrement();
Output:
0
Counter becomes:
1
decrementAndGet()
counter.decrementAndGet();
addAndGet()
counter.addAndGet(10);
Example:
AtomicInteger count =
new AtomicInteger(5);
System.out.println(
count.addAndGet(10));
Output:
15
AtomicLong Example
AtomicLong totalAmount =
new AtomicLong(1000);
totalAmount.addAndGet(500);
Output:
1500
AtomicBoolean Example
AtomicBoolean flag =
new AtomicBoolean(false);
flag.set(true);
System.out.println(
flag.get());
Output:
true
AtomicReference Example
Used for object references.
AtomicReference<String> name =
new AtomicReference<>("John");
name.set("Alex");
System.out.println(
name.get());
Output:
Alex
How Atomic Classes Work?
Internally they use:
CAS
(Compare-And-Swap)
operation.
Compare-And-Swap (CAS)
CAS performs:
Compare Current Value
If Match
→ Update Value
Else
→ Retry
Example:
Current = 10
Expected = 10
New Value = 20
Result:
Update Successful
If:
Current = 15
Expected = 10
Result:
Update Fails
Retry
compareAndSet()
Most important atomic operation.
Example:
AtomicInteger count =
new AtomicInteger(10);
boolean result =
count.compareAndSet(
10,
20);
Output:
true
Value becomes:
20
Example
AtomicInteger count =
new AtomicInteger(10);
boolean updated =
count.compareAndSet(
5,
20);
Output:
false
Value remains:
10
Atomic Classes vs Synchronization
Synchronization
synchronized void increment() {
count++;
}
Characteristics:
Uses Locks
Thread Blocking
Context Switching
Atomic Classes
counter.incrementAndGet();
Characteristics:
Lock-Free
Non-Blocking
High Performance
Comparison
| Feature | synchronized | Atomic Classes |
|---|---|---|
| Thread Safe | ✅ Yes | ✅ Yes |
| Uses Locks | ✅ Yes | ❌ No |
| Blocking | ✅ Yes | ❌ No |
| Performance | Lower | Higher |
| Context Switching | More | Less |
| Scalability | Moderate | Better |
Real-World Example
Visitor Counter
Bad:
int visitors = 0;
visitors++;
Race condition possible.
Good:
AtomicInteger visitors =
new AtomicInteger();
visitors.incrementAndGet();
Thread-safe.
Real-World Example
API Request Counter
AtomicLong requests =
new AtomicLong();
Each request:
requests.incrementAndGet();
Safe even with thousands of threads.
Limitations of Atomic Classes
Suitable for Single Variables
Good:
AtomicInteger
Not ideal for:
Multiple Variable Updates
Example:
balance
transactionCount
must be updated together.
Atomic classes alone are insufficient.
Use:
synchronized
Lock
instead.
Atomic Classes vs Volatile
Volatile
volatile int count;
Provides:
Visibility
only.
Not thread-safe for:
count++;
AtomicInteger
Provides:
Visibility
+
Atomicity
Volatile vs AtomicInteger
| Feature | volatile | AtomicInteger |
|---|---|---|
| Visibility | ✅ Yes | ✅ Yes |
| Atomic Operations | ❌ No | ✅ Yes |
| Thread Safe Increment | ❌ No | ✅ Yes |
| CAS Support | ❌ No | ✅ Yes |
Interview Trick Questions
Q1: Is count++ Atomic?
❌ No.
It consists of:
Read
Modify
Write
operations.
Q2: Which Package Contains Atomic Classes?
java.util.concurrent.atomic
Q3: Do Atomic Classes Use Locks?
❌ No.
They use:
CAS
(Compare-And-Swap)
Q4: Which Is Faster?
AtomicInteger
usually performs better than:
synchronized
for simple counters.
Q5: Can AtomicInteger Replace Synchronization Everywhere?
❌ No.
Only for operations involving a single variable.
Q6: What Is the Most Common Atomic Class?
AtomicInteger
Key Interview Points
- Atomic Classes provide lock-free, thread-safe operations on variables.
- They are located in the
java.util.concurrent.atomicpackage. - Common classes include
AtomicInteger,AtomicLong,AtomicBoolean, andAtomicReference. - They internally use CAS (Compare-And-Swap) operations.
- Atomic classes are generally faster than synchronized blocks for simple operations.
incrementAndGet()andcompareAndSet()are commonly used methods.- They help prevent race conditions without explicit locking.
- Atomic classes are best suited for single-variable thread-safe operations.
One-Line Interview Answer
Atomic Classes are lock-free, thread-safe utility classes in java.util.concurrent.atomic that use CAS (Compare-And-Swap) operations to perform atomic updates on variables without using explicit synchronization.
50) JVM vs JRE vs JDK in Java
One of the most frequently asked Core Java interview questions is:
What is the difference between JVM, JRE, and JDK?
The relationship can be summarized as:
JDK
└── JRE
└── JVM
or
JDK = JRE + Development Tools
JRE = JVM + Core Libraries
JVM = Runtime Engine
Quick Definitions
JVM (Java Virtual Machine)
The JVM is an abstract machine that executes Java bytecode.
Responsible For:
- Loading Classes
- Executing Bytecode
- Memory Management
- Garbage Collection
JRE (Java Runtime Environment)
The JRE provides everything needed to run Java applications.
Contains:
- JVM
- Core Java Libraries
- Supporting Files
JDK (Java Development Kit)
The JDK provides everything needed to develop, compile, and run Java applications.
Contains:
- JRE
- Development Tools
Big Picture
JDK
┌─────────────────────────┐
│ │
│ JRE │
│ ┌─────────────────────┐ │
│ │ JVM │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────┘
JVM (Java Virtual Machine)
Definition
JVM is the runtime engine that executes Java bytecode.
Example:
HelloWorld.class
is executed by:
JVM
Responsibilities of JVM
Class Loading
Loads .class files.
ClassLoader
Bytecode Execution
Converts bytecode into machine instructions.
Memory Management
Manages:
Heap
Stack
Method Area
Garbage Collection
Automatically removes unused objects.
Security
Provides sandbox execution.
JVM Workflow
Java Source Code
│
▼
javac Compiler
│
▼
Bytecode (.class)
│
▼
JVM
│
▼
Machine Code
Why JVM Is Important?
Because Java follows:
Write Once
Run Anywhere (WORA)
The same bytecode can run on:
Windows
Linux
macOS
provided a JVM exists.
JRE (Java Runtime Environment)
Definition
JRE is the environment required to run Java applications.
Components of JRE
JRE
├── JVM
├── Core Libraries
├── Runtime Classes
└── Supporting Files
Example
Suppose you have:
HelloWorld.class
To run it:
java HelloWorld
You need:
JRE
What JRE Does NOT Contain?
JRE does not include:
javac
javadoc
jar
debugging tools
Therefore:
Can Run Java
Cannot Develop Java
JDK (Java Development Kit)
Definition
JDK is a complete toolkit for Java developers.
Components of JDK
JDK
├── JRE
├── javac
├── java
├── jar
├── javadoc
├── jdb
└── Other Tools
What JDK Provides?
Compiler
javac
Converts:
Hello.java
into:
Hello.class
Runtime
java
Runs applications.
Documentation Tool
javadoc
Generates API documentation.
Archiving Tool
jar
Creates JAR files.
Debugger
jdb
Used for debugging.
Example Workflow
Step 1: Write Code
public class Hello {
public static void main(
String[] args) {
System.out.println(
"Hello Java");
}
}
Step 2: Compile
javac Hello.java
Produces:
Hello.class
Compiler comes from:
JDK
Step 3: Run
java Hello
Execution happens using:
JRE → JVM
Real-Life Analogy
Imagine:
Java Program = Food Recipe
JVM
Chef
Actually cooks the food.
JRE
Kitchen
Provides chef and cooking equipment.
JDK
Kitchen + Recipe Writing Tools
Allows creating and cooking recipes.
JVM vs JRE vs JDK
| Feature | JVM | JRE | JDK |
|---|---|---|---|
| Full Form | Java Virtual Machine | Java Runtime Environment | Java Development Kit |
| Purpose | Execute Bytecode | Run Java Programs | Develop + Run Java Programs |
| Contains JVM | N/A | ✅ Yes | ✅ Yes |
| Contains JRE | ❌ No | N/A | ✅ Yes |
| Compiler Included | ❌ No | ❌ No | ✅ Yes |
| Development Tools | ❌ No | ❌ No | ✅ Yes |
| Run Programs | ✅ Yes | ✅ Yes | ✅ Yes |
| Compile Programs | ❌ No | ❌ No | ✅ Yes |
Hierarchy
JDK
├── JRE
│ ├── JVM
│ └── Libraries
│
├── javac
├── jar
├── javadoc
└── jdb
Which One Do Developers Install?
Typically:
JDK
because it contains everything.
Which One Does End User Need?
If only running Java applications:
JRE
(Though modern Java distributions usually provide the JDK.)
Example Commands
Compile
javac Employee.java
Uses:
JDK
Run
java Employee
Uses:
JRE → JVM
Interview Trick Questions
Q1: Can JVM Compile Java Code?
❌ No.
Compilation is done by:
javac
from the JDK.
Q2: Can JRE Compile Java Programs?
❌ No.
JRE only runs programs.
Q3: Does JDK Contain JRE?
✅ Yes.
Q4: Does JRE Contain JVM?
✅ Yes.
Q5: Can a Java Program Run Without JVM?
❌ No.
The JVM is responsible for executing bytecode.
Q6: Which Component Enables "Write Once, Run Anywhere"?
✅ JVM
because platform-specific JVMs execute the same bytecode.
Key Interview Points
- JVM executes Java bytecode and manages memory, class loading, and garbage collection.
- JRE provides the environment required to run Java applications.
- JDK provides tools required to develop, compile, and run Java applications.
- JDK contains JRE, and JRE contains JVM.
javaccompiler is available only in the JDK.- JVM is responsible for Java's platform independence.
- JRE is sufficient for running applications but not for development.
- Developers generally install the JDK because it includes everything needed.
One-Line Interview Answer
JVM executes Java bytecode, JRE provides the runtime environment to run Java applications, and JDK provides the complete toolkit (JRE + development tools) required to develop, compile, and run Java applications.
Want a printable version?
If you want, I can also generate a PDF-style version (clean formatting) or split this into multiple posts (Core Java / Collections / Concurrency).