What are some best practices for using enums in Java? #


  • How does Enum help?
    • Fixed Set of Constants – Define all allowed values in one place
    • Type Safe – Only valid enum values can be used
    • Readable Code – Improves clarity over magic strings or numbers
    • Prevents Errors – No typos or invalid assignments
    • Switch Friendly – Works great in switch statements
  • JDK Examples
    • java.time.DayOfWeek (enum representing the 7 days of the week)
    • java.util.concurrent.TimeUnit (Represents time durations - NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS)
    • java.lang.Thread.State
      public enum State {
          NEW,
          RUNNABLE,
          BLOCKED,
          WAITING,
          TIMED_WAITING,
          TERMINATED;
      }
  • Simple Example:
    enum Status {
        PENDING, APPROVED, REJECTED;
    }
    
    Status status = Status.APPROVED;
  • Using switch with Enums
    enum Season {
        WINTER, SPRING, SUMMER, FALL
    }
    
    class Weather {
        public int getMaxTemperature(
                            Season season) {
            return switch (season) {
                case WINTER -> 5;
                case SPRING, FALL -> 15;
                case SUMMER -> 30;
            };
        }
    }
  • Enum with Fields, Constructor, and Methods
    enum Direction {
        NORTH("Up"), 
        SOUTH("Down"), 
        EAST("Right"), 
        WEST("Left");
    
        private final String movement;
    
        Direction(String movement) {
            this.movement = movement;
        }
    
        public String getMovement() {
            return movement;
        }
    }
    
    public class EnumExample {
        public static void main(String[] args) {
            System.out.println(
                Direction.NORTH.getMovement());
        }
    }
  • Enum Best Practices
    • Uppercase Names – Use all caps for enum constants (MONDAY, HIGH, STARTED)
    • Add Fields & Methods – Enums can have constructors, fields, and behavior
    • Avoid ordinal() for Logic – Enum order can change, leading to bugs
    • Use switch or switch expressions – Clean and readable for branching logic
    • Use Enums Instead of Strings – Prevents typos and invalid values
    • Don’t Subclass Enums – Enums are implicitly final
    • Override toString() if Needed – For user-friendly display values
    • Use EnumSet / EnumMap – Optimized for enum keys

In what scenarios should you use an EnumSet? #


  • What?EnumSet is a specialized Set for enum types
  • Why? – Much faster than HashSet when working with enums
  • How? – Internally uses a bit vector for storage
  • Null Allowed? – ❌ No null values allowed
  • All Constants? – Use EnumSet.allOf(EnumType.class)
  • Empty Set? – Use EnumSet.noneOf(EnumType.class)
  • Range of Values? – Use EnumSet.range(A, C)
  • Example: Using EnumSet
    import java.util.EnumSet;
    
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, 
        THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
    
    public class EnumSetExample {
        public static void main(String[] args) {
            
            // Use Case 1: Weekend Days
            EnumSet<Day> weekend = 
                EnumSet.of(Day.SATURDAY, Day.SUNDAY);
            System.out.println("Weekend: " + weekend);
    
            // Use Case 2: Working Days
            EnumSet<Day> weekdays = 
                EnumSet.range(Day.MONDAY, Day.FRIDAY);
            System.out.println("Weekdays: " + weekdays);
    
            // Use Case 3: All Days
            EnumSet<Day> allDays = 
                EnumSet.allOf(Day.class);
            System.out.println("All Days: " + allDays);
    
            // Use Case 4: Empty Set (will add later)
            EnumSet<Day> holidays = 
                EnumSet.noneOf(Day.class);
            holidays.add(Day.FRIDAY); // dynamically add
            System.out.println("Holidays: " + holidays);
    
            // Use Case 5: Removing from a set
            weekdays.remove(Day.WEDNESDAY);
            System.out.println(
                "Working Days (No Wednesday): " + weekdays);
        }
    }
    
  • Best Practices for EnumSet
    • Use EnumSet for Enums – Better than HashSet in speed and memory
    • Faster Operationsadd(), remove(), contains() are constant-time
    • No Nulls Allowed – Inserting null throws NullPointerException
    • Cleaner Code – Methods like of(), allOf(), range() make code more readable

What is the purpose of using assertions in Java? #


  • What? – Assertions check conditions that must be true during development
  • How? – Use assert condition; or assert condition : "message";
  • Fails? – Throws AssertionError if the condition is false
  • When? – Only works if assertions are enabled (-ea flag)
  • Not for Production – Meant for testing, not for handling runtime errors
  • Example: Using assert
    public void processFile(String fileNameTxt) {
        
        assert fileNameTxt.endsWith(".txt") 
                : "Invalid file type: " + fileNameTxt;
        
        //Other code to process the file!
    }
  • Running with assertions enabled:
    java -ea AssertionExample
    
  • Asserts - Best Practices
    • Validate Assumptions – Use assertions to catch logic errors during development
    • Pre/Post Conditions – Check method input/output expectations in private/internal code
    • Never for User Input Validation – Don’t validate user input with assertions
    • Never for Business Logic – Business rules must throw proper exceptions
    • Don't Use for Security – Never rely on assertions for security-sensitive checks
    • Add Messages – Use assert condition : "Helpful message" for clarity

When should you use variable arguments (varargs) in a method? #


  • What? – Varargs (...) let a method take zero or more arguments
  • Syntax? – Use type... name as the last parameter in the method
  • Internally? – Treated as an array inside the method
  • Why? – Makes method calls flexible without overloading
  • JDK Examples?
    • String.format(String format, Object... args)
    • Collections.addAll(Collection<? super T> c, T... elements)
    • List.of(E... elements)
  • Example: Using Varargs
    public class VarargsExample {        
        public static int sum(int... numbers) {
            return Arrays.stream(numbers).sum();
        }
    
        public static void main(String[] args) {
            System.out.println(sum(1, 2, 3));
            System.out.println(sum(5, 10, 15, 20));
            System.out.println(sum());
        }
    }
  • Best Practices for Varargs
    • Use When Necessary – Avoid overusing varargs; prefer specific parameters if possible
    • Only One Vararg – A method can’t have more than one varargs parameter
    • Always Last – Varargs must be the last parameter in the method signature
    • Avoid Ambiguity – Overloaded methods with varargs can confuse the compiler
    • Handle Nulls Safely – Varargs can be null; check before using
    • Combine with Streams – Use functional style (Arrays.stream(...)) for clean code

What are the best practices for working with date and time APIs in Java? #


Evolution of Date and Time APIs

API Introduced In Issues Recommended?
java.util.Date Java 1.0 Mutable, time zone issues, bad API design ❌ No
java.util.Calendar Java 1.1 Complex, still mutable, verbose ❌ No
java.time (LocalDate, LocalTime, ZonedDateTime) Java 8 Immutable, easy to use, thread-safe ✅ Yes

Example: java.util.Date (Not Recommended)

import java.util.Date;

public class DateExample {
    public static void main(String[] args) {
        Date date = new Date();
        
        // Output: Mon Mar 04 12:30:45 IST 2024
        System.out.println("Current Date: " + date);  
    }
}

Example: Calendar (Better, but still not ideal)

import java.util.Calendar;

public class CalendarExample {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2024, Calendar.MARCH, 4);
        System.out.println("Date: " + calendar.getTime());  
    }
}

Best Practice: Use java.time API (Java 8+)

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class JavaTimeExample {
    public static void main(String[] args) {

        // 1️⃣ LocalDate – Immutable, thread-safe (unlike Date)
        LocalDate today = LocalDate.now();
        System.out.println("Today's Date: " + today);

        // 2️⃣ LocalDateTime + Formatting (no SimpleDateFormat)
        LocalDateTime dateTime = LocalDateTime.now();
        DateTimeFormatter formatter = 
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println("Formatted: " + 
            dateTime.format(formatter));

        // 3️⃣ Specific Date – Easier than Calendar
        // No month-0 confusion (Jan = 1, not 0)
        LocalDate independenceDay = 
            LocalDate.of(1947, 8, 15);
        System.out.println("Independence Day: " + 
            independenceDay);

        // 4️⃣ Add Days/Months – Fluent API, clear syntax
        LocalDate nextMonth = today.plusMonths(1);
        System.out.println("Next Month: " + nextMonth);

        LocalDate tenDaysLater = today.plusDays(10);
        System.out.println("10 Days Later: " + 
            tenDaysLater);

        // 5️⃣ Subtract Time – No confusing Calendar math
        LocalDate lastYear = today.minusYears(1);
        System.out.println("Last Year: " + lastYear);

        // 6️⃣ Change Fields
        LocalDate changedYear = today.withYear(2020);
        System.out.println("Changed Year: " + 
            changedYear);

        LocalDate firstDay = today.withDayOfMonth(1);
        System.out.println("First Day: " + firstDay);

        // 7️⃣ Compare Dates – Easier than Date.before/after
        boolean isBefore = independenceDay.isBefore(today);
        System.out.println("Is Before Today? " + isBefore);

        boolean isAfter = today.isAfter(independenceDay);
        System.out.println("Is After Independence? " + 
            isAfter);

        // 8️⃣ Days Between – Use ChronoUnit directly
        long days = ChronoUnit.DAYS.between(
                        independenceDay, today);
        System.out.println("Days Since 1947: " + days);
    }
}

Advantages of java.time API:

  • Immutable and thread-safe.
  • Clear and concise API.
  • Better formatting support (DateTimeFormatter).

Best Practices for Date and Time Handling

  • Prefer java.time over Date and Calendar.
  • Use DateTimeFormatter for proper formatting instead of SimpleDateFormat.

What is the Use of Reflection? #


  • What? – Reflection lets you inspect and interact with code at runtime
    • Used For? – Dynamic loading, calling methods, accessing fields, working with annotations
    • Common Uses
      • Dependency injection
      • Test frameworks
      • Annotation processing
  • JDK API ClassesClass, Method, Field, Constructor (from java.lang.reflect)
  • Example: Getting Class Information Using Reflection
    import java.lang.reflect.Method;
    
    public class ReflectionExample {
        public static void main(String[] args) {
            Class<String> clazz = String.class;
    
            // 1️⃣ Print declared methods of String class
            for (Method method : 
                    clazz.getDeclaredMethods()) {
                System.out.println(
                    "Method: " + method.getName());
            }
        }
    }
  • Example: Creating Objects Using Reflection
    import java.lang.reflect.Constructor;
    
    class Example {
        public void display() {
            System.out.println("Reflection Example");
        }
    }
    
    public class ReflectionConstructorExample {
        public static void main(String[] args) 
                                    throws Exception {
            
            Class<Example> clazz = Example.class;
    
            // Create instance dynamically
            Constructor<Example> constructor 
                    = clazz.getDeclaredConstructor();
            
            Example obj = constructor.newInstance();
            
            obj.display();  // Output: Reflection Example
        }
    }
  • Best Practices with Reflection:
    • Use When Needed – Prefer reflection only for special use cases (frameworks, testing)
    • Avoid in Hot Paths – Reflection is slower than direct access — not for performance-critical code
    • Handle Exceptions Properly – Catch and log ClassNotFoundException, NoSuchMethodException, etc.
    • Respect Security – Accessing private fields/methods may break encapsulation — use with caution
    • Document Usage – Clearly explain why reflection is used in code comments
    • Avoid Reflection for Business Logic – Keep it in utilities or framework-level code only
    • Test Thoroughly – Reflective code is harder to debug — write strong tests to verify behavior

Why are annotations used in Java? #


  • What? – Annotations are special markers that add metadata to code
  • JDK Examples:
    • @Override – Tells the compiler you're overriding a super-class method or implementing an interface method.
      • Catches typos or mismatches early.
    • @Deprecated – Marks a method or class as outdated.
      • Warns developers to avoid using it.
    • @FunctionalInterface – Ensures the interface has only one abstract method.
      • Required for lambda expressions.
      • Fails at compile-time if multiple abstract methods exist in an interface.
  • Marker Annotations: Annotations without parameters
  • Framework Usage? – Widely used in Java, Spring, JUnit, Jakarta EE
  • Examples of Using Annotations
    class Parent {
        void show() {
            System.out.println("Parent class method");
        }
    }
    
    class Child extends Parent {
        @Override
        void show() {
            System.out.println("Child class method");
        }
    
        @Deprecated
        void oldMethod() {
            System.out.println("This method is deprecated");
        }
    
        @SuppressWarnings("unchecked")
        void warningFree() {
            // No warning due to SuppressWarnings
            List rawList = new ArrayList(); 
        }
    }
    
    @FunctionalInterface
    interface Greeter {
        void greet(String name);
    
        // ❌ Uncommenting the below method 
        // would cause a compile error
        
        // void sayBye(); 
    }

How do you define and use a custom annotation in Java? #


  • What? – Custom annotations are user-defined markers that add metadata to code
  • @Retention – Controls how long the annotation is retained
    • SOURCE – Discarded by compiler, used only in code (e.g., @Generated for code gen tools)
    • CLASS – Kept in bytecode, ignored at runtime (e.g., @Deprecated)
    • RUNTIME – Available at runtime via reflection (e.g., @Override, custom annotations)
  • @Target – Controls where the annotation can be applied
    • METHOD – Used on methods
    • FIELD – Used on fields or variables
    • TYPE – Used on classes, interfaces, enums
    • PARAMETER – Used on method parameters
    • CONSTRUCTOR, etc. – More specialized use cases
  • Creating a Custom Annotation:
    import java.lang.annotation.*;
    
    // Define the custom annotation
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface TestAnnotation {
        String value();  // Annotation element
    }
  • Applying the Custom Annotation
    class Example {
        @TestAnnotation("This is a test method")
        public void myMethod() {
            System.out.println("Method executed");
        }
    }
  • Using Reflection to Process the Annotation
    import java.lang.reflect.Method;
    
    public class AnnotationProcessor {
        public static void main(String[] args) 
                                throws Exception {
            
            Example example = new Example();
            
            // Get the method
            Method method 
                    = Example.class.getMethod("myMethod");
    
            // Check if annotation is present
            if (method.isAnnotationPresent(
                        TestAnnotation.class)) {
    
                // Retrieve the annotation
                TestAnnotation annotation 
                    = method.getAnnotation(TestAnnotation.class);
    
                System.out.println(
                    "Annotation Value: " + annotation.value());
    
                // Execute the method dynamically
                method.invoke(example);
            }
        }
    }
  • Creating a Simple Unit Test Framework with Annotations
    • The @Test annotation is applied to test methods.
    • TestRunner automatically finds and runs methods marked with @Test.
    • The test description is displayed before executing each test.
      import java.lang.annotation.*;
      import java.lang.reflect.Method;
      
      // Define a test annotation
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      @interface Test {
          String description() default "No description";
      }
      
      // Example test class
      class TestSuite {
          @Test(description = "Test for addition")
          public void testAddition() {
              System.out.println("Addition Test Passed!");
          }
      
          @Test(description = "Test for subtraction")
          public void testSubtraction() {
              System.out.println("Subtraction Test Passed!");
          }
      }
      
      // Annotation Processor
      //Runs all methods with @Test annotation
      public class TestRunner {
          public static void main(String[] args) 
                                      throws Exception {
              
              TestSuite testSuite = new TestSuite();
      
              for (Method method : 
                  TestSuite.class.getDeclaredMethods()) {
      
                  if (method.isAnnotationPresent(Test.class)) {
                      
                      Test test 
                          = method.getAnnotation(Test.class);
                      
                      System.out.println(
                                  "Running Test: " + 
                                      test.description());
      
                      // Execute the test method
                      method.invoke(testSuite);
                  }
      
              }
          }
      }
    • Example Output
      Running Test: Test for addition
      Addition Test Passed!
      Running Test: Test for subtraction
      Subtraction Test Passed!
      

What are unnamed variables and patterns, and why are they useful? #


Unnamed variables (_) and unnamed patterns (catch (NumberFormatException _)) help reduce clutter when a variable or exception is declared but not used.

Example 1: Using Unnamed Variable in Try-Catch

try {
    // Invalid integer
    int n = Integer.parseInt("abc"); 
} catch (NumberFormatException _) {  // No name needed
//} catch (NumberFormatException ex) { // 'ex' is unused
    System.err.println("Invalid number format!");
}

Example 2: Unnamed Pattern in Switch Expressions

static void checkObject(Object obj) {
    switch (obj) {
        case String _ 
            -> System.out.println("It's a string.");
        case Integer _ 
            -> System.out.println("It's an integer.");
        default 
            -> System.out.println("Unknown type.");
    }
}

Example 3: Unnamed Variable in For-Each Loops

String[] names = {"Alice", "Bob", "Charlie"};

//Since we don’t need to use the variable, `_` replaces it.
for (String _ : names) {
    System.out.println("Processing...");
}

Example 4: Unnamed Variable in Record Patterns

record Person(String name, int age) {}

static void printPerson(Object obj) {
    //Here, `name` is ignored, as we only need `age`.
    if (obj instanceof Person(_, int age)) {
        System.out.println("Person's age: " + age);
    }
}

Example 5: Ignoring Unused Return Values

static int calculate(int a, int b) {
    return a + b;
}

public static void main(String[] args) {
    _ = calculate(10, 20); // We ignore the return value
    System.out.println("Calculation done.");
}

Advantages

  • Cleaner Code – Removes unnecessary variable names when they are not used.
  • Improved Readability – Makes the intent clearer by avoiding unused variables.
  • Less Clutter – No warnings for unused variables, reducing distractions in IDEs.

How has pattern matching evolved across recent Java versions? #


Before Java 14 Example

  • Requires explicit casting ((String) obj).
  • Redundant code for type checking.
    public void process(Object obj) {
        if (obj instanceof String) {
            // Explicit casting needed
            String s = (String) obj;  
            System.out.println("Message: " + s);
        }
    }

Java 14: Simplified instanceof Check

  • Eliminates explicit casting.
  • Declares variable inline.
  • Cleaner and safer.
    public void process(Object obj) {
        if (obj instanceof String s) {
            // No explicit cast needed
            System.out.println("Message: " + s);  
        }
    }

Java 21: Record Patterns

  • Java 21 allows automatic deconstruction of records.
  • Automatically extracts fields (sender, receiver, amount).
    record Transaction(
        String sender, String receiver
        , double amount) {}
    
    public void processTransaction(Object obj) {
        if (obj instanceof Transaction(
                String sender, String receiver,
                                 double amount)) {
            System.out.println("Processing transaction: " 
                + sender + " -> " 
                + receiver + " : ₹" + amount);
        }
    }

Java 21: Nested Record Patterns

  • Nested pattern matching extracts values directly.
  • Eliminates manual field access (order.customer().name()).
  • More concise and readable.
    package com.in28minutes;
    
    record Customer(String name, String email) {}
    
    record Product(String name, double price) {}
    
    record Order(Customer customer, Product product) {}
    
    
    public class CustomerSupport {
        public static void processOrder(Object obj) {
            if (obj instanceof Order(
                Customer(String name, String email), 
                Product(String prodName, double price))) {
    
                System.out.println("Customer " + name + 
                            " ordered " + prodName + 
                            " for ₹" + price);
    
            }
        }
    
        public static void main(String[] args) {
            processOrder(new Order(
                            new Customer("Ranga","Email"), 
                            new Product("Cricket Bat",100)));
        }
    }

How have switch expressions improved in modern Java? #


Java 14: Arrow Syntax (->) in switch

  • No need for break statements.
  • More concise and readable.
  • Uses -> instead of case : syntax.
    switch (day) {
        case "Monday" -> System.out.println("Start of the week");
        case "Friday" -> System.out.println("Weekend is near");
        case "Sunday" -> System.out.println("Weekend");
        default -> System.out.println("Regular day");
    }

Java 14: Switch Expression with Return Values

  • switch as an expression (returns a value).
    public class CustomerSupportSwitchExpression {
        public static void main(String[] args) {
            Object message = "Need help with my order";
    
            String response = switch (message) {
                
                case String text -> 
                    "Processing text message: " 
                                        + text;
                
                case Integer ticketNumber -> 
                    "Processing support ticket: #" 
                                        + ticketNumber;
                
                case Double rating -> 
                    "Processing feedback rating: " 
                                        + rating + " stars";
                
                case byte[] attachment -> 
                    "Processing attachment of size: " 
                                        + attachment.length;
                
                default -> "Unknown message type.";
            
            };
    
            System.out.println(response);
        }
    }

Java 17: Switch Expression with Enums

  • Eliminates repetitive if-else logic.
    enum Priority {
        LOW, MEDIUM, HIGH, CRITICAL
    }
    
    public class CustomerSupportJava17 {
        public static void main(String[] args) {
            
            // Example: High-priority ticket
            Priority priority = Priority.HIGH; 
    
            String response = switch (priority) {
                case LOW 
                    -> "Response within 48 hours.";
                case MEDIUM 
                    -> "Response within 24 hours.";
                case HIGH 
                    -> "Immediate response required!";
                case CRITICAL 
                    -> "Escalating to the highest level!";
            };
    
            System.out.println(response);
        }
    }

Java 21: Switch Expression with Record Patterns

  • Pattern Matching in switch allows direct record deconstruction.
  • More concise and readable code.
    sealed interface CustomerMessage 
        permits TextMessage, Ticket, Feedback, Attachment {}
    
    record TextMessage(String text) implements CustomerMessage {}
    record Ticket(int ticketNumber) implements CustomerMessage {}
    record Feedback(double rating) implements CustomerMessage {}
    record Attachment(byte[] file) implements CustomerMessage {}
    
    public class CustomerSupportJava21 {
        public static void main(String[] args) {
            CustomerMessage message = new Ticket(1050);
    
            String response = switch (message) {
                case TextMessage(String text) 
                        -> "Processing text message: " 
                                            + text;
                case Ticket(int ticketNumber) 
                        -> "Processing support ticket: #" 
                                            + ticketNumber;
                case Feedback(double rating) 
                        -> "Processing feedback rating: " 
                                            + rating + " stars";
                case Attachment(byte[] file) 
                    -> "Processing attachment of size: " 
                                            + file.length;
            };
    
            System.out.println(response);
        }
    }

Java 21: Nested Record Patterns in Switch Expressions

  • Switch expressions work seamlessly with nested records.
  • Automatic deconstruction of records inside switch.
    package com.in28minutes;
    
    sealed interface CustomerMessage 
        permits TextMessage, Ticket, 
                Feedback, Attachment {}
    
    record TextMessage(String text) 
            implements CustomerMessage {}
    
    record Ticket(int ticketNumber) 
            implements CustomerMessage {}
    
    record Feedback(double rating) 
            implements CustomerMessage {}
    
    record Attachment(byte[] file) 
            implements CustomerMessage {}
    
    record SupportRequest(String user, 
                            CustomerMessage message) {}
    
    public class CustomerSupport {
        public static void main(String[] args) {
            SupportRequest request 
                = new SupportRequest("Alice", 
                                new Feedback(4.8));
    
            String response = switch (request) {
                
                case SupportRequest(String user, 
                        Ticket(int ticketNumber)) -> 
                    "User " + user + 
                    " has a support ticket: #" + ticketNumber;
                
                case SupportRequest(String user, 
                        Feedback(double rating)) -> 
                    "User " + user + 
                    " left a rating: " + rating + " stars";
                
                case SupportRequest(String user, 
                        TextMessage(String text)) -> 
                    "User " + user + 
                    " sent a message: " + text;
                
                case SupportRequest(String user, 
                        Attachment(byte[] file)) -> 
                    "User " + user + 
                    " uploaded a file of size: " + file.length;
            };
    
            System.out.println(response);
        }
    }

📌 Java 22: when Guards in Switch Expressions

  • What? Add extra conditions inside switch cases
  • Why? Filter patterns without nesting if statements
  • Benefit? Cleaner and more powerful control flow

Example: Using when for Additional Filtering

record Feedback(double rating) implements CustomerMessage {}

public class SupportWithWhen {
    public static void main(String[] args) {
        CustomerMessage message = new Feedback(2.5);

        String response = switch (message) {
            case Feedback(double rating) when rating < 3.0 ->
                "We're sorry to hear that. Support will contact you.";
            case Feedback(double rating) ->
                "Thanks for your positive feedback!";
            default ->
                "Unhandled message type.";
        };

        System.out.println(response);
    }
}