Practice Problems

Sharpen your Java skills with hands-on coding exercises

Basic Level

1. Palindrome Check

Easy

Write a program to check if a given string is a palindrome (reads same forwards and backwards).

View Solution
public class Palindrome {
    public static boolean isPalindrome(String str) {
        str = str.toLowerCase().replaceAll("[^a-z0-9]", "");
        int left = 0, right = str.length() - 1;
        while (left < right) {
            if (str.charAt(left++) != str.charAt(right--)) {
                return false;
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        System.out.println(isPalindrome("Madam"));  // true
        System.out.println(isPalindrome("Hello"));  // false
    }
}

2. Factorial Calculation

Easy

Write a program to calculate factorial of a number using both iteration and recursion.

View Solution
public class Factorial {
    // Iterative approach
    public static long factorialIterative(int n) {
        long result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }
    
    // Recursive approach
    public static long factorialRecursive(int n) {
        if (n <= 1) return 1;
        return n * factorialRecursive(n - 1);
    }
    
    public static void main(String[] args) {
        System.out.println(factorialIterative(5));  // 120
        System.out.println(factorialRecursive(5));  // 120
    }
}

3. Prime Number Check

Easy

Write a method to check if a number is prime.

View Solution
public class PrimeCheck {
    public static boolean isPrime(int n) {
        if (n <= 1) return false;
        if (n <= 3) return true;
        if (n % 2 == 0 || n % 3 == 0) return false;
        
        for (int i = 5; i * i <= n; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        System.out.println(isPrime(17));  // true
        System.out.println(isPrime(20));  // false
    }
}

4. Reverse an Array

Easy

Write a program to reverse an array in-place.

View Solution
import java.util.Arrays;

public class ReverseArray {
    public static void reverse(int[] arr) {
        int left = 0, right = arr.length - 1;
        while (left < right) {
            int temp = arr[left];
            arr[left++] = arr[right];
            arr[right--] = temp;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        reverse(arr);
        System.out.println(Arrays.toString(arr));  // [5, 4, 3, 2, 1]
    }
}

5. Fibonacci Series

Easy

Generate first N Fibonacci numbers.

View Solution
public class Fibonacci {
    public static void printFibonacci(int n) {
        long a = 0, b = 1;
        for (int i = 0; i < n; i++) {
            System.out.print(a + " ");
            long next = a + b;
            a = b;
            b = next;
        }
    }
    
    public static void main(String[] args) {
        printFibonacci(10);  // 0 1 1 2 3 5 8 13 21 34
    }
}

Intermediate Level

5. Two Sum Problem

Easy

Given an array of integers and a target sum, find two numbers that add up to the target.

View Solution
import java.util.*;

public class TwoSum {
    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] {map.get(complement), i};
            }
            map.put(nums[i], i);
        }
        return new int[] {};
    }
    
    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int[] result = twoSum(nums, 9);
        System.out.println("Indices: " + Arrays.toString(result));  // [0, 1]
    }
}

6. Find Missing Number

Easy

Given an array containing n-1 numbers taken from 1 to n, find the missing number.

View Solution
public class MissingNumber {
    public static int findMissing(int[] arr, int n) {
        // Sum of first n natural numbers: n*(n+1)/2
        int expectedSum = n * (n + 1) / 2;
        int actualSum = 0;
        
        for (int num : arr) {
            actualSum += num;
        }
        
        return expectedSum - actualSum;
    }
    
    public static void main(String[] args) {
        int[] arr = {1, 2, 4, 5, 6};
        System.out.println("Missing: " + findMissing(arr, 6));  // 3
    }
}

7. Second Largest Element

Easy

Find the second largest element in an array without sorting.

View Solution
public class SecondLargest {
    public static int findSecondLargest(int[] arr) {
        if (arr.length < 2) return -1;
        
        int largest = Integer.MIN_VALUE;
        int secondLargest = Integer.MIN_VALUE;
        
        for (int num : arr) {
            if (num > largest) {
                secondLargest = largest;
                largest = num;
            } else if (num > secondLargest && num != largest) {
                secondLargest = num;
            }
        }
        
        return secondLargest;
    }
    
    public static void main(String[] args) {
        int[] arr = {12, 35, 1, 10, 34, 1};
        System.out.println("Second largest: " + findSecondLargest(arr));  // 34
    }
}

8. Count Vowels and Consonants

Easy

Write a program to count vowels and consonants in a given string.

View Solution
public class VowelConsonantCounter {
    public static void countVowelsConsonants(String str) {
        str = str.toLowerCase();
        int vowels = 0, consonants = 0;
        
        for (char ch : str.toCharArray()) {
            if (Character.isLetter(ch)) {
                if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
                    vowels++;
                } else {
                    consonants++;
                }
            }
        }
        
        System.out.println("Vowels: " + vowels + ", Consonants: " + consonants);
    }
    
    public static void main(String[] args) {
        countVowelsConsonants("Hello World");  // Vowels: 3, Consonants: 7
    }
}

9. Remove Duplicates from Array

Easy

Remove duplicate elements from a sorted array in-place.

View Solution
import java.util.Arrays;

public class RemoveDuplicates {
    public static int removeDuplicates(int[] arr) {
        if (arr.length == 0) return 0;
        
        int j = 0;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] != arr[j]) {
                j++;
                arr[j] = arr[i];
            }
        }
        
        return j + 1;
    }
    
    public static void main(String[] args) {
        int[] arr = {1, 1, 2, 2, 3, 4, 4};
        int newLength = removeDuplicates(arr);
        System.out.println("New length: " + newLength);
        System.out.println(Arrays.toString(Arrays.copyOf(arr, newLength)));
    }
}

10. Anagram Check

Easy

Check if two strings are anagrams of each other.

View Solution
import java.util.Arrays;

public class AnagramCheck {
    public static boolean isAnagram(String s1, String s2) {
        if (s1.length() != s2.length()) return false;
        
        char[] arr1 = s1.toLowerCase().toCharArray();
        char[] arr2 = s2.toLowerCase().toCharArray();
        
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        
        return Arrays.equals(arr1, arr2);
    }
    
    public static void main(String[] args) {
        System.out.println(isAnagram("listen", "silent"));  // true
        System.out.println(isAnagram("hello", "world"));    // false
    }
}

Intermediate Level

11. Find Duplicates in Array

Medium

Find all duplicate elements in an array using different approaches.

View Solution
import java.util.*;

public class FindDuplicates {
    public static Set<Integer> findDuplicates(int[] arr) {
        Set<Integer> seen = new HashSet<>();
        Set<Integer> duplicates = new HashSet<>();
        
        for (int num : arr) {
            if (!seen.add(num)) {
                duplicates.add(num);
            }
        }
        return duplicates;
    }
    
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 2, 4, 3, 5};
        System.out.println(findDuplicates(arr));  // [2, 3]
    }
}

12. Custom Exception

Medium

Create a custom exception for invalid age and use it in a Person class.

View Solution
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

class Person {
    private String name;
    private int age;
    
    public void setAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("Age must be between 0 and 150");
        }
        this.age = age;
    }
}

public class CustomExceptionDemo {
    public static void main(String[] args) {
        Person p = new Person();
        try {
            p.setAge(-5);
        } catch (InvalidAgeException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

13. Producer-Consumer with Threads

Medium

Implement producer-consumer pattern using wait/notify.

View Solution
import java.util.LinkedList;
import java.util.Queue;

class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity = 5;
    
    public synchronized void produce(int item) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();
        }
        queue.add(item);
        System.out.println("Produced: " + item);
        notifyAll();
    }
    
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        int item = queue.poll();
        System.out.println("Consumed: " + item);
        notifyAll();
        return item;
    }
}

public class ProducerConsumer {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try { buffer.produce(i); } 
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });
        
        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try { buffer.consume(); } 
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });
        
        producer.start();
        consumer.start();
    }
}

9. Stream API - Word Frequency

Medium

Count word frequency in a list of strings using Stream API.

View Solution
import java.util.*;
import java.util.stream.*;

public class WordFrequency {
    public static void main(String[] args) {
        List<String> words = Arrays.asList(
            "apple", "banana", "apple", "orange", 
            "banana", "apple"
        );
        
        Map<String, Long> frequency = words.stream()
            .collect(Collectors.groupingBy(
                word -> word,
                Collectors.counting()
            ));
        
        System.out.println(frequency);
        // {orange=1, banana=2, apple=3}
        
        // Find most frequent word
        String mostFrequent = frequency.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElse("");
        
        System.out.println("Most frequent: " + mostFrequent);
    }
}

10. Comparable and Comparator

Medium

Create a Student class with multiple sorting options.

View Solution
import java.util.*;

class Student implements Comparable<Student> {
    String name;
    int age;
    double gpa;
    
    Student(String name, int age, double gpa) {
        this.name = name;
        this.age = age;
        this.gpa = gpa;
    }
    
    @Override
    public int compareTo(Student other) {
        return this.name.compareTo(other.name);
    }
    
    @Override
    public String toString() {
        return name + "(" + age + ", " + gpa + ")";
    }
}

public class SortingDemo {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Charlie", 20, 3.5));
        students.add(new Student("Alice", 22, 3.8));
        students.add(new Student("Bob", 21, 3.2));
        
        // Natural ordering (by name)
        Collections.sort(students);
        System.out.println("By name: " + students);
        
        // By age using Comparator
        students.sort(Comparator.comparingInt(s -> s.age));
        System.out.println("By age: " + students);
        
        // By GPA descending
        students.sort(Comparator.comparingDouble((Student s) -> s.gpa).reversed());
        System.out.println("By GPA (desc): " + students);
    }
}

Advanced Level

11. LRU Cache Implementation

Hard

Implement a Least Recently Used (LRU) cache using LinkedHashMap.

View Solution
import java.util.*;

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);  // accessOrder=true
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}

public class LRUCacheDemo {
    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        
        cache.put(1, "One");
        cache.put(2, "Two");
        cache.put(3, "Three");
        System.out.println(cache);  // {1=One, 2=Two, 3=Three}
        
        cache.get(1);  // Access 1
        cache.put(4, "Four");  // Evicts 2 (least recently used)
        System.out.println(cache);  // {3=Three, 1=One, 4=Four}
    }
}

12. Generic Stack Implementation

Hard

Implement a generic stack with push, pop, peek, and isEmpty operations.

View Solution
import java.util.ArrayList;
import java.util.EmptyStackException;

class GenericStack<T> {
    private ArrayList<T> elements = new ArrayList<>();
    
    public void push(T item) {
        elements.add(item);
    }
    
    public T pop() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        return elements.remove(elements.size() - 1);
    }
    
    public T peek() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        return elements.get(elements.size() - 1);
    }
    
    public boolean isEmpty() {
        return elements.isEmpty();
    }
    
    public int size() {
        return elements.size();
    }
}

public class GenericStackDemo {
    public static void main(String[] args) {
        GenericStack<String> stack = new GenericStack<>();
        stack.push("A");
        stack.push("B");
        stack.push("C");
        
        System.out.println("Peek: " + stack.peek());  // C
        System.out.println("Pop: " + stack.pop());    // C
        System.out.println("Size: " + stack.size());  // 2
    }
}

13. File Copy with Streams

Hard

Copy a file using buffered streams with proper exception handling.

View Solution
import java.io.*;

public class FileCopy {
    public static void copyFile(String source, String dest) {
        try (
            BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream(source));
            BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(dest))
        ) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("File copied successfully");
            
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO error: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        copyFile("source.txt", "destination.txt");
    }
}

14. Thread-Safe Singleton

Hard

Implement a thread-safe singleton pattern using double-checked locking.

View Solution
public class Singleton {
    private static volatile Singleton instance;
    private String data;
    
    private Singleton(String data) {
        this.data = data;
    }
    
    public static Singleton getInstance(String data) {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(data);
                }
            }
        }
        return instance;
    }
    
    public String getData() {
        return data;
    }
}

// Alternative: Bill Pugh Singleton (preferred)
class BillPughSingleton {
    private BillPughSingleton() {}
    
    private static class Holder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

15. Stream Parallel Processing

Hard

Use parallel streams to process large data efficiently.

View Solution
import java.util.*;
import java.util.stream.*;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
            .boxed()
            .collect(Collectors.toList());
        
        // Sequential sum
        long start = System.currentTimeMillis();
        long seqSum = numbers.stream()
            .mapToLong(Integer::longValue)
            .sum();
        long seqTime = System.currentTimeMillis() - start;
        
        // Parallel sum
        start = System.currentTimeMillis();
        long parSum = numbers.parallelStream()
            .mapToLong(Integer::longValue)
            .sum();
        long parTime = System.currentTimeMillis() - start;
        
        System.out.println("Sequential: " + seqSum + " in " + seqTime + "ms");
        System.out.println("Parallel: " + parSum + " in " + parTime + "ms");
        
        // Parallel filtering and mapping
        List<Integer> evenSquares = numbers.parallelStream()
            .filter(n -> n % 2 == 0)
            .map(n -> n * n)
            .limit(100)
            .collect(Collectors.toList());
    }
}

More Practice Problems

16. Second Largest Element in Array

Easy

Find the second largest element in an array without sorting.

View Solution
public class SecondLargest {
    public static int findSecondLargest(int[] arr) {
        if (arr.length < 2) throw new IllegalArgumentException("Need at least 2 elements");
        
        int first = Integer.MIN_VALUE, second = Integer.MIN_VALUE;
        for (int num : arr) {
            if (num > first) {
                second = first;
                first = num;
            } else if (num > second && num != first) {
                second = num;
            }
        }
        return second;
    }
    
    public static void main(String[] args) {
        int[] arr = {12, 35, 1, 10, 34, 1};
        System.out.println("Second largest: " + findSecondLargest(arr));  // 34
    }
}

17. Count Vowels and Consonants

Easy

Count the number of vowels and consonants in a given string.

View Solution
public class VowelConsonant {
    public static void count(String str) {
        str = str.toLowerCase();
        int vowels = 0, consonants = 0;
        
        for (char c : str.toCharArray()) {
            if (c >= 'a' && c <= 'z') {
                if ("aeiou".indexOf(c) != -1) {
                    vowels++;
                } else {
                    consonants++;
                }
            }
        }
        System.out.println("Vowels: " + vowels + ", Consonants: " + consonants);
    }
    
    public static void main(String[] args) {
        count("Hello World");  // Vowels: 3, Consonants: 7
    }
}

18. Armstrong Number Check

Easy

Check if a number is an Armstrong number (sum of cubes of digits equals the number).

View Solution
public class Armstrong {
    public static boolean isArmstrong(int num) {
        int original = num;
        int digits = String.valueOf(num).length();
        int sum = 0;
        
        while (num > 0) {
            int digit = num % 10;
            sum += Math.pow(digit, digits);
            num /= 10;
        }
        return sum == original;
    }
    
    public static void main(String[] args) {
        System.out.println(isArmstrong(153));  // true (1^3 + 5^3 + 3^3 = 153)
        System.out.println(isArmstrong(370));  // true
        System.out.println(isArmstrong(123));  // false
    }
}

19. GCD and LCM

Easy

Find GCD (Greatest Common Divisor) and LCM (Least Common Multiple) of two numbers.

View Solution
public class GcdLcm {
    public static int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    public static int lcm(int a, int b) {
        return (a * b) / gcd(a, b);
    }
    
    public static void main(String[] args) {
        System.out.println("GCD of 12, 18: " + gcd(12, 18));  // 6
        System.out.println("LCM of 12, 18: " + lcm(12, 18));  // 36
    }
}

20. Matrix Addition

Easy

Add two matrices and display the result.

View Solution
public class MatrixAddition {
    public static int[][] add(int[][] a, int[][] b) {
        int rows = a.length, cols = a[0].length;
        int[][] result = new int[rows][cols];
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                result[i][j] = a[i][j] + b[i][j];
            }
        }
        return result;
    }
    
    public static void main(String[] args) {
        int[][] a = {{1, 2}, {3, 4}};
        int[][] b = {{5, 6}, {7, 8}};
        int[][] sum = add(a, b);
        
        for (int[] row : sum) {
            System.out.println(Arrays.toString(row));  // [6, 8], [10, 12]
        }
    }
}

21. Binary Search

Medium

Implement binary search algorithm for a sorted array.

View Solution
public class BinarySearch {
    public static int search(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;  // Not found
    }
    
    public static void main(String[] args) {
        int[] arr = {2, 3, 4, 10, 40};
        System.out.println("Index of 10: " + search(arr, 10));  // 3
    }
}

22. Merge Two Sorted Arrays

Medium

Merge two sorted arrays into a single sorted array.

View Solution
public class MergeArrays {
    public static int[] merge(int[] a, int[] b) {
        int[] result = new int[a.length + b.length];
        int i = 0, j = 0, k = 0;
        
        while (i < a.length && j < b.length) {
            if (a[i] <= b[j]) {
                result[k++] = a[i++];
            } else {
                result[k++] = b[j++];
            }
        }
        
        while (i < a.length) result[k++] = a[i++];
        while (j < b.length) result[k++] = b[j++];
        
        return result;
    }
    
    public static void main(String[] args) {
        int[] a = {1, 3, 5};
        int[] b = {2, 4, 6};
        System.out.println(Arrays.toString(merge(a, b)));  // [1, 2, 3, 4, 5, 6]
    }
}

23. String Anagram Check

Medium

Check if two strings are anagrams of each other.

View Solution
import java.util.*;

public class AnagramCheck {
    public static boolean isAnagram(String s1, String s2) {
        if (s1.length() != s2.length()) return false;
        
        char[] arr1 = s1.toLowerCase().toCharArray();
        char[] arr2 = s2.toLowerCase().toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        
        return Arrays.equals(arr1, arr2);
    }
    
    // Alternative using HashMap
    public static boolean isAnagramMap(String s1, String s2) {
        if (s1.length() != s2.length()) return false;
        
        Map<Character, Integer> map = new HashMap<>();
        for (char c : s1.toLowerCase().toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (char c : s2.toLowerCase().toCharArray()) {
            if (!map.containsKey(c) || map.get(c) == 0) return false;
            map.put(c, map.get(c) - 1);
        }
        return true;
    }
    
    public static void main(String[] args) {
        System.out.println(isAnagram("Listen", "Silent"));  // true
        System.out.println(isAnagram("Hello", "World"));   // false
    }
}

24. Remove Duplicates from ArrayList

Medium

Remove duplicate elements from an ArrayList while preserving order.

View Solution
import java.util.*;

public class RemoveDuplicates {
    // Using LinkedHashSet
    public static <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
    }
    
    // Using Stream API
    public static <T> List<T> removeDuplicatesStream(List<T> list) {
        return list.stream()
            .distinct()
            .collect(Collectors.toList());
    }
    
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 2, 1, 4));
        System.out.println(removeDuplicates(list));  // [1, 2, 3, 4]
    }
}

25. Bank Account Class

Medium

Create a BankAccount class with deposit, withdraw, and balance checking functionality.

View Solution
public class BankAccount {
    private String accountNumber;
    private String holderName;
    private double balance;
    
    public BankAccount(String accountNumber, String holderName, double initialBalance) {
        this.accountNumber = accountNumber;
        this.holderName = holderName;
        this.balance = initialBalance;
    }
    
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("Deposited: " + amount);
        } else {
            System.out.println("Invalid deposit amount");
        }
    }
    
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("Withdrawn: " + amount);
        } else {
            System.out.println("Insufficient balance or invalid amount");
        }
    }
    
    public double getBalance() {
        return balance;
    }
    
    public static void main(String[] args) {
        BankAccount acc = new BankAccount("123456", "John", 1000);
        acc.deposit(500);
        acc.withdraw(200);
        System.out.println("Balance: " + acc.getBalance());  // 1300
    }
}

26. Quick Sort Implementation

Hard

Implement the Quick Sort algorithm.

View Solution
public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }
    
    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }
    
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));  // [1, 5, 7, 8, 9, 10]
    }
}

27. Linked List Implementation

Hard

Implement a singly linked list with insert, delete, and display operations.

View Solution
public class LinkedList {
    private Node head;
    
    class Node {
        int data;
        Node next;
        Node(int data) { this.data = data; }
    }
    
    public void insertAtEnd(int data) {
        Node newNode = new Node(data);
        if (head == null) {
            head = newNode;
            return;
        }
        Node current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = newNode;
    }
    
    public void insertAtBeginning(int data) {
        Node newNode = new Node(data);
        newNode.next = head;
        head = newNode;
    }
    
    public void delete(int key) {
        if (head == null) return;
        
        if (head.data == key) {
            head = head.next;
            return;
        }
        
        Node current = head;
        while (current.next != null && current.next.data != key) {
            current = current.next;
        }
        if (current.next != null) {
            current.next = current.next.next;
        }
    }
    
    public void display() {
        Node current = head;
        while (current != null) {
            System.out.print(current.data + " -> ");
            current = current.next;
        }
        System.out.println("null");
    }
    
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        list.insertAtEnd(1);
        list.insertAtEnd(2);
        list.insertAtEnd(3);
        list.insertAtBeginning(0);
        list.display();  // 0 -> 1 -> 2 -> 3 -> null
        list.delete(2);
        list.display();  // 0 -> 1 -> 3 -> null
    }
}

28. Binary Tree Traversals

Hard

Implement inorder, preorder, and postorder traversals of a binary tree.

View Solution
public class BinaryTree {
    class Node {
        int data;
        Node left, right;
        Node(int data) { this.data = data; }
    }
    
    private Node root;
    
    // Inorder: Left -> Root -> Right
    public void inorder(Node node) {
        if (node != null) {
            inorder(node.left);
            System.out.print(node.data + " ");
            inorder(node.right);
        }
    }
    
    // Preorder: Root -> Left -> Right
    public void preorder(Node node) {
        if (node != null) {
            System.out.print(node.data + " ");
            preorder(node.left);
            preorder(node.right);
        }
    }
    
    // Postorder: Left -> Right -> Root
    public void postorder(Node node) {
        if (node != null) {
            postorder(node.left);
            postorder(node.right);
            System.out.print(node.data + " ");
        }
    }
    
    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        tree.root = tree.new Node(1);
        tree.root.left = tree.new Node(2);
        tree.root.right = tree.new Node(3);
        tree.root.left.left = tree.new Node(4);
        tree.root.left.right = tree.new Node(5);
        
        System.out.print("Inorder: ");
        tree.inorder(tree.root);     // 4 2 5 1 3
        System.out.print("\nPreorder: ");
        tree.preorder(tree.root);    // 1 2 4 5 3
        System.out.print("\nPostorder: ");
        tree.postorder(tree.root);   // 4 5 2 3 1
    }
}

29. Student Management System

Hard

Create a Student class with CRUD operations using ArrayList.

View Solution
import java.util.*;

class Student {
    private int id;
    private String name;
    private double marks;
    
    public Student(int id, String name, double marks) {
        this.id = id;
        this.name = name;
        this.marks = marks;
    }
    
    // Getters and Setters
    public int getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getMarks() { return marks; }
    public void setMarks(double marks) { this.marks = marks; }
    
    public String toString() {
        return "Student[id=" + id + ", name=" + name + ", marks=" + marks + "]";
    }
}

public class StudentManagement {
    private List<Student> students = new ArrayList<>();
    
    public void addStudent(Student s) {
        students.add(s);
    }
    
    public Student findById(int id) {
        return students.stream()
            .filter(s -> s.getId() == id)
            .findFirst()
            .orElse(null);
    }
    
    public void updateStudent(int id, String name, double marks) {
        Student s = findById(id);
        if (s != null) {
            s.setName(name);
            s.setMarks(marks);
        }
    }
    
    public void deleteStudent(int id) {
        students.removeIf(s -> s.getId() == id);
    }
    
    public void displayAll() {
        students.forEach(System.out::println);
    }
    
    public static void main(String[] args) {
        StudentManagement sm = new StudentManagement();
        sm.addStudent(new Student(1, "Alice", 85.5));
        sm.addStudent(new Student(2, "Bob", 90.0));
        sm.displayAll();
        sm.updateStudent(1, "Alice Smith", 88.0);
        sm.deleteStudent(2);
        System.out.println("After update and delete:");
        sm.displayAll();
    }
}

30. File Word Counter

Medium

Read a file and count occurrences of each word.

View Solution
import java.io.*;
import java.util.*;

public class WordCounter {
    public static Map<String, Integer> countWords(String filename) throws IOException {
        Map<String, Integer> wordCount = new HashMap<>();
        
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] words = line.toLowerCase().split("\\W+");
                for (String word : words) {
                    if (!word.isEmpty()) {
                        wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
                    }
                }
            }
        }
        return wordCount;
    }
    
    public static void main(String[] args) {
        try {
            Map<String, Integer> counts = countWords("sample.txt");
            counts.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .limit(10)
                .forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Exam Practice Questions

These problems are based on actual exam patterns. Practice these for your semester exams.

E1. Method Overriding - Shape and Rectangle

Medium

Write a superclass Shape with method displayArea() and subclass Rectangle implementing method overriding. Compare method overloading vs overriding.

View Solution
// Superclass Shape
class Shape {
    public void displayArea() {
        System.out.println("Area calculation depends on shape type");
    }

    // Method Overloading - same name, different parameters
    public void displayArea(String shapeName) {
        System.out.println("Calculating area for: " + shapeName);
    }
}

// Subclass Rectangle - Method Overriding
class Rectangle extends Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public void displayArea() {
        double area = length * width;
        System.out.println("Rectangle Area: " + area);
    }
}

// Subclass Circle - Another example of overriding
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void displayArea() {
        double area = Math.PI * radius * radius;
        System.out.println("Circle Area: " + String.format("%.2f", area));
    }
}

public class ShapeDemo {
    public static void main(String[] args) {
        Shape shape = new Shape();
        Rectangle rect = new Rectangle(5.0, 3.0);
        Circle circle = new Circle(4.0);

        // Method Overriding demonstration
        System.out.println("--- Method Overriding ---");
        shape.displayArea();      // Parent method
        rect.displayArea();       // Overridden in Rectangle
        circle.displayArea();     // Overridden in Circle

        // Runtime Polymorphism
        System.out.println("\n--- Runtime Polymorphism ---");
        Shape s1 = new Rectangle(10, 5);
        Shape s2 = new Circle(7);
        s1.displayArea();  // Calls Rectangle's displayArea()
        s2.displayArea();  // Calls Circle's displayArea()

        // Method Overloading demonstration
        System.out.println("\n--- Method Overloading ---");
        shape.displayArea();                    // No parameter
        shape.displayArea("Triangle");         // With parameter
    }
}

/*
OUTPUT:
--- Method Overriding ---
Area calculation depends on shape type
Rectangle Area: 15.0
Circle Area: 50.27

--- Runtime Polymorphism ---
Rectangle Area: 50.0
Circle Area: 153.94

--- Method Overloading ---
Area calculation depends on shape type
Calculating area for: Triangle

KEY DIFFERENCES:
| Overloading              | Overriding                    |
|--------------------------|-------------------------------|
| Same class               | Parent-child classes          |
| Different parameters     | Same signature                |
| Compile-time polymorphism| Runtime polymorphism          |
| Can change return type   | Must be same/covariant return |
*/

E2. Constructor Overloading

Medium

Create a class with at least three constructors demonstrating constructor overloading. Show how constructors are invoked.

View Solution
class Student {
    private int rollNo;
    private String name;
    private double marks;
    private String course;

    // Constructor 1: Default constructor
    public Student() {
        this.rollNo = 0;
        this.name = "Unknown";
        this.marks = 0.0;
        this.course = "Not Assigned";
        System.out.println("Default constructor called");
    }

    // Constructor 2: With rollNo and name
    public Student(int rollNo, String name) {
        this.rollNo = rollNo;
        this.name = name;
        this.marks = 0.0;
        this.course = "Not Assigned";
        System.out.println("Constructor with rollNo and name called");
    }

    // Constructor 3: With rollNo, name, and marks
    public Student(int rollNo, String name, double marks) {
        this.rollNo = rollNo;
        this.name = name;
        this.marks = marks;
        this.course = "Not Assigned";
        System.out.println("Constructor with rollNo, name, marks called");
    }

    // Constructor 4: With all parameters
    public Student(int rollNo, String name, double marks, String course) {
        this.rollNo = rollNo;
        this.name = name;
        this.marks = marks;
        this.course = course;
        System.out.println("Parameterized constructor with all fields called");
    }

    // Constructor 5: Copy constructor
    public Student(Student other) {
        this.rollNo = other.rollNo;
        this.name = other.name;
        this.marks = other.marks;
        this.course = other.course;
        System.out.println("Copy constructor called");
    }

    public void display() {
        System.out.println("Roll: " + rollNo + ", Name: " + name +
                          ", Marks: " + marks + ", Course: " + course);
    }
}

public class ConstructorOverloadingDemo {
    public static void main(String[] args) {
        // Invoking different constructors
        System.out.println("=== Constructor Invocation Examples ===\n");

        // 1. Using default constructor
        Student s1 = new Student();
        s1.display();

        System.out.println();

        // 2. Using constructor with 2 parameters
        Student s2 = new Student(101, "Alice");
        s2.display();

        System.out.println();

        // 3. Using constructor with 3 parameters
        Student s3 = new Student(102, "Bob", 85.5);
        s3.display();

        System.out.println();

        // 4. Using constructor with all parameters
        Student s4 = new Student(103, "Charlie", 92.0, "Computer Science");
        s4.display();

        System.out.println();

        // 5. Using copy constructor
        Student s5 = new Student(s4);
        s5.display();
    }
}

/*
OUTPUT:
=== Constructor Invocation Examples ===

Default constructor called
Roll: 0, Name: Unknown, Marks: 0.0, Course: Not Assigned

Constructor with rollNo and name called
Roll: 101, Name: Alice, Marks: 0.0, Course: Not Assigned

Constructor with rollNo, name, marks called
Roll: 102, Name: Bob, Marks: 85.5, Course: Not Assigned

Parameterized constructor with all fields called
Roll: 103, Name: Charlie, Marks: 92.0, Course: Computer Science

Copy constructor called
Roll: 103, Name: Charlie, Marks: 92.0, Course: Computer Science
*/

E3. Multiple Exception Handling with try-catch-finally

Medium

Write a Java program to handle multiple exceptions using try-catch-finally. Include throw and throws keywords.

View Solution
import java.util.Scanner;

public class MultipleExceptionDemo {

    // Method using 'throws' keyword
    public static int divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            // Using 'throw' keyword to explicitly throw exception
            throw new ArithmeticException("Cannot divide by zero!");
        }
        return a / b;
    }

    public static void main(String[] args) {
        Scanner scanner = null;

        try {
            scanner = new Scanner(System.in);

            // Example 1: ArrayIndexOutOfBoundsException
            int[] numbers = {10, 20, 30};
            System.out.println("Array element at index 1: " + numbers[1]);

            // Example 2: ArithmeticException
            int result = divide(10, 2);
            System.out.println("Division result: " + result);

            // Example 3: NumberFormatException
            String numStr = "123";
            int num = Integer.parseInt(numStr);
            System.out.println("Parsed number: " + num);

            // Example 4: NullPointerException
            String str = "Hello";
            System.out.println("String length: " + str.length());

            // Uncomment any line below to trigger exception:
            // System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException
            // divide(10, 0);                   // ArithmeticException
            // Integer.parseInt("abc");         // NumberFormatException
            // String s = null; s.length();     // NullPointerException

        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Error: Array index out of bounds!");
            System.out.println("Details: " + e.getMessage());

        } catch (ArithmeticException e) {
            System.out.println("Error: Arithmetic exception occurred!");
            System.out.println("Details: " + e.getMessage());

        } catch (NumberFormatException e) {
            System.out.println("Error: Invalid number format!");
            System.out.println("Details: " + e.getMessage());

        } catch (NullPointerException e) {
            System.out.println("Error: Null pointer exception!");
            System.out.println("Details: " + e.getMessage());

        } catch (Exception e) {
            // Generic catch block for any other exceptions
            System.out.println("Error: An unexpected exception occurred!");
            System.out.println("Details: " + e.getMessage());

        } finally {
            // Finally block always executes
            System.out.println("\n--- Finally Block ---");
            System.out.println("Cleanup operations performed.");
            if (scanner != null) {
                scanner.close();
                System.out.println("Scanner closed.");
            }
        }

        System.out.println("\nProgram continues after exception handling...");
    }
}

/*
KEY CONCEPTS:
1. try: Contains code that might throw exceptions
2. catch: Handles specific exception types
3. finally: Always executes (cleanup code)
4. throw: Explicitly throws an exception
5. throws: Declares exceptions a method might throw

OUTPUT (normal execution):
Array element at index 1: 20
Division result: 5
Parsed number: 123
String length: 5

--- Finally Block ---
Cleanup operations performed.
Scanner closed.

Program continues after exception handling...
*/

E4. Multiple Inheritance using Interfaces

Medium

Demonstrate why multiple inheritance is not supported in Java with classes and show how interfaces achieve multiple inheritance.

View Solution
/*
 WHY MULTIPLE INHERITANCE IS NOT SUPPORTED WITH CLASSES:

 If Java allowed:
 class A { void show() { print("A"); } }
 class B { void show() { print("B"); } }
 class C extends A, B { }  // NOT ALLOWED

 Problem: Which show() would C inherit? This is the "Diamond Problem"
*/

// Interface 1
interface Printable {
    void print();

    // Default method (Java 8+)
    default void defaultPrint() {
        System.out.println("Default print from Printable");
    }
}

// Interface 2
interface Showable {
    void show();

    default void defaultShow() {
        System.out.println("Default show from Showable");
    }
}

// Interface 3 - extends another interface
interface Displayable extends Printable {
    void display();
}

// Class implementing multiple interfaces - MULTIPLE INHERITANCE
class Document implements Printable, Showable {
    private String content;

    public Document(String content) {
        this.content = content;
    }

    @Override
    public void print() {
        System.out.println("Printing: " + content);
    }

    @Override
    public void show() {
        System.out.println("Showing: " + content);
    }
}

// Another class implementing Displayable (which extends Printable)
class Report implements Displayable, Showable {
    private String title;

    public Report(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Printing Report: " + title);
    }

    @Override
    public void show() {
        System.out.println("Showing Report: " + title);
    }

    @Override
    public void display() {
        System.out.println("Displaying Report: " + title);
    }
}

public class MultipleInheritanceDemo {
    public static void main(String[] args) {
        System.out.println("=== Multiple Inheritance via Interfaces ===\n");

        // Document implements both Printable and Showable
        Document doc = new Document("Hello World");
        doc.print();
        doc.show();
        doc.defaultPrint();
        doc.defaultShow();

        System.out.println();

        // Report implements Displayable (extends Printable) and Showable
        Report report = new Report("Annual Report 2024");
        report.print();
        report.show();
        report.display();

        System.out.println("\n=== Polymorphism with Interfaces ===\n");

        // Interface reference can hold implementing class object
        Printable p = new Document("Polymorphic Doc");
        p.print();

        Showable s = new Report("Polymorphic Report");
        s.show();
    }
}

/*
OUTPUT:
=== Multiple Inheritance via Interfaces ===

Printing: Hello World
Showing: Hello World
Default print from Printable
Default show from Showable

Printing Report: Annual Report 2024
Showing Report: Annual Report 2024
Displaying Report: Annual Report 2024

=== Polymorphism with Interfaces ===

Polymorphic Doc
Polymorphic Report

KEY POINTS:
1. Java doesn't support multiple inheritance with classes (Diamond Problem)
2. A class can implement multiple interfaces
3. Interfaces provide method signatures (contract)
4. Default methods allow code in interfaces (Java 8+)
5. Interface can extend multiple interfaces
*/

E5. Thread Creation - Thread Class and Runnable Interface

Medium

Create threads using both extending Thread class and implementing Runnable interface. Demonstrate the thread lifecycle.

View Solution
// Method 1: Extending Thread class
class MyThread extends Thread {
    private String threadName;

    public MyThread(String name) {
        this.threadName = name;
    }

    @Override
    public void run() {
        System.out.println(threadName + " started (extends Thread)");
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ": Count " + i);
            try {
                Thread.sleep(500);  // Sleep for 500ms
            } catch (InterruptedException e) {
                System.out.println(threadName + " interrupted");
            }
        }
        System.out.println(threadName + " finished");
    }
}

// Method 2: Implementing Runnable interface
class MyRunnable implements Runnable {
    private String threadName;

    public MyRunnable(String name) {
        this.threadName = name;
    }

    @Override
    public void run() {
        System.out.println(threadName + " started (implements Runnable)");
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ": Count " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println(threadName + " interrupted");
            }
        }
        System.out.println(threadName + " finished");
    }
}

public class ThreadCreationDemo {
    public static void main(String[] args) {
        System.out.println("=== Thread Creation Demo ===\n");
        System.out.println("Main thread started");

        // Method 1: Using Thread class
        MyThread thread1 = new MyThread("Thread-1");

        // Method 2: Using Runnable interface
        MyRunnable runnable = new MyRunnable("Thread-2");
        Thread thread2 = new Thread(runnable);

        // Method 3: Using Lambda (Java 8+) - Anonymous Runnable
        Thread thread3 = new Thread(() -> {
            System.out.println("Thread-3 started (Lambda)");
            for (int i = 1; i <= 3; i++) {
                System.out.println("Thread-3: Count " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Thread-3 finished");
        });

        // Display thread states before starting
        System.out.println("\n--- Thread States ---");
        System.out.println("Thread-1 state: " + thread1.getState());  // NEW

        // Start all threads
        System.out.println("\n--- Starting Threads ---");
        thread1.start();  // Transitions to RUNNABLE
        thread2.start();
        thread3.start();

        System.out.println("Thread-1 state after start: " + thread1.getState());

        // Wait for threads to complete
        try {
            thread1.join();  // Main waits for thread1
            thread2.join();  // Main waits for thread2
            thread3.join();  // Main waits for thread3
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("\nThread-1 state after completion: " + thread1.getState());  // TERMINATED
        System.out.println("Main thread finished");
    }
}

/*
THREAD LIFECYCLE STATES:
1. NEW        - Thread created but not started
2. RUNNABLE   - Thread executing or ready to execute
3. BLOCKED    - Waiting to acquire a lock
4. WAITING    - Waiting indefinitely for another thread
5. TIMED_WAITING - Waiting for specified time (sleep, wait with timeout)
6. TERMINATED - Thread execution completed

DIFFERENCE: Thread vs Runnable
| Thread Class           | Runnable Interface           |
|------------------------|------------------------------|
| Extends Thread         | Implements Runnable          |
| Cannot extend other    | Can extend other classes     |
| Each thread has object | Single object, multiple threads |
| Less flexible          | More flexible (preferred)    |
*/

E6. User-Defined Exception

Medium

Create a user-defined exception and demonstrate proper exception handling with custom exceptions.

View Solution
// User-defined exception class
class InsufficientBalanceException extends Exception {
    private double balance;
    private double amount;

    public InsufficientBalanceException(String message) {
        super(message);
    }

    public InsufficientBalanceException(double balance, double amount) {
        super("Insufficient balance! Available: " + balance +
              ", Requested: " + amount);
        this.balance = balance;
        this.amount = amount;
    }

    public double getBalance() { return balance; }
    public double getAmount() { return amount; }
}

// Another user-defined exception
class InvalidAccountException extends Exception {
    public InvalidAccountException(String message) {
        super(message);
    }
}

// Bank Account class using custom exceptions
class BankAccount {
    private String accountNumber;
    private double balance;

    public BankAccount(String accountNumber, double initialBalance)
            throws InvalidAccountException {
        if (accountNumber == null || accountNumber.length() < 5) {
            throw new InvalidAccountException(
                "Account number must be at least 5 characters");
        }
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    public void withdraw(double amount) throws InsufficientBalanceException {
        if (amount > balance) {
            throw new InsufficientBalanceException(balance, amount);
        }
        balance -= amount;
        System.out.println("Withdrawn: " + amount + ", New Balance: " + balance);
    }

    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited: " + amount + ", New Balance: " + balance);
    }

    public double getBalance() { return balance; }
}

public class UserDefinedExceptionDemo {
    public static void main(String[] args) {
        System.out.println("=== User-Defined Exception Demo ===\n");

        // Test 1: Invalid Account Exception
        System.out.println("Test 1: Creating account with invalid number");
        try {
            BankAccount invalidAcc = new BankAccount("123", 1000);
        } catch (InvalidAccountException e) {
            System.out.println("Exception caught: " + e.getMessage());
        }

        System.out.println();

        // Test 2: Valid account operations
        System.out.println("Test 2: Valid account operations");
        try {
            BankAccount account = new BankAccount("ACC001", 5000);
            System.out.println("Account created. Balance: " + account.getBalance());

            account.deposit(2000);
            account.withdraw(3000);

            // This will throw InsufficientBalanceException
            System.out.println("\nAttempting to withdraw 10000...");
            account.withdraw(10000);

        } catch (InvalidAccountException e) {
            System.out.println("Account Error: " + e.getMessage());
        } catch (InsufficientBalanceException e) {
            System.out.println("Transaction Error: " + e.getMessage());
            System.out.println("Available Balance: " + e.getBalance());
            System.out.println("Requested Amount: " + e.getAmount());
        } finally {
            System.out.println("\nTransaction processing completed.");
        }
    }
}

/*
OUTPUT:
=== User-Defined Exception Demo ===

Test 1: Creating account with invalid number
Exception caught: Account number must be at least 5 characters

Test 2: Valid account operations
Account created. Balance: 5000.0
Deposited: 2000.0, New Balance: 7000.0
Withdrawn: 3000.0, New Balance: 4000.0

Attempting to withdraw 10000...
Transaction Error: Insufficient balance! Available: 4000.0, Requested: 10000.0
Available Balance: 4000.0
Requested Amount: 10000.0

Transaction processing completed.

KEY POINTS:
1. Extend Exception for checked exceptions
2. Extend RuntimeException for unchecked exceptions
3. Call super(message) in constructor
4. Can add custom fields and methods
*/

E7. Functional Interface with Lambda Expression

Medium

Define a functional interface and implement it using lambda expressions. Show built-in functional interfaces.

View Solution
import java.util.*;
import java.util.function.*;

// Custom Functional Interface
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);

    // Can have default methods
    default void printResult(int a, int b) {
        System.out.println("Result: " + calculate(a, b));
    }
}

// Another Functional Interface
@FunctionalInterface
interface Greeting {
    String greet(String name);
}

public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        System.out.println("=== Custom Functional Interface ===\n");

        // Lambda implementations of Calculator
        Calculator add = (a, b) -> a + b;
        Calculator subtract = (a, b) -> a - b;
        Calculator multiply = (a, b) -> a * b;
        Calculator divide = (a, b) -> b != 0 ? a / b : 0;

        System.out.println("10 + 5 = " + add.calculate(10, 5));
        System.out.println("10 - 5 = " + subtract.calculate(10, 5));
        System.out.println("10 * 5 = " + multiply.calculate(10, 5));
        System.out.println("10 / 5 = " + divide.calculate(10, 5));

        // Using default method
        System.out.print("Using default method: ");
        add.printResult(20, 30);

        // Greeting functional interface
        Greeting formalGreeting = name -> "Good morning, " + name + "!";
        Greeting casualGreeting = name -> "Hey " + name + "!";

        System.out.println("\n" + formalGreeting.greet("Mr. Smith"));
        System.out.println(casualGreeting.greet("John"));

        // Built-in Functional Interfaces
        System.out.println("\n=== Built-in Functional Interfaces ===\n");

        // 1. Predicate - takes T, returns boolean
        Predicate<Integer> isEven = n -> n % 2 == 0;
        System.out.println("Is 4 even? " + isEven.test(4));
        System.out.println("Is 7 even? " + isEven.test(7));

        // 2. Function - takes T, returns R
        Function<String, Integer> strLength = s -> s.length();
        System.out.println("Length of 'Hello': " + strLength.apply("Hello"));

        // 3. Consumer - takes T, returns nothing
        Consumer<String> printer = s -> System.out.println("Printing: " + s);
        printer.accept("Consumer demo");

        // 4. Supplier - takes nothing, returns T
        Supplier<Double> randomSupplier = () -> Math.random();
        System.out.println("Random number: " + randomSupplier.get());

        // 5. BiFunction - takes T and U, returns R
        BiFunction<Integer, Integer, Integer> max = (a, b) -> a > b ? a : b;
        System.out.println("Max of 10, 20: " + max.apply(10, 20));

        // Using with Collections
        System.out.println("\n=== Lambda with Collections ===\n");
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // forEach with Consumer
        System.out.print("Names: ");
        names.forEach(name -> System.out.print(name + " "));

        // filter with Predicate
        System.out.println("\nNames starting with 'A' or 'C':");
        names.stream()
             .filter(n -> n.startsWith("A") || n.startsWith("C"))
             .forEach(System.out::println);
    }
}

/*
OUTPUT:
=== Custom Functional Interface ===

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
Using default method: Result: 50

Good morning, Mr. Smith!
Hey John!

=== Built-in Functional Interfaces ===

Is 4 even? true
Is 7 even? false
Length of 'Hello': 5
Printing: Consumer demo
Random number: 0.xyz...
Max of 10, 20: 20

=== Lambda with Collections ===

Names: Alice Bob Charlie David
Names starting with 'A' or 'C':
Alice
Charlie
*/

E8. Abstraction vs Encapsulation

Medium

Demonstrate the difference between abstraction and encapsulation with practical examples.

View Solution
/*
 ABSTRACTION: Hiding implementation details, showing only functionality
 ENCAPSULATION: Binding data and methods together, protecting data
*/

// ABSTRACTION using Abstract Class
abstract class Vehicle {
    // Abstract method - no implementation (ABSTRACTION)
    public abstract void start();
    public abstract void stop();

    // Concrete method
    public void honk() {
        System.out.println("Vehicle is honking!");
    }
}

// ENCAPSULATION - Data hiding with private fields
class Car extends Vehicle {
    // Private fields (ENCAPSULATION)
    private String model;
    private int speed;
    private boolean engineRunning;

    public Car(String model) {
        this.model = model;
        this.speed = 0;
        this.engineRunning = false;
    }

    // Implementing abstract methods (ABSTRACTION)
    @Override
    public void start() {
        engineRunning = true;
        System.out.println(model + " engine started");
    }

    @Override
    public void stop() {
        engineRunning = false;
        speed = 0;
        System.out.println(model + " engine stopped");
    }

    // Getters and Setters (ENCAPSULATION)
    public String getModel() { return model; }

    public int getSpeed() { return speed; }

    // Controlled access with validation (ENCAPSULATION)
    public void setSpeed(int speed) {
        if (!engineRunning) {
            System.out.println("Cannot set speed. Engine is off!");
            return;
        }
        if (speed >= 0 && speed <= 200) {
            this.speed = speed;
            System.out.println("Speed set to " + speed + " km/h");
        } else {
            System.out.println("Invalid speed! Must be 0-200");
        }
    }

    public boolean isEngineRunning() { return engineRunning; }
}

// ABSTRACTION using Interface
interface PaymentProcessor {
    void processPayment(double amount);
    boolean validatePayment();
}

// Implementation hides the complexity (ABSTRACTION)
class CreditCardPayment implements PaymentProcessor {
    // Encapsulated data
    private String cardNumber;
    private String cvv;

    public CreditCardPayment(String cardNumber, String cvv) {
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }

    @Override
    public void processPayment(double amount) {
        if (validatePayment()) {
            // Complex logic hidden from user (ABSTRACTION)
            System.out.println("Processing credit card payment of $" + amount);
            System.out.println("Connecting to bank...");
            System.out.println("Payment successful!");
        }
    }

    @Override
    public boolean validatePayment() {
        // Validation logic hidden (ABSTRACTION)
        return cardNumber != null && cardNumber.length() == 16;
    }

    // Encapsulated - no direct access to card details
    public String getMaskedCardNumber() {
        return "****-****-****-" + cardNumber.substring(12);
    }
}

public class AbstractionEncapsulationDemo {
    public static void main(String[] args) {
        System.out.println("=== Abstraction vs Encapsulation Demo ===\n");

        // Abstraction: User doesn't know HOW car starts
        System.out.println("--- Abstraction Example ---");
        Vehicle myCar = new Car("Tesla Model 3");
        myCar.start();  // Abstract method implemented
        myCar.honk();
        myCar.stop();

        System.out.println("\n--- Encapsulation Example ---");
        // Encapsulation: Cannot directly access/modify private fields
        Car car = new Car("BMW X5");

        // Cannot do: car.speed = 500; (field is private)
        // Must use setter with validation
        car.setSpeed(100);   // Fails - engine not running
        car.start();
        car.setSpeed(100);   // Works
        car.setSpeed(300);   // Fails validation
        System.out.println("Current speed: " + car.getSpeed());

        System.out.println("\n--- Payment Processing Example ---");
        PaymentProcessor payment = new CreditCardPayment("1234567890123456", "123");
        payment.processPayment(99.99);  // User doesn't know internal logic

        CreditCardPayment cc = (CreditCardPayment) payment;
        System.out.println("Card: " + cc.getMaskedCardNumber());  // Protected access
    }
}

/*
KEY DIFFERENCES:

| Abstraction                      | Encapsulation                    |
|----------------------------------|----------------------------------|
| WHAT an object does              | HOW an object does it            |
| Achieved via abstract class/     | Achieved via private fields      |
| interface                        | and public methods               |
| Hides complexity                 | Hides data                       |
| Design level concept             | Implementation level concept     |
| Example: Interface methods       | Example: Getters/Setters         |

Both work together:
- Abstraction defines the contract
- Encapsulation protects the implementation
*/

E9. Static Members Demonstration

Easy

Explain and demonstrate the use of static variables, static methods, and static blocks in Java.

View Solution
class Counter {
    // Static variable - shared across all instances
    private static int count = 0;

    // Instance variable - unique to each object
    private int id;
    private String name;

    // Static block - executes once when class is loaded
    static {
        System.out.println("Static block executed - Class loaded!");
        System.out.println("Initial count: " + count);
    }

    // Constructor
    public Counter(String name) {
        this.name = name;
        count++;  // Increment shared counter
        this.id = count;
        System.out.println("Object created: " + name + " (ID: " + id + ")");
    }

    // Static method - belongs to class, not instances
    public static int getCount() {
        // Cannot access instance variables directly
        // Cannot use 'this' keyword
        return count;
    }

    // Static method
    public static void resetCount() {
        count = 0;
        System.out.println("Count reset to 0");
    }

    // Instance method - can access both static and instance members
    public void display() {
        System.out.println("ID: " + id + ", Name: " + name +
                          ", Total Count: " + count);
    }

    public int getId() { return id; }
}

// Utility class with only static members
class MathUtils {
    // Static constant
    public static final double PI = 3.14159;

    // Private constructor - prevents instantiation
    private MathUtils() {}

    // Static utility methods
    public static int square(int n) {
        return n * n;
    }

    public static double circleArea(double radius) {
        return PI * radius * radius;
    }
}

public class StaticMembersDemo {
    public static void main(String[] args) {
        System.out.println("=== Static Members Demo ===\n");

        // Accessing static method before creating any object
        System.out.println("Count before creating objects: " + Counter.getCount());

        System.out.println();

        // Creating objects - static variable is shared
        Counter c1 = new Counter("First");
        Counter c2 = new Counter("Second");
        Counter c3 = new Counter("Third");

        System.out.println();

        // Static variable reflects total count
        System.out.println("Total objects created: " + Counter.getCount());

        // Each object has unique ID but shares count
        c1.display();
        c2.display();
        c3.display();

        System.out.println("\n--- Utility Class Example ---");
        // Static members accessed via class name
        System.out.println("PI value: " + MathUtils.PI);
        System.out.println("Square of 5: " + MathUtils.square(5));
        System.out.println("Circle area (r=3): " + MathUtils.circleArea(3));

        System.out.println("\n--- Static vs Instance ---");
        // Can access static via instance (not recommended)
        System.out.println("Via instance (not recommended): " + c1.getCount());
        // Recommended way
        System.out.println("Via class name: " + Counter.getCount());
    }
}

/*
OUTPUT:
=== Static Members Demo ===

Static block executed - Class loaded!
Initial count: 0
Count before creating objects: 0

Object created: First (ID: 1)
Object created: Second (ID: 2)
Object created: Third (ID: 3)

Total objects created: 3
ID: 1, Name: First, Total Count: 3
ID: 2, Name: Second, Total Count: 3
ID: 3, Name: Third, Total Count: 3

--- Utility Class Example ---
PI value: 3.14159
Square of 5: 25
Circle area (r=3): 28.27431

--- Static vs Instance ---
Via instance (not recommended): 3
Via class name: 3

KEY POINTS:
1. Static variables: One copy shared by all instances
2. Static methods: Called via class name, cannot access instance members
3. Static blocks: Execute once when class loads
4. Static constants: final static (e.g., Math.PI)
5. Cannot use 'this' or 'super' in static context
*/