Java final Keyword - Complete Guide with Real-Time Examples
Java final Keyword - Complete Guide for Beginners
Hey there! If you're starting your Java journey or preparing for interviews, understanding the final keyword is crucial. I've seen many freshers struggle with this concept, so let me break it down for you in the simplest way possible.
Think of final as a lock 🔒 - once you lock something, you can't change it. That's exactly what final does in Java.
What is the final Keyword?
The final keyword in Java is a non-access modifier that means "this cannot be changed" or "this is the end, no modifications allowed".
Simple Definition:
final is like writing something in permanent marker instead of pencil. Once written, you can't erase or change it.
Three Main Uses:
- ›final Variables → Create constants (values that never change)
- ›final Methods → Prevent method overriding in child classes
- ›final Classes → Prevent class inheritance (no one can extend this class)
In real projects, you'll see final everywhere - from configuration files to security implementations.
Why is the final Keyword Important?
Let me tell you why companies use final in production code:
1. Prevents Accidental Changes
Imagine you're working on a banking application. The bank's interest rate is 7.5%. You don't want any developer to accidentally change it to 75% or 0.75%. Making it final prevents such disasters.
1final double INTEREST_RATE = 7.5; // No one can change this accidentally2. Security
In payment systems, authentication logic should never be modified by child classes. final methods ensure security logic stays intact.
3. Performance
The JVM can optimize final variables better because it knows these values won't change.
4. Thread Safety
final variables are inherently thread-safe. Multiple threads can read them without synchronization issues.
5. Clear Intent
When you see final, you immediately know "this is not meant to change". It makes code more readable.
Real-Time Example:
1// E-commerce application
2public class ShoppingCart {
3 private final String userId; // User ID should never change
4 private final String sessionId; // Session ID is permanent
5
6 public ShoppingCart(String userId, String sessionId) {
7 this.userId = userId;
8 this.sessionId = sessionId;
9 }
10}Key Rules You Must Remember
Before we dive into examples, memorize these rules:
For final Variables:
- ›✅ Must be initialized when declared OR in constructor
- ›✅ Cannot be reassigned once initialized
- ›✅ For primitives (int, double, etc.) → Value cannot change
- ›✅ For objects (ArrayList, etc.) → Reference cannot change, but object content CAN change
- ›✅ Naming convention: Use UPPERCASE for constants (e.g.,
MAX_SIZE,DATABASE_URL)
For final Methods:
- ›✅ Cannot be overridden by child classes
- ›✅ Can be inherited but not modified
- ›✅ Used to protect critical business logic
For final Classes:
- ›✅ Cannot be extended (no child classes allowed)
- ›✅ All methods are automatically final
- ›✅ Used for utility classes and immutable classes
1. final Variables - Creating Constants
What You'll Learn:
- ›How to create constants using
final - ›Difference between regular variables and final variables
- ›Real-time use cases in projects
Example 1: Basic final Variable
This is the simplest use of final - creating a constant that never changes.
1public class BankingApp {
2 public static void main(String[] args) {
3 // Regular variable - can be changed
4 int accountBalance = 10000;
5 System.out.println("Initial balance: " + accountBalance);
6
7 accountBalance = 15000; // ✅ This works fine
8 System.out.println("Updated balance: " + accountBalance);
9
10 // final variable - CANNOT be changed
11 final double INTEREST_RATE = 7.5;
12 System.out.println("Interest rate: " + INTEREST_RATE + "%");
13
14 // INTEREST_RATE = 8.0; // ❌ COMPILE ERROR!
15 // Error: cannot assign a value to final variable INTEREST_RATE
16 }
17}Output:
Initial balance: 10000
Updated balance: 15000
Interest rate: 7.5%
What This Program Does:
- ›Line 4-5: Creates a regular variable
accountBalancewith value 10000 and prints it - ›Line 7: Changes
accountBalanceto 15000 - this is allowed for regular variables - ›Line 11: Creates a
finalvariableINTEREST_RATEwith value 7.5- ›
finalkeyword makes it unchangeable - ›
INTEREST_RATEis in UPPERCASE (naming convention for constants)
- ›
- ›Line 14 (commented): If you try to change
INTEREST_RATE, compiler throws error- ›The program won't even compile
- ›This is the protection
finalprovides
Real-Time Use:
In banking apps, interest rates, tax rates, maximum withdrawal limits are all final constants. They're configured once and should never change accidentally.
Example 2: final with Different Data Types
final works with ALL data types - primitives and objects.
1public class EcommerceConfig {
2 public static void main(String[] args) {
3 // final with primitive types
4 final int MAX_ITEMS_IN_CART = 50;
5 final double TAX_RATE = 18.0;
6 final boolean FREE_SHIPPING_ENABLED = true;
7 final char CURRENCY_SYMBOL = '₹';
8
9 // final with reference types
10 final String PAYMENT_GATEWAY = "Razorpay";
11 final String APP_VERSION = "2.1.0";
12
13 // Using these constants
14 System.out.println("Maximum items allowed: " + MAX_ITEMS_IN_CART);
15 System.out.println("Tax rate: " + TAX_RATE + "%");
16 System.out.println("Free shipping: " + FREE_SHIPPING_ENABLED);
17 System.out.println("Currency: " + CURRENCY_SYMBOL);
18 System.out.println("Payment Gateway: " + PAYMENT_GATEWAY);
19 System.out.println("App Version: " + APP_VERSION);
20
21 // None of these can be changed
22 // MAX_ITEMS_IN_CART = 100; // ❌ COMPILE ERROR
23 // TAX_RATE = 20.0; // ❌ COMPILE ERROR
24 // PAYMENT_GATEWAY = "PayPal"; // ❌ COMPILE ERROR
25 }
26}Output:
Maximum items allowed: 50
Tax rate: 18.0%
Free shipping: true
Currency: ₹
Payment Gateway: Razorpay
App Version: 2.1.0
What This Program Does:
- ›Lines 4-7: Declares final constants with primitive data types
- ›
int,double,boolean,charall work withfinal - ›Each variable is a constant that cannot be modified
- ›
- ›Lines 10-11: Declares final constants with String (reference type)
- ›Strings can also be final
- ›Lines 14-19: Uses all constants normally - reading values is always allowed
- ›Lines 22-24: Shows that reassignment fails for all final variables
Real-Time Use: E-commerce platforms use such constants for configuration. Payment gateway names, tax rates, shipping rules are all defined as final constants in configuration classes.
Example 3: CRITICAL CONCEPT - final with Objects
⚠️ This is where most beginners get confused!
What You'll Learn:
- ›
finalfor objects only locks the reference, NOT the content - ›You can modify the object, but can't point to a new object
1import java.util.ArrayList;
2
3public class ShoppingCart {
4 public static void main(String[] args) {
5 // Creating a final ArrayList
6 final ArrayList<String> cartItems = new ArrayList<>();
7
8 System.out.println("Initial cart: " + cartItems);
9
10 // ✅ You CAN modify the ArrayList's contents
11 cartItems.add("iPhone 15");
12 cartItems.add("AirPods");
13 cartItems.add("MacBook");
14
15 System.out.println("After adding items: " + cartItems);
16
17 // ✅ You CAN remove items
18 cartItems.remove("AirPods");
19 System.out.println("After removing item: " + cartItems);
20
21 // ✅ You CAN clear all items
22 cartItems.clear();
23 System.out.println("After clearing: " + cartItems);
24
25 // ❌ You CANNOT assign a new ArrayList
26 // cartItems = new ArrayList<>(); // COMPILE ERROR!
27 // Error: cannot assign a value to final variable cartItems
28 }
29}Output:
Initial cart: []
After adding items: [iPhone 15, AirPods, MacBook]
After removing item: [iPhone 15, MacBook]
After clearing: []
What This Program Does:
- ›
Line 6: Creates a
finalArrayList reference- ›The variable
cartItemsis final - ›It always points to the SAME ArrayList object
- ›But the ArrayList itself is NOT final
- ›The variable
- ›
Lines 11-13: Adds items to the cart
- ›✅ This is ALLOWED because we're modifying the ArrayList's content
- ›We're NOT changing what
cartItemspoints to - ›Think of it like: The box is locked to one location, but you can add/remove items from inside the box
- ›
Line 18: Removes an item
- ›✅ Also allowed - we're modifying contents, not the reference
- ›
Line 22: Clears all items
- ›✅ Still allowed - emptying the contents is fine
- ›
Line 26 (commented): Trying to assign new ArrayList fails
- ›❌ This would make
cartItemspoint to a DIFFERENT ArrayList object - ›This is what
finalprevents!
- ›❌ This would make
Think of it like this:
final = The LABEL on the box cannot be moved to another box
But you CAN add/remove items FROM the box
cartItems ──→ [ArrayList Object]
↑
This arrow is LOCKED (final)
The ArrayList object itself can be modified
Real-Time Industry Example:
1public class UserSession {
2 private final List<String> permissions; // User permissions list
3
4 public UserSession(List<String> permissions) {
5 this.permissions = permissions; // Reference is final
6 }
7
8 public void addPermission(String permission) {
9 permissions.add(permission); // ✅ Can modify contents
10 }
11
12 public void revokePermission(String permission) {
13 permissions.remove(permission); // ✅ Can modify contents
14 }
15
16 // public void replacePermissions(List<String> newPermissions) {
17 // this.permissions = newPermissions; // ❌ Cannot change reference
18 // }
19}Interview Tip: Interviewers LOVE asking: "If a reference is final, can you modify the object?" Answer: "Yes! final locks the reference, not the object's state. You can call methods that modify the object, but you cannot reassign the reference to a new object."
Example 4: Blank final Variables
What You'll Learn:
- ›final variables don't need immediate initialization
- ›You can initialize them once in constructor or conditional blocks
- ›After first initialization, they're locked forever
1public class BankAccount {
2 public static void main(String[] args) {
3 // Blank final - declared but not initialized
4 final String accountType;
5 final double interestRate;
6
7 // Get user input (simulated here)
8 int userChoice = 2; // 1 = Savings, 2 = Current
9
10 // Initialize based on account type
11 if (userChoice == 1) {
12 accountType = "Savings Account";
13 interestRate = 4.5;
14 } else {
15 accountType = "Current Account";
16 interestRate = 0.0; // No interest for current accounts
17 }
18
19 System.out.println("Account Type: " + accountType);
20 System.out.println("Interest Rate: " + interestRate + "%");
21
22 // Now these are initialized and CANNOT be changed
23 // accountType = "Fixed Deposit"; // ❌ COMPILE ERROR!
24 // interestRate = 7.0; // ❌ COMPILE ERROR!
25 }
26}Output:
Account Type: Current Account
Interest Rate: 0.0%
What This Program Does:
- ›
Lines 4-5: Declares
finalvariables WITHOUT initializing them- ›These are called "blank final" variables
- ›Must be initialized before first use
- ›Can only be initialized ONCE
- ›
Line 8: Simulates user selecting account type
- ›
Lines 11-17: Initializes final variables based on condition
- ›For savings account: 4.5% interest
- ›For current account: 0% interest
- ›Only ONE of these blocks will execute
- ›After this, the variables are locked forever
- ›
Lines 23-24: Cannot reassign once initialized
- ›Compiler error if you try
Why Use Blank final?
- ›Configuration values that depend on runtime conditions
- ›Values from user input or database
- ›Different environments (dev, staging, production)
Real-Time Example:
1public class DatabaseConfig {
2 private final String DB_URL;
3 private final int DB_PORT;
4
5 public DatabaseConfig(String environment) {
6 if (environment.equals("production")) {
7 DB_URL = "prod.database.com";
8 DB_PORT = 5432;
9 } else if (environment.equals("staging")) {
10 DB_URL = "staging.database.com";
11 DB_PORT = 5433;
12 } else {
13 DB_URL = "localhost";
14 DB_PORT = 5434;
15 }
16 // After constructor, DB_URL and DB_PORT are locked
17 }
18}Example 5: static final Variables (True Constants)
What You'll Learn:
- ›Combining
staticandfinalcreates class-level constants - ›Most common pattern you'll see in real projects
- ›These constants are shared across all instances
1public class PaymentGateway {
2 // Class-level constants (static final)
3 public static final String RAZORPAY_API_KEY = "rzp_live_xxxxx";
4 public static final String PAYTM_API_KEY = "ptm_live_xxxxx";
5 public static final double TRANSACTION_FEE_PERCENT = 2.5;
6 public static final int MAX_RETRY_ATTEMPTS = 3;
7 public static final long TIMEOUT_MILLISECONDS = 30000; // 30 seconds
8
9 public static void main(String[] args) {
10 // Using constants without creating objects
11 System.out.println("Razorpay Key: " + RAZORPAY_API_KEY);
12 System.out.println("Transaction Fee: " + TRANSACTION_FEE_PERCENT + "%");
13 System.out.println("Max Retries: " + MAX_RETRY_ATTEMPTS);
14 System.out.println("Timeout: " + TIMEOUT_MILLISECONDS + "ms");
15
16 // Calculate transaction fee
17 double orderAmount = 1000.0;
18 double fee = orderAmount * (TRANSACTION_FEE_PERCENT / 100);
19 double totalAmount = orderAmount + fee;
20
21 System.out.println("\nOrder Amount: ₹" + orderAmount);
22 System.out.println("Transaction Fee: ₹" + fee);
23 System.out.println("Total Amount: ₹" + totalAmount);
24
25 // Cannot change these constants
26 // TRANSACTION_FEE_PERCENT = 3.0; // ❌ COMPILE ERROR!
27 // MAX_RETRY_ATTEMPTS = 5; // ❌ COMPILE ERROR!
28 }
29}Output:
Razorpay Key: rzp_live_xxxxx
Transaction Fee: 2.5%
Max Retries: 3
Timeout: 30000ms
Order Amount: ₹1000.0
Transaction Fee: ₹25.0
Total Amount: ₹1025.0
What This Program Does:
- ›
Lines 3-7: Declares class-level constants
- ›
public= accessible from anywhere - ›
static= belongs to class, not objects (shared by all instances) - ›
final= cannot be changed - ›All initialized when declared
- ›
- ›
Lines 11-14: Uses constants directly without creating objects
- ›No need to write
new PaymentGateway() - ›Access directly:
PaymentGateway.TRANSACTION_FEE_PERCENT
- ›No need to write
- ›
Lines 17-19: Uses constant in calculation
- ›Calculates transaction fee based on constant
- ›Constants make calculations consistent across the app
- ›
Lines 26-27: Cannot modify these values
Standard Pattern in Real Projects:
1public class AppConstants {
2 // API Configuration
3 public static final String BASE_URL = "https://api.myapp.com";
4 public static final int API_TIMEOUT = 5000;
5
6 // Database Configuration
7 public static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver";
8 public static final int DB_POOL_SIZE = 20;
9
10 // Business Rules
11 public static final int MIN_PASSWORD_LENGTH = 8;
12 public static final int MAX_LOGIN_ATTEMPTS = 3;
13 public static final double GST_RATE = 18.0;
14
15 // File Upload Limits
16 public static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
17 public static final String[] ALLOWED_EXTENSIONS = {".jpg", ".png", ".pdf"};
18}Usage in Other Classes:
1import static AppConstants.*;
2
3public class UserService {
4 public boolean validatePassword(String password) {
5 return password.length() >= MIN_PASSWORD_LENGTH;
6 }
7}Interview Question:
"What's the difference between static final and just final?"
Answer:
- ›
finalalone: Instance-level constant, each object can have different value (set in constructor) - ›
static final: Class-level constant, one value shared by all objects - ›
static finalis most common for application-wide constants
Example 6: final Method Parameters
What You'll Learn:
- ›Making method parameters final prevents accidental modification
- ›Good practice for important parameters
- ›Makes code safer and intentions clear
1public class OrderProcessor {
2 // Method with final parameters
3 public static void calculateTotal(final double basePrice, final double discount) {
4 // Parameters are final - cannot be modified inside method
5
6 System.out.println("Base Price: ₹" + basePrice);
7 System.out.println("Discount: " + discount + "%");
8
9 // Calculate discount amount and final price
10 double discountAmount = basePrice * (discount / 100);
11 double finalPrice = basePrice - discountAmount;
12
13 System.out.println("Discount Amount: ₹" + discountAmount);
14 System.out.println("Final Price: ₹" + finalPrice);
15
16 // ❌ Cannot modify final parameters
17 // basePrice = 1000.0; // COMPILE ERROR!
18 // discount = 20.0; // COMPILE ERROR!
19 }
20
21 // Method without final parameters (for comparison)
22 public static void processRefund(double amount) {
23 System.out.println("Original refund: ₹" + amount);
24
25 // ✅ Can modify non-final parameter
26 amount = amount - 50; // Deduct processing fee
27
28 System.out.println("After deduction: ₹" + amount);
29 }
30
31 public static void main(String[] args) {
32 System.out.println("=== Order Calculation ===");
33 calculateTotal(5000.0, 10.0);
34
35 System.out.println("\n=== Refund Processing ===");
36 processRefund(1000.0);
37 }
38}Output:
=== Order Calculation ===
Base Price: ₹5000.0
Discount: 10.0%
Discount Amount: ₹500.0
Final Price: ₹4500.0
=== Refund Processing ===
Original refund: ₹1000.0
After deduction: ₹950.0
What This Program Does:
- ›
Line 3: Method with
finalparameters- ›
basePriceanddiscountcannot be changed inside the method - ›Protects parameters from accidental modification
- ›
- ›
Lines 10-11: Uses parameters in calculations
- ›Reading and using parameters is always allowed
- ›Just can't reassign them
- ›
Lines 17-18: Shows what final prevents
- ›Trying to modify would cause compile error
- ›
Lines 22-28: Contrasting example without final
- ›Parameter
amountcan be modified - ›Line 26 changes the parameter value
- ›Parameter
- ›
Lines 31-35: Calls both methods to show difference
Why Use final Parameters?
✅ Advantages:
- ›Prevents bugs from accidental reassignment
- ›Makes code intent clear: "this value won't change"
- ›Helpful in long methods where parameter might be modified by mistake
- ›Some code reviewers require this
❌ When Not to Use:
- ›Short, simple methods
- ›When you actually need to modify the parameter
- ›Can make code verbose
Real-Time Example:
1public class PaymentService {
2 public boolean processPayment(final String userId,
3 final double amount,
4 final String paymentMethod) {
5 // userId, amount, paymentMethod are critical
6 // They should NOT be modified during payment processing
7
8 validateUser(userId);
9 validateAmount(amount);
10 validatePaymentMethod(paymentMethod);
11
12 // Process payment with original values
13 return executePayment(userId, amount, paymentMethod);
14 }
15}Interview Tip:
Mentioning that you use final for method parameters shows you write defensive, safe code. But don't overuse it - use it for important parameters in critical methods.
2. final Methods - Preventing Method Overriding
What You'll Learn:
- ›How
finalprevents method overriding - ›When and why to make methods final
- ›Real-world security scenarios
Basic Concept:
When a method is final, child classes can inherit it but cannot override (change) it.
Example 7: final Method Basics
1// Parent class
2class BankAccount {
3 protected double balance;
4
5 public BankAccount(double balance) {
6 this.balance = balance;
7 }
8
9 // final method - cannot be overridden
10 public final void displayBalance() {
11 System.out.println("Current Balance: ₹" + balance);
12 System.out.println("(Displayed using secure method)");
13 }
14
15 // Regular method - can be overridden
16 public void withdraw(double amount) {
17 if (amount <= balance) {
18 balance -= amount;
19 System.out.println("Withdrawn: ₹" + amount);
20 } else {
21 System.out.println("Insufficient balance!");
22 }
23 }
24}
25
26// Child class
27class SavingsAccount extends BankAccount {
28 private double interestRate;
29
30 public SavingsAccount(double balance, double interestRate) {
31 super(balance);
32 this.interestRate = interestRate;
33 }
34
35 // ✅ Can override regular method
36 @Override
37 public void withdraw(double amount) {
38 System.out.println("Savings Account Withdrawal");
39 if (balance >= 1000) { // Minimum balance rule
40 super.withdraw(amount);
41 } else {
42 System.out.println("Maintain minimum balance of ₹1000");
43 }
44 }
45
46 // ❌ CANNOT override final method
47 // @Override
48 // public void displayBalance() { // COMPILE ERROR!
49 // System.out.println("Custom display");
50 // }
51 // Error: displayBalance() in SavingsAccount cannot override
52 // displayBalance() in BankAccount; overridden method is final
53}
54
55public class FinalMethodExample {
56 public static void main(String[] args) {
57 SavingsAccount account = new SavingsAccount(5000, 4.5);
58
59 // ✅ Can call final method (inherited from parent)
60 account.displayBalance();
61
62 // ✅ Can call overridden method
63 account.withdraw(500);
64
65 account.displayBalance();
66 }
67}Output:
Current Balance: ₹5000.0
(Displayed using secure method)
Savings Account Withdrawal
Withdrawn: ₹500.0
Current Balance: ₹4500.0
(Displayed using secure method)
What This Program Does:
- ›
Lines 2-23: Parent class
BankAccount- ›Line 10:
displayBalance()isfinal- locked, cannot be changed - ›Lines 16-22:
withdraw()is regular - can be overridden
- ›Line 10:
- ›
Lines 26-51: Child class
SavingsAccount- ›Lines 35-43: Successfully overrides
withdraw()method- ›Adds savings-specific logic (minimum balance check)
- ›Lines 46-51: Shows compile error when trying to override
displayBalance()- ›final methods cannot be overridden
- ›Lines 35-43: Successfully overrides
- ›
Lines 54-63: Using SavingsAccount
- ›Calls
displayBalance()- uses parent's final method - ›Calls
withdraw()- uses child's overridden method
- ›Calls
Why Make displayBalance() final?
- ›It might contain critical formatting or security checks
- ›Bank wants ALL account types to display balance the same way
- ›Prevents child classes from changing the display logic
Interview Question: "Can you call a final method from child class?" Answer: "Yes! Child class inherits final methods and can call them. It just cannot override (change) them."
Example 8: final Method for Security
Real-World Scenario: Banking application where authentication must work identically for all account types.
1// Parent class - BankAccount
2class BankAccount {
3 protected String accountNumber;
4 protected String accountHolderName;
5 private String hashedPassword; // In real app, never store plain password
6
7 public BankAccount(String accountNumber, String name, String password) {
8 this.accountNumber = accountNumber;
9 this.accountHolderName = name;
10 this.hashedPassword = hashPassword(password); // Simulate hashing
11 }
12
13 // Simulate password hashing (in real app, use BCrypt or similar)
14 private String hashPassword(String password) {
15 return "HASHED_" + password; // Simplified for demo
16 }
17
18 // ⚠️ SECURITY-CRITICAL: final method - authentication logic MUST be same for all
19 public final boolean authenticate(String enteredPassword) {
20 System.out.println("🔒 Running secure authentication...");
21
22 // Step 1: Log authentication attempt
23 System.out.println(" → Logging authentication attempt for: " + accountNumber);
24
25 // Step 2: Hash the entered password
26 String enteredHash = hashPassword(enteredPassword);
27
28 // Step 3: Compare hashes
29 boolean isValid = this.hashedPassword.equals(enteredHash);
30
31 // Step 4: Log result
32 if (isValid) {
33 System.out.println(" ✓ Authentication successful");
34 } else {
35 System.out.println(" ✗ Authentication failed");
36 }
37
38 return isValid;
39 }
40
41 // Regular method - can be customized by child classes
42 public void showAccountInfo() {
43 System.out.println("Account: " + accountNumber);
44 System.out.println("Holder: " + accountHolderName);
45 }
46}
47
48// Child class - SavingsAccount
49class SavingsAccount extends BankAccount {
50 private double interestRate;
51
52 public SavingsAccount(String accountNumber, String name,
53 String password, double interestRate) {
54 super(accountNumber, name, password);
55 this.interestRate = interestRate;
56 }
57
58 // ✅ Can override display method
59 @Override
60 public void showAccountInfo() {
61 System.out.println("=== Savings Account ===");
62 super.showAccountInfo();
63 System.out.println("Interest Rate: " + interestRate + "%");
64 }
65
66 // ❌ CANNOT override authenticate() - it's final and security-critical
67 // @Override
68 // public boolean authenticate(String password) {
69 // return true; // Malicious attempt to bypass security - BLOCKED!
70 // }
71}
72
73// Child class - CurrentAccount
74class CurrentAccount extends BankAccount {
75 private double overdraftLimit;
76
77 public CurrentAccount(String accountNumber, String name,
78 String password, double overdraftLimit) {
79 super(accountNumber, name, password);
80 this.overdraftLimit = overdraftLimit;
81 }
82
83 @Override
84 public void showAccountInfo() {
85 System.out.println("=== Current Account ===");
86 super.showAccountInfo();
87 System.out.println("Overdraft Limit: ₹" + overdraftLimit);
88 }
89}
90
91public class BankingSecurityExample {
92 public static void main(String[] args) {
93 SavingsAccount savings = new SavingsAccount("SA-12345", "Rahul", "pass123", 4.5);
94 CurrentAccount current = new CurrentAccount("CA-67890", "Priya", "secure456", 50000);
95
96 System.out.println("=== Testing Savings Account ===");
97 savings.showAccountInfo();
98 System.out.println();
99
100 // Try wrong password
101 savings.authenticate("wrongpass");
102 System.out.println();
103
104 // Try correct password
105 savings.authenticate("pass123");
106 System.out.println("\n" + "=".repeat(40) + "\n");
107
108 System.out.println("=== Testing Current Account ===");
109 current.showAccountInfo();
110 System.out.println();
111
112 // Current account also uses SAME authentication logic
113 current.authenticate("secure456");
114 }
115}Output:
=== Testing Savings Account ===
=== Savings Account ===
Account: SA-12345
Holder: Rahul
Interest Rate: 4.5%
🔒 Running secure authentication...
→ Logging authentication attempt for: SA-12345
✗ Authentication failed
🔒 Running secure authentication...
→ Logging authentication attempt for: SA-12345
✓ Authentication successful
========================================
=== Testing Current Account ===
=== Current Account ===
Account: CA-67890
Holder: Priya
Overdraft Limit: ₹50000.0
🔒 Running secure authentication...
→ Logging authentication attempt for: CA-67890
✓ Authentication successful
What This Program Does:
- ›
Lines 2-41: Parent class with final authentication method
- ›Line 19:
authenticate()isfinal- security-critical - ›Lines 20-37: Authentication logic with 4 steps:
- ›Log attempt
- ›Hash password
- ›Compare hashes
- ›Log result
- ›This logic MUST be same for all account types
- ›Line 19:
- ›
Lines 44-69: SavingsAccount class
- ›Can override
showAccountInfo()- not security-critical - ›CANNOT override
authenticate()- it's final - ›Lines 66-69 show what happens if you try
- ›Can override
- ›
Lines 72-86: CurrentAccount class
- ›Also cannot override authentication
- ›Uses same secure authentication as parent
- ›
Lines 89-110: Testing both account types
- ›Both use identical authentication logic
- ›No way for child classes to bypass security
Why This Matters:
🔒 Security Benefits:
- ›Consistent Security: All account types authenticate the same way
- ›No Bypass: Child classes cannot create "backdoor" authentication
- ›Centralized Logic: Authentication changes happen in one place
- ›Audit Trail: Single point for security logging
Real-World Equivalents:
- ›E-commerce: Payment validation must be same for all payment methods
- ›Social Media: User verification must be consistent across all user types
- ›Hospital System: Patient record access authentication cannot be overridden
Interview Question: "Why would you make a method final instead of just private?" Answer:
- ›
private: Not inherited at all, child classes don't even know it exists - ›
final: Inherited and callable by child classes, but cannot be overridden - ›Use
finalwhen child classes need to USE the method but shouldn't CHANGE it
3. final Classes - Preventing Inheritance
What You'll Learn:
- ›How
finalprevents class inheritance - ›When to make a class final
- ›Immutability and security benefits
Basic Concept:
A final class cannot be extended. No one can create child classes of a final class.
Example 9: final Class Basics - Utility Class
1// final class - cannot be extended
2final class StringUtils {
3 // Utility methods for string operations
4
5 public static boolean isEmpty(String str) {
6 return str == null || str.trim().isEmpty();
7 }
8
9 public static String capitalize(String str) {
10 if (isEmpty(str)) {
11 return str;
12 }
13 return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
14 }
15
16 public static int wordCount(String str) {
17 if (isEmpty(str)) {
18 return 0;
19 }
20 return str.trim().split("\\s+").length;
21 }
22
23 public static String reverse(String str) {
24 if (isEmpty(str)) {
25 return str;
26 }
27 return new StringBuilder(str).reverse().toString();
28 }
29}
30
31// ❌ Trying to extend final class - COMPILE ERROR
32// class AdvancedStringUtils extends StringUtils {
33// // Additional methods
34// }
35// Error: cannot inherit from final StringUtils
36
37public class UtilityClassExample {
38 public static void main(String[] args) {
39 // ✅ Can use final class methods normally
40
41 String text = "hello world";
42
43 System.out.println("Original: " + text);
44 System.out.println("Capitalized: " + StringUtils.capitalize(text));
45 System.out.println("Word count: " + StringUtils.wordCount(text));
46 System.out.println("Reversed: " + StringUtils.reverse(text));
47
48 String empty = " ";
49 System.out.println("\nIs empty string empty? " + StringUtils.isEmpty(empty));
50 }
51}Output:
Original: hello world
Capitalized: Hello world
Word count: 2
Reversed: dlrow olleh
Is empty string empty? true
What This Program Does:
- ›
Lines 2-28: Final utility class
- ›
final class StringUtilsmeans no inheritance allowed - ›Contains utility methods for string operations
- ›All methods are
static- don't need object to use them
- ›
- ›
Lines 31-35: Shows compile error when trying to extend
- ›Cannot create child class of final class
- ›
Lines 38-50: Using the final class
- ›Can use all methods normally
- ›Just cannot create child classes
Why Make StringUtils final?
✅ Reasons:
- ›Complete Functionality: Class is feature-complete, doesn't need extensions
- ›Utility Nature: Utility classes shouldn't have variants
- ›Clear Design Intent: "This is a tool, not a base for inheritance"
- ›Prevention of Misuse: Stops people from creating unnecessary subclasses
Real-World Example:
1final class MathUtils {
2 public static final double PI = 3.14159;
3
4 public static double calculateCircleArea(double radius) {
5 return PI * radius * radius;
6 }
7
8 public static double calculateRectangleArea(double length, double width) {
9 return length * width;
10 }
11
12 public static int factorial(int n) {
13 if (n <= 1) return 1;
14 return n * factorial(n - 1);
15 }
16
17 // Make constructor private to prevent instantiation
18 private MathUtils() {
19 throw new AssertionError("Utility class - do not instantiate");
20 }
21}Famous Final Classes in Java:
- ›
Stringclass is final - ›
Integer,Double,Boolean(all wrapper classes) are final - ›
Mathclass is final - ›
Systemclass is final
Interview Question: "Why is String class final in Java?" Answer:
- ›Security: String is used for sensitive data (passwords, URLs). If it could be extended, malicious code could override methods
- ›Immutability: Making String final helps ensure it stays immutable
- ›Performance: JVM can optimize String operations knowing the class won't change
- ›String Pool: String interning wouldn't work reliably if String could be extended
Example 10: Real-World final Class - Immutable Value Object
What You'll Learn:
- ›Creating immutable classes using final
- ›Why immutability matters in real applications
- ›Thread-safety benefits
1// Immutable Money class - final class with final fields
2final class Money {
3 // All fields are final - set once, never change
4 private final double amount;
5 private final String currency;
6
7 // Constructor - only way to set values
8 public Money(double amount, String currency) {
9 if (amount < 0) {
10 throw new IllegalArgumentException("Amount cannot be negative");
11 }
12 if (currency == null || currency.trim().isEmpty()) {
13 throw new IllegalArgumentException("Currency cannot be null or empty");
14 }
15
16 this.amount = amount;
17 this.currency = currency;
18 }
19
20 // Only getters - NO setters
21 public double getAmount() {
22 return amount;
23 }
24
25 public String getCurrency() {
26 return currency;
27 }
28
29 // Methods return NEW objects instead of modifying this object
30 public Money add(Money other) {
31 if (!this.currency.equals(other.currency)) {
32 throw new IllegalArgumentException("Cannot add different currencies: "
33 + this.currency + " and " + other.currency);
34 }
35 return new Money(this.amount + other.amount, this.currency);
36 }
37
38 public Money subtract(Money other) {
39 if (!this.currency.equals(other.currency)) {
40 throw new IllegalArgumentException("Cannot subtract different currencies");
41 }
42 double result = this.amount - other.amount;
43 if (result < 0) {
44 throw new IllegalArgumentException("Result cannot be negative");
45 }
46 return new Money(result, this.currency);
47 }
48
49 public Money multiply(double multiplier) {
50 return new Money(this.amount * multiplier, this.currency);
51 }
52
53 @Override
54 public String toString() {
55 return currency + " " + String.format("%.2f", amount);
56 }
57}
58
59// ❌ Cannot extend Money - it's final
60// class ExtendedMoney extends Money { // COMPILE ERROR!
61// }
62
63public class ImmutableMoneyExample {
64 public static void main(String[] args) {
65 // Create money objects
66 Money salary = new Money(50000, "INR");
67 Money bonus = new Money(10000, "INR");
68 Money tax = new Money(5000, "INR");
69
70 System.out.println("Salary: " + salary);
71 System.out.println("Bonus: " + bonus);
72 System.out.println("Tax: " + tax);
73
74 // Perform calculations - returns NEW objects
75 Money totalIncome = salary.add(bonus);
76 System.out.println("\nTotal Income: " + totalIncome);
77
78 Money netIncome = totalIncome.subtract(tax);
79 System.out.println("Net Income (after tax): " + netIncome);
80
81 // Original objects are UNCHANGED
82 System.out.println("\nOriginal salary still: " + salary);
83 System.out.println("Original bonus still: " + bonus);
84
85 // Calculate monthly income
86 Money monthlyIncome = netIncome.multiply(1.0 / 12);
87 System.out.println("\nMonthly Income: " + monthlyIncome);
88
89 // Try to add different currencies - will throw exception
90 try {
91 Money dollars = new Money(100, "USD");
92 Money result = salary.add(dollars); // ERROR!
93 } catch (IllegalArgumentException e) {
94 System.out.println("\nError: " + e.getMessage());
95 }
96 }
97}Output:
Salary: INR 50000.00
Bonus: INR 10000.00
Tax: INR 5000.00
Total Income: INR 60000.00
Net Income (after tax): INR 55000.00
Original salary still: INR 50000.00
Original bonus still: INR 10000.00
Monthly Income: INR 4583.33
Error: Cannot add different currencies: INR and USD
What This Program Does:
- ›
Line 2: Class declared
final- ›Cannot be extended
- ›No one can create mutable subclass
- ›
Lines 4-5: All fields are
final- ›
amountandcurrencyset in constructor - ›Can NEVER be changed after object creation
- ›
- ›
Lines 8-18: Constructor with validation
- ›Only place to set field values
- ›Validates input before creating object
- ›After construction, object is immutable
- ›
Lines 21-27: Only getters, NO setters
- ›Can read values but not modify them
- ›No
setAmount()orsetCurrency()methods exist
- ›
Lines 30-52: Operations return NEW objects
- ›
add()doesn't modify original, returns new Money - ›
subtract()returns new Money - ›
multiply()returns new Money - ›Original object never changes
- ›
- ›
Lines 64-96: Using immutable Money objects
- ›Shows that operations create new objects
- ›Original objects remain unchanged
- ›Demonstrates currency mismatch protection
Benefits of Immutability:
✅ Thread-Safe:
1// Multiple threads can safely share Money objects
2public class BankAccount {
3 private Money balance; // Safe to share between threads
4
5 public synchronized void deposit(Money amount) {
6 this.balance = balance.add(amount); // Creates new object
7 }
8}✅ Predictable:
1Money price = new Money(100, "INR");
2processOrder(price);
3// price is STILL 100 INR - processOrder cannot modify it✅ Safe HashMap Keys:
1Map<Money, String> transactionLog = new HashMap<>();
2Money amount = new Money(500, "INR");
3transactionLog.put(amount, "Payment received");
4// amount cannot change, so HashMap stays consistent✅ Easier Debugging:
1Money price = new Money(1000, "INR");
2// Throughout the entire program, price is ALWAYS 1000 INR
3// No need to worry about who might have changed itReal-World Examples of Immutable Classes:
- ›
String- Most famous immutable class in Java - ›
LocalDate,LocalTime,LocalDateTime- Java 8 date/time classes - ›
BigDecimal- For precise decimal calculations - ›
Integer,Double, etc. - Wrapper classes
Interview Question: "How do you create an immutable class in Java?" Answer:
- ›Declare class as
final(prevent inheritance) - ›Make all fields
privateandfinal - ›Initialize all fields in constructor
- ›Provide only getter methods, NO setters
- ›Don't provide methods that modify object state
- ›If fields are mutable objects, return defensive copies
- ›Validate input in constructor
How final Works Internally
Understanding what happens behind the scenes helps you use final effectively.
For final Variables:
At Compile Time:
1final int MAX_SIZE = 100;What Compiler Does:
- ›Creates a constant reference in bytecode
- ›Replaces usage with literal value (optimization)
- ›Ensures no reassignment code exists
- ›If reassignment found → Compile error
Optimization Example:
1final int SIZE = 10;
2int[] array = new int[SIZE];Compiler optimizes to:
1int[] array = new int[10]; // SIZE directly replaced with 10For final Methods:
Normal Method Call (without final):
1. Runtime looks at object's actual type
2. Finds which version of method to call (dynamic binding)
3. Calls the method
final Method Call:
1. Compiler knows method won't be overridden
2. Can use direct method call (static binding)
3. Can inline the method code (performance boost)
4. No need for dynamic lookup
Performance Benefit:
1// Without final - slower
2public void process() { ... }
3
4// With final - faster
5public final void process() { ... }
6// Compiler can inline this methodFor final Classes:
What JVM Knows:
1final class Value {
2 private int data;
3}JVM Optimizations:
- ›No need to check for subclass methods
- ›Can devirtualize method calls
- ›Can inline more aggressively
- ›Better memory layout optimization
Common Mistakes Beginners Make
Mistake 1: Forgetting to Initialize final Variable
1// ❌ WRONG - Compile error
2public class WrongExample {
3 final int value; // Not initialized!
4
5 public void someMethod() {
6 // value = 10; // Too late! Must initialize in constructor
7 }
8}
9// Error: variable value might not have been initialized
10
11// ✅ CORRECT - Option 1: Initialize when declaring
12public class Correct1 {
13 final int value = 10; // Initialized immediately
14}
15
16// ✅ CORRECT - Option 2: Initialize in constructor
17public class Correct2 {
18 final int value;
19
20 public Correct2() {
21 value = 10; // Initialized in constructor
22 }
23}
24
25// ✅ CORRECT - Option 3: Initialize in instance initializer
26public class Correct3 {
27 final int value;
28
29 {
30 value = 10; // Instance initializer block
31 }
32}Explanation: final variables have only 3 chances to be initialized:
- ›When declared
- ›In instance initializer block
- ›In constructor
After that, they're locked forever.
Mistake 2: Thinking final Makes Objects Immutable
1// ❌ COMMON MISUNDERSTANDING
2public class MisunderstandingExample {
3 public static void main(String[] args) {
4 final List<String> list = new ArrayList<>();
5
6 // Beginners think this won't work because list is final
7 list.add("Item 1"); // ✅ This WORKS!
8 list.add("Item 2"); // ✅ This WORKS!
9 list.clear(); // ✅ This WORKS!
10
11 System.out.println(list); // []
12
13 // THIS is what final prevents:
14 // list = new ArrayList<>(); // ❌ COMPILE ERROR!
15 }
16}Correct Understanding:
1final reference = object
2 ↓
3 LOCKED MUTABLE
4
5// final locks the ARROW (reference)
6// NOT the object itselfReal Example:
1final StringBuilder sb = new StringBuilder("Hello");
2sb.append(" World"); // ✅ Can modify
3// sb = new StringBuilder(); // ❌ Cannot reassignInterview Tip: Always clarify: "final makes the reference immutable, not the object. The reference cannot be changed to point to a different object, but the object's internal state can still be modified."
Mistake 3: Overusing final
1// ❌ OVERUSE - Hard to read
2public class OveruseExample {
3 public void process(final int a, final int b, final int c,
4 final int d, final int e) {
5 final int sum = a + b;
6 final int diff = c - d;
7 final int product = sum * diff;
8 final int temp1 = product + e;
9 final int temp2 = temp1 * 2;
10 final int result = temp2 - 10;
11
12 System.out.println(result);
13 }
14}
15
16// ✅ BETTER - Use final where it adds value
17public class BetterExample {
18 // final for class constants
19 private static final int TAX_RATE = 18;
20
21 public void process(int a, int b, int c, int d, int e) {
22 int sum = a + b;
23 int diff = c - d;
24 int product = sum * diff;
25 int temp = product + e;
26 int result = (temp * 2) - 10;
27
28 System.out.println(result);
29 }
30}When to Use final:
✅ Class-level constants (static final)
✅ Configuration values
✅ Method parameters in critical methods
✅ Variables that truly shouldn't change
When NOT to Use: ❌ Every local variable ❌ Simple temporary calculations ❌ When it reduces readability
Mistake 4: Confusing final with Immutability
1// ❌ WRONG ASSUMPTION
2final class Person {
3 private String name; // NOT final - can be modified!
4
5 public Person(String name) {
6 this.name = name;
7 }
8
9 public void setName(String name) {
10 this.name = name; // Can still modify!
11 }
12}
13
14// This is NOT immutable even though class is final!
15Person p = new Person("Rahul");
16p.setName("Priya"); // ✅ This works! Person is mutable
17
18// ✅ CORRECT - Truly immutable class
19final class ImmutablePerson {
20 private final String name; // Field is final
21
22 public ImmutablePerson(String name) {
23 this.name = name;
24 }
25
26 // Only getter, NO setter
27 public String getName() {
28 return name;
29 }
30}
31
32ImmutablePerson p2 = new ImmutablePerson("Rahul");
33// p2.setName("Priya"); // Method doesn't exist!For True Immutability:
- ›✅ Class must be
final - ›✅ All fields must be
final - ›✅ All fields must be
private - ›✅ No setters
- ›✅ Defensive copies for mutable fields
Mistake 5: Not Understanding final with Inheritance
1class Parent {
2 public final void display() {
3 System.out.println("Parent display");
4 }
5}
6
7class Child extends Parent {
8 // ❌ COMMON MISTAKE - Trying to override
9 // @Override
10 // public void display() { // COMPILE ERROR!
11 // System.out.println("Child display");
12 // }
13}
14
15// ✅ CORRECT - Can call, cannot override
16class Child extends Parent {
17 public void show() {
18 display(); // ✅ Can call parent's final method
19 System.out.println("Child show");
20 }
21}Remember:
- ›final method can be CALLED by child
- ›final method cannot be OVERRIDDEN by child
- ›Child class inherits final method as-is
Best Practices
1. Use final for Constants
1// ✅ GOOD - Clear constants
2public class AppConfig {
3 public static final String DATABASE_URL = "jdbc:mysql://localhost:3306/mydb";
4 public static final int CONNECTION_TIMEOUT = 5000;
5 public static final double TAX_RATE = 18.0;
6}Why:
- ›Makes constants obvious
- ›Prevents accidental modification
- ›Single source of truth
- ›Easy to maintain
2. Make Utility Classes final
1// ✅ GOOD - Utility class pattern
2public final class ValidationUtils {
3 // Private constructor prevents instantiation
4 private ValidationUtils() {
5 throw new AssertionError("Utility class");
6 }
7
8 public static boolean isValidEmail(String email) {
9 return email != null && email.contains("@");
10 }
11
12 public static boolean isValidPhone(String phone) {
13 return phone != null && phone.matches("\\d{10}");
14 }
15}Why:
- ›Utility classes shouldn't be extended
- ›Prevents misuse
- ›Clear design intent
3. Use final for Security-Critical Methods
1public class UserService {
2 // ✅ GOOD - Security method is final
3 public final boolean authenticateUser(String username, String password) {
4 // Critical authentication logic
5 // No subclass should bypass this
6 return checkCredentials(username, password);
7 }
8
9 // Regular method - can be overridden
10 public void sendWelcomeEmail(String email) {
11 // Non-critical functionality
12 }
13}Why:
- ›Prevents security bypasses
- ›Ensures consistent behavior
- ›Protects critical logic
4. Prefer Immutable Objects for Value Types
1// ✅ GOOD - Immutable value object
2public final class Address {
3 private final String street;
4 private final String city;
5 private final String zipCode;
6
7 public Address(String street, String city, String zipCode) {
8 this.street = street;
9 this.city = city;
10 this.zipCode = zipCode;
11 }
12
13 // Only getters
14 public String getStreet() { return street; }
15 public String getCity() { return city; }
16 public String getZipCode() { return zipCode; }
17}Why:
- ›Thread-safe
- ›Cacheable
- ›Predictable
- ›No defensive copying needed
5. Document Why Something is final
1/**
2 * Payment processor class.
3 * This class is final to prevent subclasses from bypassing
4 * security checks and payment validations.
5 */
6public final class PaymentProcessor {
7
8 /**
9 * Validates payment details.
10 * This method is final to ensure all payment methods
11 * use the same validation logic.
12 */
13 public final boolean validatePayment(PaymentDetails details) {
14 // ...
15 }
16}Why:
- ›Helps other developers understand design decisions
- ›Prevents accidental removal of final
- ›Documents security/design intent
Interview Questions & Answers
Q1: What is the final keyword in Java?
Answer:
The final keyword is a non-access modifier that creates constants and prevents modification. It can be applied to:
- ›Variables - Create constants that cannot be reassigned
- ›Methods - Prevent method overriding in subclasses
- ›Classes - Prevent class inheritance
Example:
1final int MAX_SIZE = 100; // Constant variable
2public final void validate() { } // Cannot override
3public final class Utils { } // Cannot extendQ2: What's the difference between final, finally, and finalize?
Answer: These are three completely different concepts:
final:
- ›Keyword for constants and preventing inheritance/overriding
- ›
final int x = 10;
finally:
- ›Block in try-catch for cleanup code
- ›Always executes whether exception occurs or not
1try {
2 // risky code
3} catch (Exception e) {
4 // handle error
5} finally {
6 // cleanup - always runs
7}finalize:
- ›Method called by garbage collector before object destruction
- ›Deprecated in Java 9+, use try-with-resources instead
1@Override
2protected void finalize() {
3 // cleanup before garbage collection
4}Q3: Can you change the value of a final variable?
Answer: For primitives: No, the value cannot change
1final int x = 10;
2// x = 20; // Compile errorFor objects: The reference cannot change, but object content can
1final List<String> list = new ArrayList<>();
2list.add("Item"); // ✅ Allowed - modifying content
3// list = new ArrayList<>(); // ❌ Not allowed - changing referenceKey Point: final locks the reference (the arrow), not what the reference points to.
Q4: Why is String class final in Java?
Answer: String is final for multiple reasons:
- ›
Security: String is used for sensitive data (passwords, URLs, file paths). If it could be extended, malicious code could override methods like
equals()orhashCode(). - ›
String Pool: Java maintains a string pool for memory optimization. If String could be extended, the pool wouldn't work reliably.
- ›
Immutability: Making String final ensures it remains immutable.
- ›
Thread Safety: Immutable final String can be safely shared between threads.
- ›
HashMap Keys: String is commonly used as HashMap key. Being final and immutable makes it reliable.
Q5: Can you override a final method?
Answer: No, you cannot override a final method. Child classes can inherit and call final methods, but cannot override them.
1class Parent {
2 public final void display() {
3 System.out.println("Parent");
4 }
5}
6
7class Child extends Parent {
8 // Cannot override
9 // public void display() { } // Compile error
10
11 public void show() {
12 display(); // ✅ Can call parent's final method
13 }
14}Q6: What's the difference between static final and final?
Answer:
final (instance-level):
- ›Each object can have different value
- ›Initialized in constructor
- ›Value is constant per object
1class Person {
2 private final String id; // Each person has their own constant ID
3
4 public Person(String id) {
5 this.id = id; // Set once in constructor
6 }
7}static final (class-level):
- ›One value shared by all objects
- ›Initialized when declared
- ›Application-wide constant
1class Config {
2 public static final double TAX_RATE = 18.0; // Same for all
3}Q7: Can final method be private?
Answer:
Yes, a method can be both private and final, but final is redundant.
1class Example {
2 private final void process() {
3 // This works, but 'final' is unnecessary
4 }
5}Why redundant?
- ›
privatemethods are not inherited - ›If not inherited, they cannot be overridden
- ›
finalprevents overriding, which is already prevented byprivate
Best Practice: Don't use final with private methods.
Q8: What are blank final variables?
Answer: Blank final variables are final variables declared without initialization. They must be initialized in constructor or instance initializer block.
1class User {
2 private final String userId; // Blank final
3
4 public User(String id) {
5 this.userId = id; // Must initialize in constructor
6 }
7}Use Case: When value depends on constructor parameter or runtime condition.
Q9: Can abstract methods be final?
Answer:
No, abstract and final are opposite concepts and cannot be used together.
1abstract class Example {
2 // public abstract final void process(); // Compile error!
3}Why?
- ›
abstractmeans "must be overridden by subclass" - ›
finalmeans "cannot be overridden" - ›These contradict each other
Q10: How do you create an immutable class?
Answer: Follow these steps:
- ›Declare class as
final - ›Make all fields
privateandfinal - ›Initialize fields in constructor
- ›Provide only getters, no setters
- ›Return defensive copies of mutable fields
- ›Don't provide methods that modify state
1public final class ImmutablePerson {
2 private final String name;
3 private final int age;
4 private final List<String> hobbies;
5
6 public ImmutablePerson(String name, int age, List<String> hobbies) {
7 this.name = name;
8 this.age = age;
9 // Defensive copy
10 this.hobbies = new ArrayList<>(hobbies);
11 }
12
13 public String getName() { return name; }
14 public int getAge() { return age; }
15
16 public List<String> getHobbies() {
17 // Return defensive copy
18 return new ArrayList<>(hobbies);
19 }
20}Summary
final Variables:
- ›✅ Create constants with
final int MAX = 100; - ›✅ For primitives: value is constant
- ›✅ For objects: reference is constant, content can change
- ›✅ Must be initialized when declared OR in constructor
- ›✅ Use UPPERCASE naming convention
final Methods:
- ›✅ Prevent overriding in child classes
- ›✅ Used for security-critical methods
- ›✅ Used for consistent behavior
- ›✅ Child classes can call but not override
final Classes:
- ›✅ Prevent inheritance (no child classes)
- ›✅ Used for utility classes
- ›✅ Required for immutable classes
- ›✅ Examples: String, Integer, Math
Best Practices:
- ›✅ Use
static finalfor application constants - ›✅ Make utility classes final
- ›✅ Use final for security methods
- ›✅ Create immutable value objects
- ›✅ Don't overuse final for local variables
Common Mistakes to Avoid:
- ›❌ Forgetting to initialize final variables
- ›❌ Thinking final makes objects immutable
- ›❌ Overusing final everywhere
- ›❌ Using final with private methods (redundant)
FAQs
1. When should I use final keyword?
Use final when:
- ›Creating application-wide constants
- ›Protecting security-critical methods
- ›Creating immutable classes
- ›Preventing accidental modifications
- ›Documenting that a value/method/class shouldn't change
Don't overuse for every variable.
2. Does final improve performance?
Yes, but minimally:
- ›JVM can optimize final variables by inlining values
- ›final methods can be inlined by JIT compiler
- ›final classes enable better optimization
However:
- ›Performance gain is usually negligible
- ›Use final for design reasons, not performance
- ›Modern JVMs are smart enough to optimize without final
3. Can I make a constructor final?
No. Constructors cannot be final because:
- ›Constructors are not inherited
- ›They cannot be overridden
- ›
finalkeyword doesn't apply to constructors
1// This is WRONG:
2// public final ClassName() { } // Compile error4. What's the difference between final and const?
Java doesn't have a const keyword. Use final instead.
In other languages (like C++):
- ›
const= constant value - ›
final= not available
In Java:
- ›
final= serves both purposes - ›Use
static finalfor compile-time constants
5. Can I modify a final array?
Yes! You can modify array contents, but cannot assign a new array.
1final int[] numbers = {1, 2, 3};
2
3numbers[0] = 10; // ✅ Allowed - modifying content
4numbers = new int[5]; // ❌ Not allowed - new referenceThe reference is final, not the array contents.
6. Is it mandatory to initialize final variables?
Yes! Final variables must be initialized:
- ›When declared, OR
- ›In constructor, OR
- ›In instance initializer block
If not initialized, you get a compile error.
7. Can interface variables be non-final?
No. All interface variables are implicitly public static final (constants).
1interface Config {
2 int MAX_SIZE = 100; // Automatically public static final
3 // Cannot change this value
4}8. What happens if I don't use final for constants?
Nothing breaks, but:
- ›Values can be accidentally modified
- ›Code is less readable (not obvious it's a constant)
- ›Thread-safety might be compromised
- ›No compiler protection against changes
Best Practice: Always use final for constants.
9. Can I use final with var keyword?
Yes! You can combine final with var (Java 10+):
1final var name = "Rahul"; // Type inferred as String
2// name = "Priya"; // Compile errorBut for constants, explicit type is more readable:
1// Preferred for constants
2final String NAME = "Rahul";10. How does final help with thread safety?
final fields are thread-safe because:
- ›Once initialized, they never change
- ›JVM guarantees visibility of final fields after construction
- ›No need for synchronization when reading final fields
1class ThreadSafeConfig {
2 private final String apiKey; // Safe to share between threads
3
4 public ThreadSafeConfig(String key) {
5 this.apiKey = key;
6 }
7
8 public String getApiKey() {
9 return apiKey; // No synchronization needed
10 }
11}Congratulations! 🎉 You now understand the final keyword thoroughly. Use it wisely to write safer, more maintainable Java code.
Remember: final is your friend for creating constants, protecting critical logic, and preventing unwanted modifications. Master it, and your code quality will improve significantly!