Group C — Long / numerical Questions (15 marks)

Q1Design and implement a Banking System in Java demonstrating Encapsulation, Inheritance, Polymorphism, Exception Handling, and File I/O.

Q1. Design and implement a Banking System in Java demonstrating Encapsulation, Inheritance, Polymorphism, Exception Handling, and File I/O.

Introduction
Object-Oriented Programming (OOP) provides a robust framework for building complex, real-world applications like a Banking System. By modeling the system using OOP principles, we ensure code reusability, security, and maintainability.

1. Core OOP Concepts Applied

  • Encapsulation (Data Hiding): The account balance and account number must be kept secure. We declare them as private variables and provide controlled access via public getter and setter methods. This prevents unauthorized direct modification.
  • Inheritance (Code Reusability): A generic Account class acts as the superclass. Specialized accounts like SavingsAccount and CurrentAccount inherit from it, promoting code reuse (e.g., both share the deposit logic).
  • Polymorphism (Dynamic Dispatch): The withdraw() method behaves differently for a Savings Account (cannot withdraw below minimum balance) vs a Current Account (allows overdraft). We achieve this via Method Overriding.
  • Exception Handling (Robustness): When a user tries to withdraw more than they have, instead of crashing, we throw a custom checked exception InsufficientFundsException.
  • File I/O (Persistence): To maintain an audit trail, every transaction is logged to a text file using FileWriter and BufferedWriter.

2. System Architecture (UML Class Diagram)

classDiagram class Account { -String accountNumber #double balance +deposit(amount: double) +withdraw(amount: double)* +getBalance() double #logTransaction(msg: String) } class SavingsAccount { -double interestRate +addInterest() +withdraw(amount: double) } class CurrentAccount { -double overdraftLimit +withdraw(amount: double) } class InsufficientFundsException { +InsufficientFundsException(message: String) } Account <|-- SavingsAccount Account <|-- CurrentAccount Account ..> InsufficientFundsException : throws

3. Complete Java Implementation

JAVA
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

// 1. Custom Exception (Exception Handling)
class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

// 2. Abstract Base Class (Abstraction & Encapsulation)
abstract class Account {
    private String accountNumber; // Encapsulated
    protected double balance;     // Accessible to child classes

    public Account(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }

    public String getAccountNumber() { return accountNumber; }
    public double getBalance() { return balance; }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            logTransaction("Deposited: $" + amount + " | New Balance: $" + balance);
        }
    }

    // Polymorphic method to be overridden
    public abstract void withdraw(double amount) throws InsufficientFundsException;

    // 3. File I/O for persistent logging
    protected void logTransaction(String message) {
        try (FileWriter fw = new FileWriter(accountNumber + "_log.txt", true);
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(message);
        } catch (IOException e) {
            System.err.println("Failed to log transaction for " + accountNumber);
        }
    }
}

// 4. Inheritance & Polymorphism (Savings Account)
class SavingsAccount extends Account {
    private double minimumBalance = 500.0;

    public SavingsAccount(String accNo, double initialBalance) {
        super(accNo, initialBalance);
    }

    @Override
    public void withdraw(double amount) throws InsufficientFundsException {
        if (balance - amount < minimumBalance) {
            throw new InsufficientFundsException("Transaction Failed: Minimum balance of $500 must be maintained.");
        }
        balance -= amount;
        logTransaction("Withdrew: $" + amount + " | New Balance: $" + balance);
    }
}

// 5. Inheritance & Polymorphism (Current Account)
class CurrentAccount extends Account {
    private double overdraftLimit = 1000.0;

    public CurrentAccount(String accNo, double initialBalance) {
        super(accNo, initialBalance);
    }

    @Override
    public void withdraw(double amount) throws InsufficientFundsException {
        if (balance - amount < -overdraftLimit) {
            throw new InsufficientFundsException("Transaction Failed: Overdraft limit of $" + overdraftLimit + " exceeded.");
        }
        balance -= amount;
        logTransaction("Withdrew: $" + amount + " | New Balance: $" + balance);
    }
}

// Execution Class
public class BankingSystem {
    public static void main(String[] args) {
        try {
            Account mySavings = new SavingsAccount("SAV-101", 2000);
            mySavings.deposit(500);
            mySavings.withdraw(300); // Success
            System.out.println("Savings Balance: $" + mySavings.getBalance());
            
            mySavings.withdraw(2000); // Throws Exception
        } catch (InsufficientFundsException e) {
            System.err.println(e.getMessage());
        }
    }
}

Conclusion

This implementation proves the power of OOP. If the bank decides to add a 'LoanAccount' tomorrow, we simply extend the Account class without modifying the existing, tested code (adhering to the Open/Closed Principle).

Q2Critically analyze Dynamic Method Dispatch in Java. Show how run-time polymorphism enables runtime pluggability with code examples.

Q2. Critically analyze Dynamic Method Dispatch in Java. Show how run-time polymorphism enables runtime pluggability with code examples.

Introduction
Polymorphism in Java is divided into Compile-time (Method Overloading) and Run-time (Method Overriding). Dynamic Method Dispatch (DMD) is the core mechanism by which Java implements run-time polymorphism. It allows Java to determine which overridden method to call at runtime rather than at compile time.

1. The Mechanism of Dynamic Method Dispatch

In Java, a superclass reference variable can point to a subclass object. When an overridden method is called through this superclass reference, the Java Virtual Machine (JVM) looks at the actual object type created in memory at runtime, NOT the reference type, to decide which method implementation to execute.

  • Compile Time: The compiler checks if the method exists in the superclass (the reference type). If it does, compilation succeeds.
  • Run Time: The JVM invokes the method belonging to the actual object type assigned to the reference.

2. Why is DMD Important? (Runtime Pluggability)

DMD is the foundation of Runtime Pluggability (or the Strategy Pattern). It allows a system to swap out behaviors dynamically without recompiling the core logic. A common example is a Payment Gateway in an e-commerce application. The cart doesn't need to know how a payment is processed (Credit Card vs UPI), it just needs to know that the payment strategy can be processed.

3. Code Example: E-Commerce Payment Gateway

JAVA
// 1. The Strategy Interface (Super type)
interface PaymentStrategy {
    void processPayment(double amount);
}

// 2. Concrete Implementation A (Sub type)
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    public CreditCardPayment(String card) { this.cardNumber = card; }
    
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing $" + amount + " via Credit Card: " + cardNumber);
        // Complex credit card API logic here
    }
}

// 3. Concrete Implementation B (Sub type)
class UPIPayment implements PaymentStrategy {
    private String upiId;
    public UPIPayment(String upi) { this.upiId = upi; }
    
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing $" + amount + " via UPI: " + upiId);
        // Complex UPI API logic here
    }
}

// 4. The Core Application
class ShoppingCart {
    // Pluggable dependency - Notice we use the interface reference!
    private PaymentStrategy paymentMethod;

    // Injecting the strategy at runtime
    public void setPaymentMethod(PaymentStrategy strategy) {
        this.paymentMethod = strategy;
    }

    public void checkout(double totalAmount) {
        if (paymentMethod == null) throw new IllegalStateException("Select a payment method.");
        
        // Dynamic Method Dispatch happens here!
        // The compiler only knows paymentMethod is of type PaymentStrategy.
        // The JVM dynamically calls the CreditCard or UPI processPayment at runtime.
        paymentMethod.processPayment(totalAmount);
    }
}

// Execution
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // User selects UPI at runtime
        cart.setPaymentMethod(new UPIPayment("user@okhdfcbank"));
        cart.checkout(150.50); // Outputs: Processing $150.5 via UPI
        
        // User switches to Credit Card at runtime
        cart.setPaymentMethod(new CreditCardPayment("4111-2222-3333-4444"));
        cart.checkout(5000.00); // Outputs: Processing $5000.0 via Credit Card
    }
}

Conclusion

Without Dynamic Method Dispatch, the ShoppingCart class would require complex, hard-coded if-else or switch statements to handle different payment types. DMD allows the code to be incredibly clean, modular, and adhering strictly to the Open/Closed Principle (open for extension by adding new payment classes, closed for modification of the cart class).

Q3Implement a Producer-Consumer problem in Java using Multithreading, Thread Synchronization, and wait()/notify() mechanisms.

Q3. Explain the Java Collections Framework (JCF). Differentiate between ArrayList, LinkedList, HashSet, and TreeSet with internal working details and time complexities.

Introduction
The Java Collections Framework (JCF) is a unified architecture representing and manipulating collections of objects (like a dynamic array, a set, or a queue). It provides ready-to-use, highly optimized, and standardized interfaces and classes, drastically reducing programming effort and increasing performance.

1. Architecture of JCF

The framework revolves around two main root interfaces:

  • Collection Interface: The root of the collection hierarchy. Extended by List, Set, and Queue.
  • Map Interface: Represents key-value pairs. Does not extend the Collection interface but is part of the JCF.

2. Detailed Comparison of Key Data Structures

Understanding the internal working of these classes is crucial for writing performant Java applications.

FeatureArrayList (List)LinkedList (List/Deque)HashSet (Set)TreeSet (Set)
Internal Data StructureDynamic, resizable Array.Doubly Linked List.Hash Table (backed by a HashMap).Self-Balancing Binary Search Tree (Red-Black Tree).
OrderingMaintains Insertion Order.Maintains Insertion Order.Unordered (No guarantee of order).Sorted Order (Natural sorting or via Comparator).
DuplicatesAllows duplicates.Allows duplicates.No duplicates.No duplicates.
Time Complexity (Search/Get)O(1) - Instant access via index.O(N) - Must traverse nodes.O(1) average case via hashing.O(log N) via binary search.
Time Complexity (Insert/Delete)O(N) - Requires shifting elements if not at the end.O(1) - If the node reference is known (just changing pointers).O(1) average case.O(log N) to maintain tree balance.
Best Use CaseFrequent read operations, random access.Frequent insertions and deletions in the middle of the list.Fastest way to ensure uniqueness and fast lookups.When you need a unique collection that is always sorted.

3. Internal Working Details

  • ArrayList: It starts with an initial capacity (default 10). When it gets full, it creates a new array (usually 1.5x the size), copies old elements over, and discards the old array. This makes adding elements at the end O(1) amortized, but inserting in the middle is O(N) due to array shifting.
  • LinkedList: Consists of 'Nodes'. Each node holds data, a pointer to the previous node, and a pointer to the next node. There is no contiguous memory allocation. Memory overhead is higher per element compared to ArrayList.
  • HashSet: Under the hood, it uses a HashMap where the element you insert is the 'Key' and a dummy object is the 'Value'. It uses the hashCode() and equals() methods to resolve hash collisions (using buckets with linked lists or balanced trees).
  • TreeSet: Backed by a TreeMap. It relies on the Comparable interface (or a provided Comparator) to organize the nodes in a Red-Black tree, ensuring operations remain O(log N) even in worst-case scenarios.

Conclusion

Choosing the right collection is the hallmark of a good Java developer. If you need fast access, use ArrayList. If you need fast sorting, use TreeSet. If you need lightning-fast unique storage, use HashSet. The JCF provides a perfectly tuned tool for every scenario.

Q4Design a custom Collection framework structure or generic Bounded Buffer class handling thread-safe concurrent access.

Q4. Implement a Producer-Consumer problem in Java using Multithreading, Thread Synchronization, and wait()/notify() mechanisms.

Introduction
The Producer-Consumer problem is a classic synchronization problem in concurrent programming. It involves two threads, a Producer and a Consumer, sharing a common, fixed-size buffer. The Producer's job is to generate data and put it into the buffer. The Consumer's job is to consume the data from the buffer. The challenge is to ensure the Producer doesn't try to add data to a full buffer, and the Consumer doesn't try to remove data from an empty buffer.

1. The Java Implementation

We use Java's built-in monitor locks (synchronized blocks) along with the wait() and notify() methods inherited from the Object class to achieve Inter-Thread Communication.

JAVA
import java.util.LinkedList;

// The Shared Buffer Class
class SharedBuffer {
    private LinkedList<Integer> list = new LinkedList<>();
    private int capacity = 2; // Bounded buffer size

    // Called by Producer thread
    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (this) {
                // Wait if buffer is full
                while (list.size() == capacity) {
                    System.out.println("Buffer is full. Producer is waiting...");
                    wait(); // Releases lock and waits
                }

                System.out.println("Producer produced: " + value);
                list.add(value++); // Add data to buffer

                // Notify the consumer that data is available
                notify();

                // Sleep to simulate time taken to produce
                Thread.sleep(1000);
            }
        }
    }

    // Called by Consumer thread
    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                // Wait if buffer is empty
                while (list.size() == 0) {
                    System.out.println("Buffer is empty. Consumer is waiting...");
                    wait(); // Releases lock and waits
                }

                // Consume data
                int val = list.removeFirst();
                System.out.println("Consumer consumed: " + val);

                // Notify the producer that space is available
                notify();

                // Sleep to simulate time taken to consume
                Thread.sleep(1000);
            }
        }
    }
}

// Main Execution Class
public class ProducerConsumerDemo {
    public static void main(String[] args) throws InterruptedException {
        final SharedBuffer buffer = new SharedBuffer();

        // Create Producer Thread
        Thread producerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try { buffer.produce(); }
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });

        // Create Consumer Thread
        Thread consumerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try { buffer.consume(); }
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });

        // Start both threads
        producerThread.start();
        consumerThread.start();
    }
}

2. Concept Analysis

  • synchronized(this): Ensures that only one thread can execute the block of code inside the shared buffer at any given time, preventing race conditions.
  • while(...) { wait(); }: A while loop is crucial here, not an if statement. When a thread wakes up from wait(), it must re-check the condition (spurious wakeups or another thread grabbing the lock first).
  • notify(): Wakes up a single thread that is waiting on the object's monitor (lock). In this two-thread scenario, notify() works perfectly. If there were multiple producers/consumers, notifyAll() would be necessary to prevent deadlocks.
Q5Analyze Java Exception Handling architecture. Write a robust program handling multiple catch blocks, nested try-catch, and custom exceptions.

Q5. Analyze Java Exception Handling architecture. Write a robust program handling multiple catch blocks, nested try-catch, and custom exceptions.

Introduction
Java's Exception Handling architecture is designed to manage runtime errors gracefully, ensuring the normal flow of the application is maintained. The root of the exception hierarchy is the Throwable class, which splits into Error (serious JVM problems, usually unrecoverable like OutOfMemoryError) and Exception (recoverable conditions).

1. Key Architectural Components

  • Checked Exceptions: Verified at compile-time (e.g., IOException). The compiler forces you to handle them (try/catch or throws).
  • Unchecked Exceptions: Subclasses of RuntimeException (e.g., NullPointerException). Not checked at compile-time; usually indicate programming logic errors.
  • Keywords: try (encloses risky code), catch (handles specific exceptions), finally (guaranteed execution for cleanup), throw (explicitly throws an exception), throws (declares exceptions a method might throw).

2. Comprehensive Implementation Example

The following code demonstrates a robust architecture combining Custom Exceptions, Nested Try-Catch, Multiple Catch blocks, and the Finally block.

JAVA
import java.util.Scanner;

// 1. Creating a Custom Checked Exception
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class RobustExceptionHandling {
    
    // Method declaring it might throw a custom checked exception
    public static void validateVoterEligibility(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException("Applicant is under 18. Voting strictly prohibited.");
        }
        System.out.println("Age verified. Applicant is eligible to vote.");
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Outer Try-Catch
        try {
            System.out.print("Enter total registered voters (for calculation): ");
            String inputVoters = scanner.nextLine();
            
            // Multiple Catch scenario - parsing integer
            int totalVoters = Integer.parseInt(inputVoters);
            
            System.out.print("Enter polling booths available: ");
            int booths = scanner.nextInt();
            
            // Nested Try-Catch for specific logic isolation
            try {
                int votersPerBooth = totalVoters / booths;
                System.out.println("Voters per booth: " + votersPerBooth);
            } catch (ArithmeticException ae) {
                System.err.println("Inner Catch: Cannot divide by zero booths! " + ae.getMessage());
            }

            System.out.print("Enter voter age for registration: ");
            int age = scanner.nextInt();
            
            // Calling a method that throws a checked exception
            validateVoterEligibility(age);
            
        } 
        // Handling Multiple Exceptions (Ordering matters: Most specific to least specific)
        catch (NumberFormatException nfe) {
            System.err.println("Outer Catch: Invalid number format. Please enter valid integers.");
        } 
        catch (InvalidAgeException iae) {
            System.err.println("Outer Catch: Validation Error -> " + iae.getMessage());
        } 
        catch (Exception e) {
            // Generic fallback for any unforeseen exceptions
            System.err.println("Outer Catch: An unexpected error occurred -> " + e.toString());
        } 
        finally {
            // Cleanup block always executes
            System.out.println("Finally Block: Releasing scanner resources and closing connection.");
            scanner.close();
        }
        
        System.out.println("Program execution completed smoothly.");
    }
}

3. Best Practices Highlighted

  • Custom Exceptions: Provide specific domain meaning (like InvalidAgeException) rather than generic exceptions.
  • Catch Ordering: NumberFormatException and InvalidAgeException are caught before the generic Exception. If Exception e was first, compilation would fail (unreachable code).
  • Resource Management: The finally block ensures the Scanner is closed regardless of whether the program crashes or succeeds.
  • Nested Blocks: The division by zero logic is isolated. If an ArithmeticException occurs, the inner catch handles it, and the program continues to the next line (asking for voter age), demonstrating true recovery.
Q6Compare and contrast Java Collections: ArrayList, LinkedList, HashSet, TreeSet, HashMap, and ConcurrentHashMap regarding performance time complexities.

Answer to be generated...

Q7Design and write a complete Java application demonstrating File I/O, Serialization, and Deserialization of complex user objects.

Answer to be generated...

Q8Elaborate on Java 8 Features: Functional Interfaces, Lambda Expressions, Stream API, and Optional Class with comprehensive code examples.

Answer to be generated...

Q9Detail the Singleton Design Pattern. Implement thread-safe Bill Pugh Singleton and Double-Checked Locking Singleton implementations.

Answer to be generated...

Q10Implement the Factory Design Pattern and Abstract Factory Pattern for a Cross-Platform GUI toolkit (Windows vs Mac UI components).

Answer to be generated...

Q11Analyze the SOLID Principles in detail with code refactoring examples (converting non-SOLID code into clean architecture).

Answer to be generated...

Q12Construct a full UML Class Diagram and Sequence Diagram for an Automated Teller Machine (ATM) or Library Management System.

Answer to be generated...

Q13Solve the Diamond Problem of Multiple Inheritance in C++ using Virtual Base Classes. Compare this with Java's Interface mechanism.

Answer to be generated...

Q14Analyze Memory Management in Java: JVM Memory Structure (Heap, Stack, Metaspace), Garbage Collection Algorithms (G1, CMS), and Memory Leaks.

Answer to be generated...

Q15Write a Java multithreaded application implementing a custom Thread Pool executor from scratch.

Answer to be generated...

Q16Design a Student Information Management System using Java Swing/JavaFX or Console I/O, JDBC database integration, and DAO Pattern.

Answer to be generated...

Q17Compare Deep Copying vs Shallow Copying in Object Cloning. Implement Cloneable interface and write custom clone() methods.

Answer to be generated...

Q18Explain the Observer Design Pattern. Implement a stock market notification system using Subject-Observer architecture in Java.

Answer to be generated...

Q19Design and implement an Order Processing Pipeline using Java Streams, Lambdas, and Functional Composition.

Answer to be generated...

Q20Discuss the Decorator Design Pattern. Implement a Coffee Shop customization program (Base Coffee + Condiments) using Decorator classes.

Answer to be generated...

Q21Compare C++ and Java OOP paradigms in terms of Pointers, Memory Management, Multiple Inheritance, Operator Overloading, and Virtual Functions.

Answer to be generated...

Q22Develop a Java program that reads a large CSV file, parses data into objects, filters data using Streams, and outputs summary analytics.

Answer to be generated...

Q23Analyze the Strategy Design Pattern. Implement a Payment Gateway system supporting Credit Card, PayPal, and UPI payment strategies.

Answer to be generated...

Q24Design a File Compression/Encryption utility interface in Java leveraging Decorator/Adapter streams.

Answer to be generated...

Q25Discuss the Adapter Design Pattern with a practical example of integrating a legacy third-party logging library.

Answer to be generated...

Q26Detailed study of Java Generics Type Erasure mechanism. Explain why primitive types cannot be used directly as generic parameters.

Answer to be generated...

Q27Develop a Concurrent File Searcher in Java that recursively searches directories for matching text using multiple threads.

Answer to be generated...

Q28Analyze the MVC (Model-View-Controller) pattern. Construct a Java application separating Data Model, GUI View, and Controller Logic.

Answer to be generated...

Q29Write a program demonstrating custom Annotation creation and processing using Java Reflection API.

Answer to be generated...

Q30Formulate an architectural design document and UML diagrams for a Real-Time Food Delivery Application (like Zomato/Swiggy).

Answer to be generated...