Java important concepts : For developers preparing for interview.
Java interview preparation should focus on core concepts like OOP principles (encapsulation, inheritance, polymorphism, abstraction), data types, control structures, exception handling, and collections. Understand JVM internals, memory management, garbage collection, and Java 8+ features like streams, lambdas, and functional interfaces. Be confident with multithreading, synchronization, and concurrency utilities. Master important APIs (Collections, IO/NIO, JDBC), design patterns, and object-oriented design principles (SOLID). For backend roles, understand Java frameworks like Spring (Core, Boot, MVC, Data, Security), Hibernate, RESTful services, and testing tools like JUnit. Knowledge of Maven/Gradle, Git, and databases (SQL/ORM) is also crucial for real-world development.
✅ Java 8 Features
- Support for Functional Programming
- Introduced Lambda Expressions and Functional Interfaces (e.g.,
Predicate,Consumer,Function, etc.)
- Introduced Lambda Expressions and Functional Interfaces (e.g.,
- Nashorn JavaScript Engine
- A new lightweight JavaScript runtime was introduced to execute JavaScript code on the JVM.
- Calling JavaScript from Java
- Java can now invoke JavaScript code using
ScriptEngineAPI and Nashorn engine.
- Java can now invoke JavaScript code using
- New Date-Time API
java.timepackage introduced (based on Joda-Time), offering classes likeLocalDate,LocalTime,ZonedDateTime, etc.
- Streams API
- Enables functional-style operations on collections using methods like
filter(),map(),reduce().
- Enables functional-style operations on collections using methods like
✅ JDK, JRE, and JVM
☕ JDK (Java Development Kit)
- A software development kit used to develop Java applications.
- Includes:
- JRE (Java Runtime Environment)
- Compiler (
javac) - Archiver (
jar) - Document Generator (
javadoc) - Debugger
- Interpreter/Loader
- It is the full-featured software kit required to develop and compile Java programs.
☕ JRE (Java Runtime Environment)
- Provides the runtime environment to execute Java bytecode.
- Includes:
- JVM (Java Virtual Machine)
- Core libraries
- Support files
- It does not include development tools like compiler or debugger.
- Used to run Java applications, not develop them.
☕ JVM (Java Virtual Machine)
- An abstract machine that enables your computer to run Java programs.
- Responsibilities:
- Converts bytecode into machine code (platform-dependent).
- Handles memory management, garbage collection, etc.
- Three key components of JVM:
- Specification – Defines how the JVM should work.
- Implementation – Actual software (like HotSpot, OpenJ9).
- Runtime Instance – Created each time a Java program is run.
✅ Java Compiler
🔧 JIT (Just-In-Time Compiler)
- Part of JVM that improves performance.
- Compiles bytecode into native machine code at runtime.
- First run compiles all, then only compiles modified code subsequently.
- Enhances performance by avoiding repeated compilation.
✅ Java Data Types
🔹 Primitive Data Types (8 total)
byte,short,int,longfloat,doublecharboolean
🔹 Reference Data Types
- Objects, Arrays, Strings, Interfaces, etc.
🔸 Note: Java is not 100% object-oriented because primitive types are not objects.
✅ Methods in the Object Class
These are the common methods inherited by all Java classes:
equals()hashCode()toString()getClass()wait()notify()notifyAll()
✅ Packages in Java
📦 What is a Package?
- A namespace that organizes a set of related classes and interfaces.
📦 Advantages:
- Organizes classes logically.
- Avoids naming conflicts.
- Provides access control and protection.
📦 How to Compile Java Packages:
javac -d <Destination_Folder> FileName.java
Example:
javac -d . Animal.java (same dir)
javac -d animals Animal.java (animals dir)
(Compiles and places class file in correct sub-package structure relative to the current directory)
✅ List vs Set in Java
| Feature | List | Set |
|---|---|---|
| Duplicates | Allows duplicate elements | Only stores unique elements |
| Order | Maintains insertion order (e.g., ArrayList, LinkedList) | Does not guarantee order (except LinkedHashSet or TreeSet) |
| Example Implementations | ArrayList, LinkedList, Vector | HashSet, LinkedHashSet, TreeSet |
✅ Java HashSet Example
javaCopyEditimport java.util.*;
class TestCollection {
public static void main(String args[]) {
// Creating HashSet and adding elements
HashSet<String> set = new HashSet<String>();
// For list comparison, uncomment the next line
// List<String> al = new ArrayList<String>();
set.add("Ravi");
set.add("Vijay");
set.add("Ravi"); // Duplicate, will not be added
set.add("Ajay");
// Traversing elements
Iterator<String> itr = set.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
Output (order may vary due to hashing):
nginxCopyEditRavi
Vijay
Ajay
✅ HashMap Overview
- HashMap stores data in key-value pairs:
HashMap<String, Integer> map = new HashMap<>();map.put("a", 1); map.put("b", 2); - Each key must be unique, but values can be duplicated.
- Not ordered; use
LinkedHashMaporTreeMapfor order-specific behavior.
✅ HashSet vs HashMap
| Feature | HashSet | HashMap |
|---|---|---|
| Purpose | Store unique values | Store key-value pairs |
| Structure | Set<E> | Map<K, V> |
| Example | {1, 2, 3} | {a → 1, b → 2} |
| Underlying | Uses HashMap<K, Object> internally | Built-in hash-based map |
✅ this Keyword in Java
- Refers to the current class object.
- Common uses:
- Resolve naming conflicts between class fields and constructor parameters.
- Invoke current class methods or constructors.
- Pass current object as an argument.
✅ Optional in Java 8
- A container object which may or may not contain a non-null value.
- Used to avoid
NullPointerException.
Example & Use Case:
import java.util.Optional;
public class JavaTester {
public static void main(String[] args) {
JavaTester javaTester = new JavaTester();
Integer value1 = null;
Integer value2 = 10;
Optional<Integer> a = Optional.ofNullable(value1); // can be null
Optional<Integer> b = Optional.of(value2); // must not be null
System.out.println("Sum is: " + javaTester.sum(a, b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b) {
System.out.println("First parameter is present: " + a.isPresent());
System.out.println("Second parameter is present: " + b.isPresent());
// Optional.orElse - return value if present, else return default
Integer value1 = a.orElse(0);
// Optional.get - get the value (must be present)
Integer value2 = b.get();
return value1 + value2;
}
}
Output:
First parameter is present: false
Second parameter is present: true
Sum is: 10
✅ JavaBeans
🔹 What is a JavaBean?
JavaBeans are Java classes that follow a specific coding convention and are used to encapsulate multiple objects into a single object (the bean).
🔹 JavaBean Conventions:
- All properties are private (use public getters/setters).
- Public no-argument constructor (required for instantiation).
- Implements
java.io.Serializable(to persist object state). - Provides public getter and setter methods for accessing properties.
public class Employee implements Serializable {
private String name;
private int id;
public Employee() {} // No-arg constructor
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
✅ Spring @Bean Annotation
@Beanis used in Spring Framework to declare a method as returning a bean object managed by Spring’s IoC container.- Typically used inside
@Configurationclasses.
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
A Spring Bean is any object that is instantiated, assembled, and managed by the Spring IoC container. These are core components in Spring applications and are created via Dependency Injection (DI).
✅ equals() and hashCode() Methods
equals(Object obj)
- Compares the current object with another for logical equality.
- By default, uses reference comparison (same memory address).
hashCode()
- Returns an integer representation of the object’s memory address.
- Should be overridden if
equals()is overridden. - Used in collections like
HashMap,HashSet.
✅ Serialization in Java
🔹 What is Serialization?
Serialization is the process of converting a Java object into a byte stream, so it can be:
- Persisted to a file or database
- Transmitted over a network
- Stored in memory (like caching)
- Sent via RMI (Remote Method Invocation)
🔹 What is Deserialization?
Deserialization is the reverse process of converting a byte stream back into a copy of the original object.
🔹 Why Use Serialization?
Serialization is mainly used for:
- Saving object state for later use
- Sending objects between JVMs (e.g., in distributed systems or remote communication)
- Caching or logging complex data
- Working with frameworks like Hibernate, JPA, Spring, etc.
🔹 How to Make a Class Serializable?
✅ Requirements:
- The class must implement
java.io.Serializableinterface. - All fields should be serializable (i.e., either primitives or Serializable objects).
- Mark non-serializable fields with the
transientkeyword (optional). - Provide a no-argument constructor (not mandatory but recommended).
✅ Simple Example:
import java.io.*;
class Student implements Serializable {
private static final long serialVersionUID = 1L; // recommended
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
🔹 Writing an Object to File (Serialization)
import java.io.*;
public class SerializeExample {
public static void main(String[] args) {
Student s = new Student(101, "Ravi");
try {
FileOutputStream fout = new FileOutputStream("student.ser");
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(s);
out.close();
fout.close();
System.out.println("Object Serialized Successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
}
🔹 Reading an Object from File (Deserialization)
import java.io.*;
public class DeserializeExample {
public static void main(String[] args) {
try {
FileInputStream fin = new FileInputStream("student.ser");
ObjectInputStream in = new ObjectInputStream(fin);
Student s = (Student) in.readObject();
in.close();
fin.close();
System.out.println("Deserialized Student:");
System.out.println("ID: " + s.id);
System.out.println("Name: " + s.name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
🔹 serialVersionUID – What and Why?
- A unique ID to verify the compatibility of sender and receiver classes during deserialization.
- If not specified and class changes, you may get
InvalidClassException.
private static final long serialVersionUID = 1L;
🔹 transient Keyword
If you don’t want a field to be serialized:
transient String password;
Transient fields are ignored during serialization.
🔹 static Fields and Serialization
- Static fields are not serialized, because they belong to the class, not to the object.
- Their values are not part of the object’s state.
🔹 Inheritance and Serialization
- If a superclass implements
Serializable, all its subclasses are automatically serializable. - If a subclass implements
Serializable, the superclass does not need to be Serializable unless it contains fields you want to serialize.
🔹 Custom Serialization: writeObject() and readObject()
You can customize the serialization process:
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // default behavior
// write custom logic
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // default behavior
// read custom logic
}
🔹 Externalizable Interface
An advanced alternative to Serializable:
class Employee implements Externalizable {
int id;
String name;
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readInt();
name = (String) in.readObject();
}
}
You have full control over serialization behavior with
Externalizable.
✅ Summary: Key Points for Interview
| Feature | Serializable |
|---|---|
| Interface | Marker Interface (no methods) |
| Control | JVM handles serialization |
| Versioning | Use serialVersionUID |
| transient | Used to exclude fields |
| static | Not serialized |
| Custom logic | Use writeObject / readObject |
| Alternative | Externalizable (full control |
✅ Scanner Class (Reading Input)
import java.util.Scanner;
public class InputExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String myString = scanner.next();
int myInt = scanner.nextInt();
scanner.close();
System.out.println("myString is: " + myString);
System.out.println("myInt is: " + myInt);
}
}
✅ Access Control Modifiers
| Modifier | Visibility |
|---|---|
default | Within the same package (no keyword) |
private | Within the same class only |
public | Everywhere |
protected | Same package + subclasses |
✅ Non-Access Modifiers
| Modifier | Usage |
|---|---|
static | Belongs to the class, not instance |
final | Constant (var), cannot be overridden (method), no inheritance (class) |
abstract | Declares abstract methods/classes |
transient | Prevents fields from being serialized |
synchronized | Prevents concurrent access by multiple threads |
volatile | Ensures value is always read from main memory (not cached) |
✅ Process vs Thread
| Concept | Process | Thread |
|---|---|---|
| Definition | Running instance of a program | A lightweight sub-process within a process |
| Memory | Each process has separate memory | Threads share memory of the process |
| Overhead | High | Low |
| Communication | Difficult | Easy (shared memory) |
✅ Wrapper Classes & Autoboxing
- Wrapper classes convert primitives into objects:
int→Integer,float→Float, etc.
int i = 10;
// Boxing
Integer bx = new Integer(i);
// Unboxing
int unbx = bx.intValue();
// Autoboxing
Integer abx = i;
// Auto-unboxing
int j = abx;
✅ final, finally, finalize
| Keyword | Purpose |
|---|---|
final | Used for constants, final methods (can’t override), final classes (can’t extend) |
finally | Block that always executes after try-catch (even if exception occurs) |
finalize() | Method called by Garbage Collector before destroying object (deprecated in recent versions) |
// final
final int x = 5;
// finally
try {
// code
} catch (Exception e) {
// handler
} finally {
System.out.println("Always runs.");
}
// finalize (not recommended)
@Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
}
System.gc()can suggest garbage collection, but it’s not guaranteed.
✅ String vs StringBuffer vs StringBuilder
| Feature | String | StringBuffer | StringBuilder |
|---|---|---|---|
| Mutability | Immutable | Mutable | Mutable |
| Thread Safety | Not thread-safe | Thread-safe (synchronized) | Not thread-safe |
| Performance | Low (creates new obj) | Slower (due to synchronization) | Faster (no synchronization overhead) |
| Reverse Method | ❌ No reverse() | ✅ Has reverse() | ✅ Has reverse() |
Code Example:
String str = new String("hi");
StringBuffer buffer = new StringBuffer("hi");
StringBuilder builder = new StringBuilder("hi");
System.out.println(str + " hello"); // Output: hi hello
System.out.println(buffer + " hello"); // Output: hi hello
System.out.println(builder + " hello"); // Output: hi hello
✅ Heap vs Stack Memory in Java
| Feature | Stack Memory | Heap Memory |
|---|---|---|
| Scope | Local variables, method calls | All objects, class instances |
| Lifetime | Until method/thread execution ends | Until object is garbage collected |
| Access | One thread only | Shared across all threads |
| Speed | Fast (LIFO based) | Slower than stack |
| Memory Management | Handled via LIFO structure | Managed by Garbage Collector |
| Usage | Stores primitive values and references to objects | Stores actual objects |
✅ Example:
Point p = new Point(1, 2);
// 'p' is in stack (reference), object (Point) is in heap
Stack memory is a part of JVM memory used to store:
1. Local Method Data (Local Variables)
- Any variable declared inside a method.
- For example:
void display() {
int a = 10; // 'a' is stored in stack
String msg = "Hi"; // 'msg' reference is in stack
}
Here:
a(primitive) is directly in the stack.msg(object reference) is in the stack, but the actual"Hi"object is stored in the heap.
2. Function Calls (Method Call Stack Frames)
- Each time a method is called, a new block (stack frame) is pushed onto the call stack.
- When the method finishes, that frame is popped off.
void methodA() {
methodB();
}
void methodB() {
// execution here
}
- Call to
methodA()→ stack frame created methodA()callsmethodB()→ new stack frame pushed- Once
methodB()ends → its frame is removed
3. Reference Variables
- Stack holds the references (addresses) to objects that are stored in the heap.
Point p = new Point(1, 2);
pis a reference variable — stored in stacknew Point(1, 2)creates an object — stored in heap
✅ ArrayList vs Vector
| Feature | ArrayList | Vector |
|---|---|---|
| Synchronization | Not synchronized | ✅ Synchronized (thread-safe) |
| Performance | Faster (no locking overhead) | Slower (due to locking) |
| Growth Rate | Increases size by 50% | Doubles the size (100%) |
| Traversal | Only Iterator | Iterator and Enumeration |
| Legacy Status | Part of Java Collections Framework | Legacy class from earlier Java |
🔸 Using Iterator with both ArrayList and Vector
import java.util.*;
public class ListIteratorExample {
public static void main(String[] args) {
// Using ArrayList
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
System.out.println("Iterating ArrayList using Iterator:");
Iterator<String> itr1 = arrayList.iterator();
while (itr1.hasNext()) {
System.out.println(itr1.next());
}
// Using Vector
Vector<String> vector = new Vector<>();
vector.add("Dog");
vector.add("Elephant");
vector.add("Fox");
System.out.println("\nIterating Vector using Iterator:");
Iterator<String> itr2 = vector.iterator();
while (itr2.hasNext()) {
System.out.println(itr2.next());
}
}
}
🔸 Using Enumeration (Only for Vector)
import java.util.*;
public class VectorEnumerationExample {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("Red");
vector.add("Green");
vector.add("Blue");
System.out.println("Iterating Vector using Enumeration:");
Enumeration<String> en = vector.elements();
while (en.hasMoreElements()) {
System.out.println(en.nextElement());
}
}
}
✅ HashMap vs Hashtable
| Feature | HashMap | Hashtable |
|---|---|---|
| Synchronization | ❌ Not synchronized (not thread-safe) | ✅ Synchronized (thread-safe) |
| Performance | Faster | Slower |
| Null Keys/Values | Allows 1 null key and multiple null values | ❌ No null keys or values allowed |
| Traversal | Iterator | Iterator and Enumeration |
| Parent Class | AbstractMap | Dictionary (legacy) |
| Thread-Safe Alternative | Use Collections.synchronizedMap() | Always synchronized |
| Modern Use | Preferred in single-threaded apps | Mostly avoided in new codebases |
✅ Quick Notes for Interviews
String: Immutable, good for constant values.StringBuffer: Thread-safe, use in multithreaded apps.StringBuilder: Fastest for string manipulation in single-threaded apps.Stack: Stores local method data, function calls, reference variables.Heap: Stores all objects and class-level fields.ArrayList: Use when thread safety is not a concern.Vector: Only if you need synchronization (legacy alternative).HashMap: Best for performance; use wrapper if thread safety is needed.Hashtable: Rarely used now; replaced by concurrent collections.
✅ Three Different Synchronized Map Implementations in Java
1️⃣ Hashtable – Legacy Synchronized Map
🔹 Description:
- An early thread-safe
Mapimplementation. - All methods are synchronized, making it inherently thread-safe.
- Extends the obsolete
Dictionaryclass.
🔹 Example:
Map<String, String> map = new Hashtable<>();
map.put("A", "Apple");
🔹 Pros:
- Thread-safe by design.
- Easy to use for single-threaded or legacy systems.
🔹 Cons:
- Synchronized at method level, leading to performance bottlenecks.
- Slower under concurrent access.
- Considered legacy — discouraged in modern applications.
✅ Use When:
- Working with very old or legacy code.
- Need basic synchronization with minimal effort.
2️⃣ Collections.synchronizedMap(Map<K,V>) – Synchronized Wrapper
🔹 Description:
- Wraps any standard
Map(likeHashMap,TreeMap) to make it thread-safe. - Synchronization is applied to every method call.
🔹 Example:
Map<String, String> map = new HashMap<>();
Map<String, String> syncMap = Collections.synchronizedMap(map);
// Required for thread-safe iteration
synchronized (syncMap) {
for (Map.Entry<String, String> entry : syncMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
🔹 Pros:
- Can convert any map into a synchronized one.
- Useful for basic thread-safety requirements.
🔹 Cons:
- Blocks the entire map for each operation → slower.
- Must manually synchronize during iteration.
- Not designed for high-concurrency environments.
✅ Use When:
- You need to ensure data consistency across threads.
- You’re working with existing unsynchronized maps and need a quick fix for thread safety.
3️⃣ ConcurrentHashMap – High-Performance Thread-Safe Map
🔹 Description:
- Part of
java.util.concurrentpackage. - Allows concurrent access by multiple threads without blocking.
- Uses bucket-level locking or lock-striping.
🔹 Example:
ConcurrentHashMap<String, String> cmap = new ConcurrentHashMap<>();
cmap.put("A", "Apple");
🔹 Pros:
- Highly efficient in multi-threaded environments.
- Allows concurrent read/write operations.
- No need for external synchronization.
🔹 Cons:
- Does not allow null keys or null values.
- Slightly more complex than basic maps.
✅ Use When:
- Performance is critical and the application is highly concurrent.
- Threads read/write frequently and simultaneously.
🧠 Summary Comparison Table
| Feature | Hashtable | Collections.synchronizedMap() | ConcurrentHashMap |
|---|---|---|---|
| Thread Safety | ✅ Yes | ✅ Yes | ✅ Yes |
| Synchronization Level | Method-level | Entire Map | Bucket-level (fine-grained) |
| Performance | ❌ Low | ❌ Low to Medium | ✅ High |
Allows null keys/values | ❌ No | ✅ Yes (if base map allows) | ❌ No |
| Iteration Safety | ✅ Yes | ❌ No (manual sync required) | ✅ Weakly consistent |
| Legacy Status | Legacy (Discouraged) | Intermediate | Preferred (Modern) |
| Ideal Use Case | Old codebases | Light concurrency | Heavy concurrency |
Instead of locking the entire map (like Hashtable or Collections.synchronizedMap()), ConcurrentHashMap only locks a small portion (called a bucket) of the map during updates (like put(), remove()).
✅ This means multiple threads can operate on different buckets at the same time — improving performance and reducing contention.
🧠 Example Analogy:
Think of a bank with 10 counters (buckets).
If all customers had to go to a single counter (like in Hashtable), it creates a long queue.
But with one counter per customer type (like in ConcurrentHashMap), many customers can be served in parallel.
✅ String Comparison in Java
Java provides three main ways to compare strings:
🔹 1. == Operator
- Compares object references, not actual values.
- Returns
trueif both references point to the same object in memory.
✅ Example:
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false (different objects)
System.out.println(s1.equals(s2)); // true (same value)
🧠 Use
==only to compare reference identity, not content.
🔹 2. .equals() Method
- Compares the contents (values) of the two strings.
- Returns
trueif values are equal.
✅ Example:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1.equals(s2)); // true
✅ Use this for value comparison.
🔹 3. .compareTo() Method
- Lexicographically compares two strings.
- Returns:
0→ if strings are equal> 0→ if first string is greater< 0→ if first string is smaller
✅ Example:
String s1 = "Sachin";
String s2 = "Sachin";
String s3 = "Ratan";
System.out.println(s1.compareTo(s2)); // 0
System.out.println(s1.compareTo(s3)); // > 0 (positive)
System.out.println(s3.compareTo(s1)); // < 0 (negative)
✅ Case-Insensitive Comparison:
s1.equalsIgnoreCase(s2); // Ignores case
✅ Abstract Class vs Interface in Java
| Feature | Abstract Class | Interface |
|---|---|---|
| Method Types | Can have abstract & non-abstract methods | Only abstract methods (Java 8+ allows default, static) |
| Inheritance Support | Single inheritance only | Supports multiple inheritance |
| Variables | Can have any type (static, non-static, final) | Only public static final (constants) |
| Declaration | abstract keyword | interface keyword |
| Extends / Implements | Extended using extends | Implemented using implements |
| Can Extend | Can extend a class and implement interfaces | Can only extend other interfaces |
| Access Modifiers | Can be private, protected, etc. | All methods are implicitly public |
| Constructors | Can have constructors | ❌ Cannot have constructors |
✅ Example – Abstract Class
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double radius;
Circle(double r) { radius = r; }
double area() { return Math.PI * radius * radius; }
}
✅ Example – Interface
interface Drawable {
void draw();
}
class Rectangle implements Drawable {
public void draw() {
System.out.println("Drawing rectangle");
}
}
✅ Why default Methods Were Introduced
Prior to Java 8:
- Interfaces could only declare methods (no implementations).
- If a method needed to be added to an interface, all implementing classes had to override it, breaking backward compatibility.
From Java 8 onward:
- You can provide default implementations inside interfaces.
- This enables adding new methods to interfaces without breaking existing code.
- Backward Compatibility: Add functionality to interfaces without breaking existing implementations.
- Code Reuse: Avoid boilerplate in every implementing class.
- Flexible Design: Classes can still override the default if needed.
❗ Important Notes
- If a class implements two interfaces with the same default method, it must override the method to resolve conflict.
🧠 Summary:
| Use | When to Use |
|---|---|
| Abstract Class | When you need base class logic + abstraction. |
| Interface | When you want to define only behavior/contract (especially for multiple classes). |
✅ Java Concepts: Overriding, Overloading, Method Hiding, Servlets, Wrapper Classes
🔹 1. Method Overriding – Runtime Polymorphism
✅ Definition:
Overriding means defining a method in the subclass that already exists in the superclass with same signature.
- Achieved via inheritance
- Decided at runtime
- Enables dynamic method dispatch
✅ Example:
class Car {
void run() {
System.out.println("Car is running");
}
}
class Test extends Car {
void run() {
System.out.println("Audi is running safely with 100km");
}
public static void main(String[] args) {
Car b = new Test(); // upcasting
b.run(); // Output: Audi is running safely with 100km
}
}
Upcasting is the process of assigning a subclass object to a superclass reference.
✅ It is automatic and safe, because every subclass object is-a superclass object.
To access subclass-specific methods, you must downcast:
Test d = (Dog) a; // Downcasting (explicit)
d.run();
🔹 2. Method Overloading – Compile-Time Polymorphism
✅ Definition:
Method overloading means defining multiple methods with same name but different parameter lists in the same class.
- Decided at compile-time
✅ Example:
class Test {
static int add(int a, int b) {
return a + b;
}
static double add(double a, double b) {
return a + b;
}
public static void main(String args[]) {
System.out.println(Test.add(11, 11)); // 22
System.out.println(Test.add(12.3, 12.6)); // 24.9
}
}
🔹 3. Method Hiding
✅ Definition:
If a static method is defined in both superclass and subclass with the same signature, then the subclass method hides the superclass method — it’s not overriding.
Also:
privateandfinalmethods cannot be overridden- Trying to override a private method simply hides it
❌ Example (Fails due to private):
class Parent {
private void display() {
System.out.println("Parent method");
}
}
class Child extends Parent {
public void display() {
System.out.println("Child method");
}
public static void main(String[] args) {
Parent obj = new Child();
obj.display(); // ❌ Compile-time error: display() not visible in Parent
}
}
🔹 4. Servlets in Java
✅ What is a Servlet?
A Java class that runs on a web server and handles HTTP requests and responses.
✅ Servlet Lifecycle Methods:
| Stage | Method |
|---|---|
| Loading | – |
| Instantiation | Constructor |
| Initialization | init() |
| Request handling | service() |
| Destruction | destroy() |
✅ Packages:
javax.servlet.*javax.servlet.http.*
🔹 ServletConfig vs ServletContext
| Feature | ServletConfig | ServletContext |
|---|---|---|
| Scope | Specific to one servlet | Shared across entire application |
| Purpose | Servlet-specific config | Application-wide parameters |
🔹 RequestDispatcher Interface
Used to forward or include another resource (JSP/HTML/Servlet).
RequestDispatcher rd = request.getRequestDispatcher("next.jsp");
rd.forward(request, response); // Forwards to another resource
rd.include(request, response); // Includes content of another resource
🔹 5. int vs Integer (Wrapper Classes)
| Feature | int | Integer (Wrapper Class) |
|---|---|---|
| Type | Primitive | Object (inherits from Object) |
| Stored as | Binary value | Object wrapping primitive |
| Nullable | ❌ No | ✅ Yes |
| Method Access | ❌ No methods | ✅ Yes (e.g., parseInt()) |
✅ Example:
int i = Integer.parseInt("10"); // returns primitive int
Integer obj = Integer.valueOf(10); // returns Integer object
✅ Every primitive type has a wrapper class:
| Primitive | Wrapper |
|———–|————|
|int|Integer|
|char|Character|
|double|Double|
|float|Float|
|long|Long|
|byte|Byte|
|boolean|Boolean|
|short|Short|
🔹 6. Static Block vs Instance Block
| Type | Static Block | Instance Block |
|---|---|---|
| When Called | When class is loaded | Every time object is created |
| Syntax | static { ... } | { ... } (non-static block) |
| Use Case | Initialize static variables | Common code for constructors |
✅ Example:
class Hello {
static {
System.out.println("Static block"); // Executes once when class is loaded
}
{
System.out.println("Instance block"); // Executes each time object is created
}
public static void main(String[] args) {
Hello h1 = new Hello(); // triggers instance block
Hello h2 = new Hello(); // triggers instance block again
}
}
✅ Why Hibernate over JDBC?
| Feature | JDBC | Hibernate |
|---|---|---|
| Boilerplate Code | Required (manual query, connection, etc.) | ✅ Removes boilerplate via ORM |
| Object-Oriented Mapping | ❌ Manual mapping | ✅ Supports inheritance, association, collection mapping |
| Transactions | Manual | ✅ Automatic transaction management |
| Caching | ❌ Not built-in | ✅ Built-in caching improves performance |
| Query Language | SQL | ✅ HQL (Hibernate Query Language) — object-oriented |
| Data Handling | Procedural | ✅ Uses Java objects (POJOs) |
✅ Java Collections Framework
➤ A Collection is a group of objects, represented as a single unit (like a list, set, or queue).
Key operations: add, remove, traverse, search, sort.
🔷 Hierarchy Overview:
Iterable
└── Collection
├── List
├── Set
└── Queue
✅ List Interface
- Ordered, allows duplicates
- Maintains insertion order
Implementations:
| Class | Synchronized | Internals | Use-case |
|---|---|---|---|
ArrayList | ❌ No | Dynamic array | Fast random access, slow insert/delete |
LinkedList | ❌ No | Doubly linked list | Fast insert/delete, slow access |
Vector | ✅ Yes | Dynamic array | Thread-safe but slower |
Stack | ✅ Yes | Extends Vector | LIFO operations |
List<String> list1 = new ArrayList<>();
List<String> list2 = new LinkedList<>();
List<String> list3 = new Vector<>();
List<String> list4 = new Stack<>();
✅ Set Interface
- Unordered, does not allow duplicates
Implementations:
| Class | Ordered | Unique Elements | Notes |
|---|---|---|---|
HashSet | ❌ No | ✅ Yes | Fast, no order, allows one null |
LinkedHashSet | ✅ Yes | ✅ Yes | Maintains insertion order |
TreeSet | ✅ Sorted | ✅ Yes | Sorted (natural order), no nulls allowed |
Set<String> set1 = new HashSet<>();
Set<String> set2 = new LinkedHashSet<>();
Set<String> set3 = new TreeSet<>();
✅ Queue Interface
- FIFO (First-In-First-Out) structure
Implementations:
| Class | Ordered | Notes |
|---|---|---|
PriorityQueue | ✅ Yes | Elements processed by priority; no nulls |
ArrayDeque | ✅ Yes | Allows add/remove from both ends; faster than Stack/ArrayList |
Queue<String> q1 = new PriorityQueue<>();
Queue<String> q2 = new ArrayDeque<>();
✅ Iterator Interface
Used for traversing collections.
3 Methods:
hasNext() // returns true if next element exists
next() // returns the next element
remove() // removes the current element (optional)
✅ List vs Set vs Queue
| Feature | List | Set | Queue |
|---|---|---|---|
| Duplicates | ✅ Allowed | ❌ Not allowed | ✅ Allowed |
| Order | ✅ Maintained | ❌ Unordered (HashSet) | ✅ FIFO (Queue), LIFO (Stack) |
| Nulls Allowed | ✅ Yes (1+) | ✅ (max one in HashSet) | ❌ Not in PriorityQueue |
✅ ArrayList vs LinkedList
| Feature | ArrayList | LinkedList |
|---|---|---|
| Internals | Dynamic array | Doubly linked list |
| Access Speed | ✅ Fast (index-based) | ❌ Slow (linear traversal) |
| Insertion/Deletion | ❌ Slow (shift required) | ✅ Fast (just node pointers) |
| Acts As | Only List | List + Queue |
| Nulls | ✅ Allowed | ✅ Allowed |
✅ List A = new ArrayList<>(); vs ArrayList A = new ArrayList<>();
| Declaration | Explanation |
|---|---|
List<String> A = new ArrayList<>(); | ✅ Best practice — flexible, code to interface |
ArrayList<String> A = new ArrayList<>(); | Tied to specific implementation — less flexible |
✅ Internal Working of HashMap in Java
HashMap<String, Integer> hm = new HashMap<>();
hm.put("Run", 572);
🔹 Step-by-Step Working
1. HashCode Generation
- Java calls
key.hashCode()on"Run", which returns an integer hash code. - For example:
"Run".hashCode() => 773737773
2. Index Calculation (Bucket Selection)
- The
HashMapuses the hash code to compute an index in the internal array (called buckets). - Formula:
index = (n - 1) & hashWherenis the current capacity of the HashMap (default is 16), and&is bitwise AND. E.g.:773737773 & (16 - 1) = index 5 (say)
hash = 773737773 = 0010 1111 1110 1001 1010 1011 1101 1101
mask = 15 = 0000 0000 0000 0000 0000 0000 0000 1111
---------------------------------------------------------
result = 0000 0000 0000 0000 0000 0000 0000 1101 = 13
bucketIndex = 773737773 & 15 = 13
3. Node Creation and Storage
- The key-value pair (
"Run"→572) is stored at the computed bucket index. - Internally, it’s stored as an object like:
Node(hash=773737773, key="Run", value=572, next=null)
4. Handling Collisions
If another key generates the same index, a collision happens.
Example:
hm.put("Run2", 3333); // Also hashes to index 5
- Collision resolution is handled by linked list or tree (since Java 8) at that bucket:
index 5: → Node(hash1, "Run", 572) → Node(hash2, "Run2", 3333)
🔍 get("Run") – Retrieval Process
- Hash code of
"Run"is calculated again. - Index is computed.
- HashMap goes to that bucket index.
- It traverses the list (or tree) comparing both:
- hash value
.equals()on the key
- Returns the value once match is found.
🔧 Java 8+ Improvement:
- When a linked list exceeds threshold (8 nodes) at a bucket, it converts it to a Red-Black Tree to improve lookup performance from O(n) to O(log n).
🔁 Summary Diagram:
Bucket Array (default size 16)
Index: 0 1 2 3 4 5 6 ...
- - - - - [Run|572] → [Run2|3333]
📌 Important Notes:
- Keys must implement
hashCode()andequals()properly. HashMapallows one null key and multiple null values.- Not thread-safe. Use
ConcurrentHashMapfor thread safety.
✅ Java 8 Stream API
🔷 What is a Stream?
A Stream in Java is a sequence of data (objects) from a source (like a Collection, Array, or I/O channel) that supports aggregate operations like filter, map, reduce, collect, etc.
Think of a stream like a data pipeline that transforms data step by step.
🔷 Why Streams?
✅ Traditional (Imperative) Style:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = new ArrayList<>();
for (Integer i : list) {
squares.add(i * i);
}
✅ Stream (Declarative) Style:
List<Integer> squares = list.stream().map(i -> i * i).collect(Collectors.toList());
🔷 Key Characteristics
- No storage: It doesn’t hold data. It pulls data from a source.
- Laziness: Intermediate operations are lazy (executed only when terminal op is called).
- Functional: Supports lambda expressions and functional-style operations.
- Chained operations: Can chain multiple operations together.
🔷 Stream Creation
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // Sequential
Stream<String> parallelStream = list.parallelStream(); // Parallel
🔷 Core Stream Operations
✅ Intermediate (return a stream)
filter(Predicate<T>)– filters elementsmap(Function<T, R>)– transforms elementsdistinct()– removes duplicatessorted()– sorts the streamlimit(n)– restricts to firstnelements
✅ Terminal (consumes stream)
forEach()– processes each elementcollect()– gathers elements into list/set/mapcount()– counts elementsreduce()– reduces to a single result (sum, min, etc.)anyMatch(),allMatch()– matching criteria
🔷 Examples with Output
✅ 1. Square & Distinct
List<Integer> numbers = Arrays.asList(6, 2, 2, 3, 7, 3, 5);
numbers.stream().map(i -> i * i).distinct().forEach(System.out::println);
🔸 Output:
36
4
9
49
25
✅ 2. Count Empty Strings
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jki");
long count = strings.stream().filter(String::isEmpty).count();
System.out.println(count);
🔸 Output:
2
✅ 3. Limit & Sort Random Integers
new Random().ints().limit(5).sorted().forEach(System.out::println);
🔸 Output (varies):
-1663187973
-1209031040
-707437559
408095708
1167106420
✅ 4. Parallel Stream
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
long count = strings.parallelStream().filter(String::isEmpty).count();
System.out.println(count);
🔸 Output:
2
✅ 5. Collectors (toList & joining)
List<String> strings = Arrays.asList("abc", "bc", "efg", "abcd", "jkl");
List<String> filtered = strings.stream()
.filter(s -> s.length() > 2)
.collect(Collectors.toList());
System.out.println("Filtered: " + filtered);
String merged = strings.stream().collect(Collectors.joining(", "));
System.out.println("Merged: " + merged);
🔸 Output:
Filtered: [abc, efg, abcd, jkl]
Merged: abc, bc, efg, abcd, jkl
✅ 6. Summary Statistics
List<String> list = Arrays.asList("3", "6", "8", "14", "15");
IntSummaryStatistics stats = list.stream()
.mapToInt(Integer::parseInt)
.summaryStatistics();
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
🔸 Output:
Max: 15
Min: 3
Sum: 46
Average: 9.2
🔷 Parallel vs Sequential Stream
| Feature | stream() | parallelStream() |
|---|---|---|
| Execution | Single-threaded | Multi-threaded |
| Performance | Slower on big data | Faster on big data (CPU-bound) |
| Thread-safe required? | No | Yes (use Concurrent Collections) |
| Use case | Simpler tasks | Heavy CPU tasks (e.g., processing large JSON) |
🔷 When to Use Streams
✅ Use Streams when:
- You want concise and readable code
- You’re doing collection transformations
- You want to avoid for-loops
❌ Avoid Streams when:
- You need indexed access
- You need side effects or complex stateful logic
✅ Summary
| Concept | Description |
|---|---|
| Stream | Abstraction for processing sequences of data |
| Intermediate Ops | map, filter, sorted, limit |
| Terminal Ops | forEach, collect, count, reduce |
| Collectors | toList, toSet, joining, groupingBy |
| Parallel Streams | Use for large data processing |
✅ Employee Class Definition. (Stram example)
class Employee {
int id;
String name;
int age;
String sex;
String dept;
double salary;
public Employee(int id, String name, int age, String sex, String dept, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.dept = dept;
this.salary = salary;
}
@Override
public String toString() {
return id + " - " + name + " (" + sex + ", " + age + ") - " + dept + " - ₹" + salary;
}
}
✅ Sample Employee List
List<Employee> employees = Arrays.asList(
new Employee(1, "Alice", 30, "Female", "HR", 50000),
new Employee(2, "Bob", 45, "Male", "IT", 90000),
new Employee(3, "Charlie", 25, "Male", "IT", 60000),
new Employee(4, "Diana", 35, "Female", "Finance", 70000),
new Employee(5, "Ethan", 28, "Male", "HR", 45000),
new Employee(6, "Fiona", 50, "Female", "Finance", 80000)
);
✅ Stream-Based Operations
🔹 A) Filter all employees whose salary > 60,000
List<Employee> highEarners = employees.stream()
.filter(e -> e.salary > 60000)
.collect(Collectors.toList());
highEarners.forEach(System.out::println);
📌 Output:
2 - Bob (Male, 45) - IT - ₹90000.0
4 - Diana (Female, 35) - Finance - ₹70000.0
6 - Fiona (Female, 50) - Finance - ₹80000.0
🔹 B) Count number of Male and Female employees
Map<String, Long> genderCount = employees.stream()
.collect(Collectors.groupingBy(e -> e.sex, Collectors.counting()));
System.out.println(genderCount);
📌 Output:
{Female=3, Male=3}
🔹 C) Average salary by department
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(e -> e.dept, Collectors.averagingDouble(e -> e.salary)));
System.out.println(avgSalaryByDept);
📌 Output:
{Finance=75000.0, IT=75000.0, HR=47500.0}
🔹 D) Highest paid employee
Optional<Employee> highestPaid = employees.stream()
.max(Comparator.comparingDouble(e -> e.salary));
highestPaid.ifPresent(System.out::println);
📌 Output:
2 - Bob (Male, 45) - IT - ₹90000.0
🔹 E) Group employees by department
Map<String, List<Employee>> deptEmployees = employees.stream()
.collect(Collectors.groupingBy(e -> e.dept));
deptEmployees.forEach((dept, list) -> {
System.out.println(dept + ":");
list.forEach(System.out::println);
});
📌 Output (formatted):
HR:
1 - Alice (Female, 30) - HR - ₹50000.0
5 - Ethan (Male, 28) - HR - ₹45000.0
IT:
2 - Bob (Male, 45) - IT - ₹90000.0
3 - Charlie (Male, 25) - IT - ₹60000.0
Finance:
4 - Diana (Female, 35) - Finance - ₹70000.0
6 - Fiona (Female, 50) - Finance - ₹80000.0
🔹 F) Names of all employees above age 30
List<String> names = employees.stream()
.filter(e -> e.age > 30)
.map(e -> e.name)
.collect(Collectors.toList());
System.out.println(names);
📌 Output:
[Alice, Bob, Diana, Fiona]
✅ Bonus: Salary Summary Statistics
DoubleSummaryStatistics salaryStats = employees.stream()
.mapToDouble(e -> e.salary)
.summaryStatistics();
System.out.println("Max: " + salaryStats.getMax());
System.out.println("Min: " + salaryStats.getMin());
System.out.println("Sum: " + salaryStats.getSum());
System.out.println("Average: " + salaryStats.getAverage());
📌 Output:
Max: 90000.0
Min: 45000.0
Sum: 395000.0
Average: 65833.33
🔷 What is a ClassLoader in Java?
Definition:
A ClassLoader in Java is a part of the Java Runtime Environment that dynamically loads Java classes into memory during runtime. Classes are not loaded until they are referenced for the first time.
Main ClassLoader types:
- Bootstrap ClassLoader – loads core Java classes from
rt.jar. - Extension ClassLoader – loads classes from the
extdirectory (jre/lib/ext). - System/Application ClassLoader – loads classes from the application’s classpath.
Core method:
javaCopyEditClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass("com.example.MyClass");
✅ What is Caching?
Caching is a performance enhancement technique. It stores frequently used data in a temporary storage (memory) so that future requests for that data are served faster.
🔹 Benefits:
- Reduces database hits
- Improves response time
- Reduces server load
🔹 Types of Caching:
| Type | Description |
|---|---|
| In-memory | Stores data in memory (e.g. HashMap, EhCache, Redis) |
| Database Caching | Used in ORM like Hibernate (Session, 2nd level) |
| Web Server Caching | Static content like CSS, JS, images |
🔷 How to Implement Caching in Spring Boot?
✅ Step-by-step:
- Enable caching:
@SpringBootApplication
@EnableCaching
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
- Use
@Cacheable:
@Cacheable(value = "books", condition = "#name.length() < 50")
public Book findBook(String name) {
// Simulate DB call
return bookRepo.findByName(name);
}
- Evicting cache:
@CacheEvict(value = "books", allEntries = true)
public void clearCache() {
// Clears all books cache
}
🔷 Hibernate Caching Levels
Hibernate provides three levels of caching:
1️⃣ First-Level Cache (Session Cache)
- Enabled by default
- Works per session (same session = same cached object)
- Doesn’t require configuration
2️⃣ Second-Level Cache
- Caches objects across sessions (application level)
- Needs config and external provider (e.g., EhCache, Infinispan, Redis)
- Example:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
3️⃣ Query Cache
- Caches query results (not just entities)
- Needs
@Cacheableannotations on queries
✅ WAR vs EAR
| Format | Use Case | Contains |
|---|---|---|
| WAR (Web Application Archive) | For web apps | JSP, Servlets, static files |
| EAR (Enterprise Archive) | For enterprise apps | Can include WARs, JARs, EJBs |
🔷 Class File Conflict in Two JARs
Q: If a class is present in two JARs in the classpath, which one is used?
✅ Answer: The class from the first JAR in the classpath order is used. ClassLoader searches sequentially and loads the first match.
java -cp jar1.jar:jar2.jar MyApp # Class from jar1 will be loaded
📌 In WAR or EAR files:
WEB-INF/classesis loaded beforeWEB-INF/lib/*.jar- Result may vary between servers and startups due to classloader hierarchy
✅ Utility: Convert String to Char Array and Sort
String s = "aasa";
char[] arr = s.toCharArray();
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // Output: [a, a, a, s]
✅ Spring Boot Debugging
To enable remote debugging in Spring Boot:
🔹 Using JAR:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 -jar myapp.jar
🔹 Using Maven:
mvn spring-boot:run -Dagentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
✅ Singleton Class in Java
➤ Only one instance of the class is created in the JVM.
🔹 Rules:
- Constructor should be private.
- Use a static method to return the instance.
🔹 Example:
class Singleton {
private static Singleton instance;
public String s;
private Singleton() {
s = "Hello, I am Singleton";
}
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
Usage:
public class Test {
public static void main(String[] args) {
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
a.s = a.s.toUpperCase();
System.out.println("From a: " + a.s); // FROM A: HELLO, I AM SINGLETON
System.out.println("From b: " + b.s); // FROM B: HELLO, I AM SINGLETON
}
}
✅ Aggregation vs Composition
🔹 Aggregation (HAS-A Relationship):
- Weak association.
- Child can exist independently of parent.
class Address {
String city, state;
}
class Employee {
int id;
Address address; // Aggregation
}
🔹 Composition (Strong HAS-A):
- Strong association.
- Child can’t exist independently of parent.
class Room { }
class House {
private final Room room = new Room(); // Composition
}
✅ JDBC vs Hibernate
🔹 JDBC:
- Manual SQL queries.
- Boilerplate code for connections, statements, result sets.
🔹 Hibernate (ORM):
- Object-Relational Mapping.
- Automatically maps Java classes to database tables.
Advantages of Hibernate:
- Removes boilerplate code.
- Supports associations, inheritance.
- Built-in caching, transaction management.
- Uses HQL (Hibernate Query Language), more object-oriented than SQL.
✅ JDBC execute() vs executeQuery() vs executeUpdate()
| Method | Use Case | Return Type |
|---|---|---|
execute() | Any SQL (SELECT, DDL) | boolean |
executeQuery() | SELECT only | ResultSet |
executeUpdate() | INSERT/UPDATE/DELETE | int |
Statement stmt = conn.createStatement();
// SELECT
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// UPDATE
int rows = stmt.executeUpdate("UPDATE users SET name='John' WHERE id=1");
// ANY
boolean status = stmt.execute("SELECT * FROM users");
✅ Exception Handling in Java
Keywords:
trycatchfinallythrow– to throw an exceptionthrows– to declare potential exceptions
➤ Checked vs Unchecked:
| Checked Exceptions | Unchecked Exceptions |
|---|---|
| Handled at compile-time | Handled at runtime |
| e.g., IOException | e.g., NullPointerException |
Example:
public void readFile() throws FileNotFoundException {
FileReader file = new FileReader("abc.txt");
}
✅ Error vs Exception
| Error (Serious) | Exception (Recoverable) |
|---|---|
| Not meant to be caught normally | Catch and handle in code |
| e.g., OutOfMemoryError | e.g., ArithmeticException, IOException |
✅ throw vs throws
| throw | throws |
|---|---|
| Used to throw an exception | Declares that a method might throw |
| Used inside method body | Used with method signature |
void validateAge(int age) {
if (age < 18)
throw new ArithmeticException("Not eligible");
}
void someMethod() throws IOException, SQLException {
// method body
}
✅ Exception Hierarchy in Java
Object
└── Throwable
├── Exception (Checked & Unchecked)
│ ├── Checked Exceptions (IOException, SQLException, etc.)
│ └── Unchecked Exceptions (RuntimeException, NullPointerException, etc.)
└── Error
├── VirtualMachineError
└── AssertionError
- Checked Exception → must be declared or handled (e.g.
IOException) - Unchecked Exception → not mandatory to handle (e.g.
NullPointerException) - Error → serious issues that should not be caught (e.g.
OutOfMemoryError)
❓ Can we write multiple catch blocks under a single try block?
✅ Yes
🔄 System.exit() vs return
| Feature | return | System.exit() |
|---|---|---|
| Scope | Exits current method | Terminates the entire JVM immediately |
| Use Case | Used for returning from a method | Used to forcefully shut down an app |
| Flow Continuity | Program continues after return | Program ends completely |
🧩 Regular Expression in Java
import java.util.regex.*;
public class RegexDemo {
public static void main(String[] args) {
String line = "This order was placed for QT3000! OK?";
String pattern = "(.*)(\\d+)(.*)"; // match a digit pattern
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(line);
if (m.find()) {
System.out.println("Group 0: " + m.group(0)); // entire match
System.out.println("Group 1: " + m.group(1)); // before digits
System.out.println("Group 2: " + m.group(2)); // digits
} else {
System.out.println("NO MATCH");
}
}
}
📞 Twilio
- Twilio is a cloud communication platform.
- Enables SMS, voice calls, and messaging from your application using APIs.
- You can send SMS using Java SDK by registering and using Twilio credentials.
🧠 Spring Singleton vs Prototype Scope
By Default:
Spring beans are singleton scoped:
@Service
public class DataService { }
@RestController
public class Controller1 {
@Autowired
DataService dataService; // Singleton - shared instance
}
@RestController
public class Controller2 {
@Autowired
DataService dataService; // Same instance as in Controller1
}
To Make it Non-Singleton (Prototype):
@Service
@Scope("prototype")
public class DataService { }
Now each time DataService is autowired, a new instance will be created.
🔁 Alternative to @Autowired (Constructor Injection)
Instead of field injection:
@Autowired
EmployeeRepository repo;
Use constructor injection:
@Component
public class MyController {
private final EmployeeRepository repo;
public MyController(EmployeeRepository repo) {
this.repo = repo;
}
}
- ✅ Preferred for immutability, testability, and clean code
🔧 Functional Interface (Java 8)
- Functional Interface: Interface with exactly one abstract method
- Can contain default and static methods (Java 8+)
- Used in Lambda expressions and Streams
Example:
@FunctionalInterface
interface MyFunctional {
void execute(); // Only one abstract method
}
Usage with Lambda:
@FunctionalInterface
interface MyFunctional {
void execute(); // Only one abstract method
}
public class FunctionalExample {
public static void main(String[] args) {
// Lambda expression for functional interface
MyFunctional mf = () -> System.out.println("Running Functional Interface");
// Calling the method
mf.execute(); // Output: Running Functional Interface
}
}
Notes:
- If you try to add another abstract method in
MyFunctional, it will throw a compiler error because the interface is marked with@FunctionalInterface. - Default and static methods do not count towards the single-abstract-method rule.
✅ Example with default Method:
@FunctionalInterface
interface MyFunctional {
void execute(); // Only abstract method
default void show() {
System.out.println("Default method in Functional Interface");
}
}
✅ What is Spring Boot?
Spring Boot is a module of Spring Framework that simplifies the development of production-ready Spring applications.
It provides:
- Auto-configuration
- Embedded server (Tomcat, Jetty)
- Opinionated starter dependencies
✅ Microservices in Spring Boot
A Microservice is an architecture style where each module of an application is a loosely coupled, independently deployable service.
Advantages:
- Easy deployment
- Scalable and lightweight
- Works well with containers (e.g., Docker)
- Easy to maintain and develop independently
✅ HTTPS Configuration in Spring Boot
server.port=443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=springboot
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
Enables SSL with a keystore on port 443 (HTTPS).
✅ Scheduling in Spring Boot
Annotations used:
@EnableScheduling– to enable scheduling feature.@Scheduled– to run a method at fixed interval or cron expression.
Example:
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Component
public class ScheduledTasks {
// Runs every minute
@Scheduled(cron = "0 * * * * ?")
public void cronJob() {
System.out.println("Running scheduled task at: " + new Date());
}
}
✅ Logging with SLF4J and Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("Info message");
logger.error("Error message");
logger.warn("Warning message");
SLF4J acts as a facade for different logging frameworks.
✅ Spring Boot Actuator
Provides endpoints to monitor and manage applications (e.g., health, metrics, info).
Add to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Sample Endpoints:
GET /actuator/health
health info::
{
"status": "UP"
}
for detail info add in application.properties
management.endpoint.health.show-details=always
now deailed health info
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 500000000000,
"free": 120000000000,
"threshold": 10485760
}
},
"ping": {
"status": "UP"
}
}
}
GET /actuator/info
{} // nothing without configuraion
You can provide custom info in your application.properties:
info.app.name=My Spring Boot App
info.app.description=A demo Spring Boot application
info.app.version=1.0.0
{
"app": {
"name": "My Spring Boot App",
"description": "A demo Spring Boot application",
"version": "1.0.0"
}
}
✅ DTO (Data Transfer Object)
Used to transfer data between layers without exposing the full entity.
@Data // Lombok annotation to reduce boilerplate
public class UserDTO {
private String name;
private String email;
}
✅ What is a POJO in Java?
POJO stands for Plain Old Java Object. A POJO is a simple Java object that doesn’t depend on any special Java frameworks, classes, or annotations. It follows standard conventions and is primarily used for data representation.
✅ Characteristics of a POJO:
- No need to extend or implement any specific classes/interfaces.
- Contains fields (variables), constructors, getters/setters.
- No business logic.
- No framework-specific annotations (optional in some frameworks like Lombok).
✅ Example: Simple POJO
public class Employee {
private int id;
private String name;
private double salary;
// Default constructor
public Employee() {}
// Parameterized constructor
public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
// toString() method
@Override
public String toString() {
return "Employee{id=" + id + ", name='" + name + "', salary=" + salary + "}";
}
}
✅ POJO with Lombok (to reduce boilerplate)
import lombok.Data;
@Data
public class Employee {
private int id;
private String name;
private double salary;
}
🔹 The @Data annotation automatically generates:
- Getters and setters
toString()equals()andhashCode()- A constructor if needed
✅ Where are POJOs used?
- As entities in JPA/Hibernate
- As DTOs (Data Transfer Objects)
- As model classes in Spring MVC
- As request/response objects in REST APIs
✅ Lombok Annotations
@Data→ generates getters, setters, toString, equals, and hashCode.@Getter/@Setter→ individually for fields@EqualsAndHashCode→ for equality comparison
Example:
@Data
public class Point {
private double x;
private double y;
}
✅ @CrossOrigin
Enables Cross-Origin Resource Sharing (CORS) at controller/method level.
@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
🧠 Summary
| Scope | Annotation/Approach | Usage |
|---|---|---|
| Method level | @CrossOrigin | On specific controller methods |
| Controller | @CrossOrigin | On entire controller |
| Application | WebMvcConfigurer class | Best for global configuration (all endpoints) |
| Spring Gateway | application.yml | Specific to Spring Cloud Gateway |
global setting using webMvsConfigurer class
@Configuration
public class GlobalCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // Allow all paths
.allowedOrigins("*") // Allow all origins
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // Allow these methods
.allowedHeaders("*") // Allow all headers
.allowCredentials(false); // You can make it true for cookies/session support
}
};
}
}
✅ @Transactional
Manages transaction boundaries automatically.
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
from.withdraw(amount);
to.deposit(amount);
}
✅ JPA vs Hibernate
| Aspect | JPA (Java Persistence API) | Hibernate |
|---|---|---|
| Type | Specification (Interface) | Implementation (Framework/API) |
| Definition | A standard for ORM in Java | A popular implementation of the JPA specification |
| Provided By | Java EE / Jakarta EE | Red Hat |
| Code Availability | Only interfaces (no implementation logic) | Full implementation + extra features |
| Annotations | Defines standard annotations like @Entity, @Id | Uses JPA annotations + its own (@Where, @CreationTimestamp) |
| Configuration | Needs provider (e.g., Hibernate) to work | Comes with full configuration & mapping engine |
| Query Language | JPQL (Java Persistence Query Language) | JPQL + HQL (Hibernate Query Language – more powerful) |
| Caching | JPA defines second-level cache (optional) | Hibernate provides L1, L2, and query caching out of the box |
| Support for Features | Basic ORM features only | Advanced features: custom naming strategies, interceptors, filters |
| Ease of Use | Needs more code and setup | Easier with built-in tools, widely used in Spring Boot |
| Learning Curve | Abstract, needs an implementation to test | Steeper but well-documented with lots of examples |
✅ JPA Fetch Types
LAZY– fetch when neededEAGER– fetch immediately
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;
✅ @JsonIgnore vs @JsonManagedReference / @JsonBackReference
@JsonIgnoreskips serialization/deserialization.@JsonManagedReference&@JsonBackReferencehelp manage parent-child references in bi-directional relationships to avoid recursion.
✅ Java Annotations Overview
Annotations are metadata that provide data about a program but are not part of the program itself. They serve various purposes:
🔹 Used For:
- Compiler instructions
- Build-time processing
- Runtime behavior (Spring, Hibernate, JPA)
🔹 Common Built-in Annotations:
@Override@Deprecated@SuppressWarnings
✅ Important Spring Annotations
| Annotation | Purpose |
|---|---|
@Entity | Marks class as a JPA entity |
@Component | Marks a Spring-managed bean |
@Autowired | Auto-inject dependencies |
@Qualifier | Specify bean to inject |
@PostConstruct | Method to run after bean is initialized |
@PreDestroy | Method to run before bean is destroyed |
@Service | Marks a service-layer component |
@Repository | Marks data access object |
🔹 Custom Annotation Example:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}
✅ @PostLoad Annotation in JPA
- The
@PostLoadannotation marks a method that is invoked after an entity is loaded from the database, after all eagerly fetched fields are populated. - It is commonly used to perform post-initialization logic.
🔹 Example:
@Entity
public class User {
@Id
private Long id;
private String name;
private String email;
@Transient
private String domain;
@PostLoad
public void extractEmailDomain() {
if (email != null && email.contains("@")) {
domain = email.substring(email.indexOf("@") + 1);
}
}
}
✅ JPA Annotations
@Entity
@Table(name = "vehicles")
@NamedQuery(name = "Vehicle.findAll", query = "SELECT v FROM Vehicle v")
public class Vehicle {
@Id
@GeneratedValue
private Long id;
private String name;
}
@Entity: Marks class as a JPA entity.@NamedQuery: Pre-defined, static query.@Table: Custom table name mapping.
✅ @Lob Annotation
The @Lob annotation is used to store large objects like binary or character data.
@Lob
private byte[] data;
@Lobmaps thebyte[]field to a BLOB (Binary Large Object) in the database.- If the type were
String, it would map to a CLOB (Character Large Object).
✅ Iterable Interface in Java
- Root interface in the collection hierarchy.
- Enables enhanced for-loop (
for-each) usage. CollectionextendsIterable.
✅ Dependency Injection Conflict Resolution
When using Spring’s @Autowired annotation for dependency injection, Spring tries to inject a matching bean by type. However, if multiple beans of the same type are found, Spring throws an exception due to ambiguity.
This commonly happens when two or more classes implement the same interface, and Spring doesn’t know which one to inject.
Scenario:
interface Animal {
String characteristics();
}
@Service("dog")
public class Dog implements Animal {
public String characteristics() { return "bark"; }
}
@Service("cat")
public class Cat implements Animal {
public String characteristics() { return "meow"; }
}
Solutions:
- Use
@Qualifier
@Autowired
@Qualifier("dog")
private Animal animal;
- Use
@Primary
@Primary
@Service
public class Dog implements Animal { ... }
- Setter Injection
private Animal animal;
@Autowired
public void setAnimal(@Qualifier("cat") Animal animal) {
this.animal = animal;
}
✅ Spring Profiles
@Profile("dev")
@Service
public class DevDataSourceConfig { ... }
- Use
application-dev.properties - Activate via
--spring.profiles.active=dev
✅ @PreAuthorize, @PostAuthorize (Spring Security)
Used for method-level access control:
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void deleteUser(Long id) { ... }
@PostAuthorize("returnObject.owner == authentication.name")
public Book getBook() { ... }
@PreAuthorize("#book.owner == authentication.name")
public void updateBook(Book book) { ...
✅ hasRole() vs hasAuthority()
| Annotation | Description |
|---|---|
hasRole('X') | Spring Security adds "ROLE_" prefix automatically. Check for "ROLE_X" |
hasAuthority('X') | No prefix added. You must match the exact authority name (e.g., "ADMIN") |
So if your role is stored as
"ROLE_TestSuperAdmin"in DB,hasRole('TestSuperAdmin')will match it, buthasAuthority('TestSuperAdmin')will not, unless no prefix exists.
🔸 Example:
@PreAuthorize("hasRole('TestSuperAdmin')")
@GetMapping("/blogs/{id}")
public ResponseEntity<?> getPostById(@PathVariable Long id) {
return ResponseEntity.ok(blogRepository.findById(id));
}
✅ @NaturalId — Natural Identifier in Hibernate
A Natural ID is a domain-specific unique identifier, like:
- ISBN for a book
- Email or SSN for a person
- PAN for a tax entity
These are unique in real-world logic and are used in business logic often.
In contrast to a Surrogate Key (e.g., auto-generated primary key), a Natural ID is usually assigned manually and has business meaning.
🔸 Example:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@NaturalId
private String isbn; // natural unique identifier
private String title;
}
⚠ Why not use Natural IDs as primary keys?
- They can change (rare but possible, e.g., email updates)
- They are often longer or more complex than numeric IDs
- Auto-generated keys (surrogate keys) are faster and lighter for joins and indexing
✅ @GeneratedValue — Strategy for Primary Key Generation
Used to automatically generate the primary key value.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
🔹 Generation Types in JPA
| Strategy | Description | Use Case / Notes |
|---|---|---|
AUTO | Default. JPA provider chooses the strategy. | Flexible but implicit (can vary per DB) |
IDENTITY | Uses DB auto-increment. New row inserted, then DB gives ID. | Simple, but can’t batch inserts; not best for performance |
SEQUENCE | Uses DB sequence object to fetch the next ID. | Preferred if DB supports it (e.g., PostgreSQL, Oracle) |
TABLE | Uses a separate table to simulate sequence generation. | Rarely used; works with all DBs, but poor performance |
✅ ResponseEntity in Spring Boot
ResponseEntity<T> is used to represent the entire HTTP response, including:
- Status code
- Headers
- Body (payload)
🔸 Example:
return new ResponseEntity<>(
new ApiResponse(false, "TestUser is already taken!"),
HttpStatus.BAD_REQUEST
);
You can also use helper methods like ResponseEntity.ok():
return ResponseEntity.ok(new ApiResponse(true, "User created successfully!"));
✅ Maven Scope: <scope> in pom.xml
🔹 <scope>compile</scope> (default)
- Needed for compiling and running
- Included in both build and runtime classpaths
🔹 <scope>provided</scope>
- Needed for compiling, but not packaged
- Example: Servlet APIs in web containers like Tomcat
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
✅ JPA Cascade Types
Cascade types let related entities be updated/deleted/saved automatically when the owning entity is acted upon.
| Type | Description |
|---|---|
PERSIST | Saves child when parent is saved |
MERGE | Merges changes from parent to child |
REMOVE | Deletes child entities when parent is deleted |
REFRESH | Refreshes children when parent is refreshed |
DETACH | Detaches children when parent is detached |
ALL | All the above operations |
🔸 Example:
@OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Address> addresses;
orphanRemoval = true: deletes the child (Address) if it is removed from the list.
✅ save() vs persist() in JPA
| Method | Description |
|---|---|
save() | (Spring Data JPA) Immediately generates the primary key |
persist() | (JPA EntityManager) Doesn’t assign the ID immediately unless flush() is called |
✅ UUID Strategy in Hibernate
UUID is a universally unique identifier (128-bit). You can generate primary keys using this strategy.
🔸 Example:
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(columnDefinition = "BINARY(16)")
private UUID id;
✅ Spring Boot Embedded Server Configuration
🔸 Default Server
Spring Boot uses Tomcat as the default embedded server.
🔸 Switch to Jetty
To switch from Tomcat to Jetty:
pom.xml
<!-- Exclude Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Add Jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
🔸 Switch to Undertow
<!-- Add Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
✅ Change Default Server Port & Context Path
In application.properties:
# Change HTTP Server port
server.port=8081
# Set custom context path
server.servlet.context-path=/myapp
App URL: http://localhost:8081/myapp
✅ Enable GZip Compression
GZIP compression helps improve performance by reducing response payload size.
# Enable compression
server.compression.enabled=true
# MIME types to compress
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
# Minimum size to compress (in bytes)
server.compression.min-response-size=1024
✅ Enable HTTP/2 in Spring Boot
HTTP/2 improves performance through features like multiplexing and header compression.
# Enable HTTP/2 (only works on HTTPS)
server.http2.enabled=true
Note: Your server must support HTTP/2 and it works only over HTTPS.
✅ Enable Static Resource Caching
Improves performance by allowing browser caching of static files (JS, CSS, images).
# Cache duration for static resources (in seconds)
spring.resources.cache.cachecontrol.max-age=120
# Forces revalidation before using stale cache
spring.resources.cache.cachecontrol.must-revalidate=true
✅ Configuring Spring Boot to Use Gson Instead of Jackson
By default, Spring Boot uses Jackson for JSON serialization/deserialization.
To use Gson instead:
🔸 Step 1: Exclude Jackson in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
🔸 Step 2: Add Gson Dependency
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
Spring Boot will automatically detect and use Gson if Jackson is excluded.
✅ Docker – Containerization Platform
Docker allows you to package your application and its dependencies into a container, which can run consistently across any environment.
Build->Ship->Deploy
🔸 Key Features:
- Lightweight & portable
- Consistent environments for development & production
- Isolated containers on the same host
🔸 Common Docker Terms:
| Term | Description |
|---|---|
| Image | Blueprint of your application (like a class) |
| Container | Running instance of an image (like an object) |
| Dockerfile | Script with instructions to build an image |
🔸 Example Dockerfile for Spring Boot:
FROM openjdk:17
VOLUME /tmp
COPY target/myapp.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
🔸 Docker Commands:
# Build image
docker build -t myapp .
# Run container
docker run -p 8080:8080 myapp
✅ Container Isolation in Docker
Each Docker container:
- Has its own process space, network, and filesystem
- Runs independently of other containers
- Can be configured with different versions of OS, libraries, or dependencies
This ensures:
- No conflict between apps
- Efficient resource usage
- Rapid deployment and scalability
✅ Remove Duplicates from a List in Java
List<Integer> sd = new ArrayList<>();
sd.add(3);
sd.add(2);
sd.add(4);
sd.add(5);
sd.add(2);
sd.stream()
.distinct() // removes duplicates
.forEach(System.out::println); // prints each unique element
If you want to double each value before filtering:
✅ Multithreading in Java
🧠 What is Multithreading?
- Executing multiple threads simultaneously
- Thread = lightweight sub-process. (a process can have multiple thread. process is a running instance of an application)
- Used for multitasking in games, animations, servers
💡 Benefits of Threads:
- Share memory space (unlike processes)
- Lightweight: fast context-switching
- Saves resources
🧵 Types of Multitasking
- Process-based: Multiple processes (each has its own memory)
- Thread-based: Multiple threads within the same process (shared memory)
✅ Ways to Create Threads
1. By Extending Thread Class
class TestThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
TestThread t1 = new TestThread();
t1.start(); // starts new thread, internally calls run()
}
}
2. By Implementing Runnable Interface
class TestRunnable implements Runnable {
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
TestRunnable m1 = new TestRunnable();
Thread t1 = new Thread(m1); // Runnable object passed to Thread
t1.start();
}
}
✅ Thread Lifecycle (Managed by JVM)
- New – thread object created but not started
- Runnable – start() called, ready for execution
- Running – thread is executing
- Blocked/Waiting – waiting for resource or lock
- Terminated – thread has finished or stopped
🛠 Common Thread Methods
| Method | Description |
|---|---|
start() | Starts thread execution |
run() | Defines task of the thread |
sleep(ms) | Pause execution |
join() | Waits for thread to finish |
currentThread() | Returns currently executing thread |
notify()/notifyAll() | Wakes up waiting thread |
wait() | Pauses thread until notified |
✅ When to Use Which?
| Case | Use |
|---|---|
You only want to override run() | Implement Runnable |
| You want to override other thread methods too | Extend Thread |
| Your class already extends another class | Implement Runnable (Java doesn’t support multiple inheritance) |
✅ Multitasking vs Multithreading
| Concept | Multitasking | Multithreading |
|---|---|---|
| Definition | Run multiple processes | Run multiple threads inside a process |
| Memory | Each process has own memory | Threads share memory |
| Speed | Slower due to heavy memory usage | Faster due to lightweight nature |
| Use Case | Running different applications | Background tasks within app, e.g., logging, timers |
class MyThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
// sleep() example
try {
Thread.sleep(1000); // pause for 1 second
System.out.println(Thread.currentThread().getName() + " woke up after sleeping");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
}
}
}
public class ThreadMethodsExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// setName()
t1.setName("Worker-1");
t2.setName("Worker-2");
// start() calls run() in new thread
t1.start();
t2.start();
// isAlive() - true if thread is still running
System.out.println(t1.getName() + " is alive: " + t1.isAlive());
System.out.println(t2.getName() + " is alive: " + t2.isAlive());
// join() - main thread waits for both threads to complete
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread: " + Thread.currentThread().getName());
System.out.println("All threads have finished");
}
}
✅ Data Structure
✅ 1. Stack – Last In First Out (LIFO)
A Stack is a linear data structure that follows the LIFO (Last In First Out) principle, meaning the last element added is the first one to be removed.
🧠 Real-world Analogy:
A stack of plates — the last plate placed on top is the first one you pick up.
🔧 Common Java Methods:
push(E item)– Adds an item to the toppop()– Removes the top itempeek()– Views the top item without removing itsearch(Object o)– Returns the 1-based position from the top
🧪 Example in Java:
Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 20
System.out.println(stack.peek()); // 10
✅ 2. Queue – First In First Out (FIFO)
A Queue is a linear data structure that follows the FIFO (First In First Out) principle, where the element added first is the one removed first.
🧠 Real-world Analogy:
A queue of people in a line — the person who comes first is served first.
🔧 Common Java Methods:
add(E e)– Adds an item to the queueremove()– Removes and returns the head of the queuepeek()– Returns the head without removingsize()– Returns the number of elements
🧪 Example in Java:
Queue<String> queue = new LinkedList<>();
queue.add("Alice");
queue.add("Bob");
System.out.println(queue.remove()); // Alice
System.out.println(queue.peek()); // Bob
✅ 3. Linked List – Dynamic Linear Data Structure
A Linked List is a linear data structure in which elements are stored in non-contiguous memory locations. Each element is called a node and has a reference to the next node.
🧠 Real-world Analogy:
A train — each coach (node) is linked to the next.
🔧 Features:
- Dynamic memory allocation
- Easier insertions/deletions compared to arrays
🔧 Common Java Methods:
add(E e)– Adds at the endadd(int index, E element)– Adds at a specific indexremove(int index)– Removes element at indexget(int index)– Fetches element at index
🧪 Example in Java:
LinkedList<String> list = new LinkedList<>();
list.add("First");
list.add("Second");
list.addFirst("Zero");
list.remove("Second");
System.out.println(list); // [Zero, First]
📚 Summary Table
| Structure | Order | Java Class | Core Operations | Use Case Example |
|---|---|---|---|---|
| Stack | LIFO | java.util.Stack | push, pop, peek, search | Browser history, Undo |
| Queue | FIFO | java.util.Queue | add, remove, peek, size | Call center, Printer |
| Linked List | N/A | java.util.LinkedList | add, remove, get, addFirst | Music playlist, Navigation |
✅ What Are Generics?
Generics allow you to define classes, interfaces, and methods with type parameters, providing compile-time type safety and eliminating the need for casting.
🔹 1. Generic Class Example
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
✅ Usage:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // Output: Hello
Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println(intBox.get()); // Output: 123
🔹 2. Generic Method Example
public class Utility {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
✅ Usage:
public class Main {
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3};
String[] strArray = {"A", "B", "C"};
Utility.printArray(intArray); // Output: 1 2 3
Utility.printArray(strArray); // Output: A B C
}
}
🔹 3. Bounded Type Parameters
You can restrict the types that can be used as type arguments.
public class Calculator<T extends Number> {
public double square(T num) {
return num.doubleValue() * num.doubleValue();
}
}
✅ Usage:
Calculator<Integer> intCalc = new Calculator<>();
System.out.println(intCalc.square(4)); // Output: 16.0
🔹 4. Wildcards (?)
Used when you want to accept unknown types.
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
✅ Benefits of Generics
- Type Safety – Errors are caught at compile-time.
- Code Reusability – Write a single class/method for multiple data types.
- No casting needed – Improves code readability and maintainability.
