ProMind
SearchFor TeachersFor Parents
ProMind
Privacy PolicyTerms of ServiceRefund Policy

© 2025 DataGrid Softwares LLP. All rights reserved.

    Enums and Annotations

    Flashcards for topic Enums and Annotations

    Intermediate43 cardsGeneral

    Preview Cards

    Card 1

    Front

    Implement a type-safe fromString method for an enum with custom string representations that properly handles the case when an invalid string is provided.

    Back

    public enum Operation { PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/"); private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } // Map to efficiently convert strings back to enum constants private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect( toMap(Object::toString, e -> e)); // Returns Operation for string, handles invalid inputs with Optional public static Optional<Operation> fromString(String symbol) { return Optional.ofNullable(stringToEnum.get(symbol)); } } // Client code must handle the Optional: Operation.fromString("+").ifPresent(op -> System.out.println("Found operation: " + op));

    Using Optional<Operation> forces clients to consider the possibility that the string might not represent a valid operation.

    Card 2

    Front

    What happens when you override toString() in an enum type, and what additional methods should you consider implementing to provide bidirectional conversion between strings and enum constants?

    Back

    When you override toString() in an enum, you override the default string representation (the constant name) with a custom format.

    Consequences:

    1. The built-in valueOf(String) method still expects the exact constant name, not your custom string
    2. You lose the automatic string-to-enum conversion for your custom string format

    Solution: Implement a fromString method:

    public enum Operation { PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/"); private final String symbol; Operation(String symbol) { this.symbol = symbol; } // Custom string representation @Override public String toString() { return symbol; } // Internal lookup map for reverse conversion private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect( toMap(Object::toString, e -> e)); // Custom string-to-enum conversion method public static Optional<Operation> fromString(String symbol) { return Optional.ofNullable(stringToEnum.get(symbol)); } }

    Key implementation details:

    1. Use a static map to store the reverse mapping
    2. Initialize the map after all enum constants are created (not in constructors)
    3. Return Optional<EnumType> to handle invalid input strings
    4. For performance with many lookups, use an efficient map implementation
    5. Ensure each constant has a unique string representation to avoid mapping conflicts

    This pattern maintains bidirectional conversion even with custom string formats.

    Card 3

    Front

    What are the two approaches for using an "extensible enum" in client code, and what are the differences between them?

    Back

    Approach 1: Using bounded type tokens with Class objects

    // Client code public static void main(String[] args) { double x = 4.0, y = 2.0; test(ExtendedOperation.class, x, y); } // Bounded type parameter ensures Class represents both enum and Operation private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) { for (Operation op : opEnumType.getEnumConstants()) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); }

    Approach 2: Using bounded wildcard types with Collection

    // Client code public static void main(String[] args) { double x = 4.0, y = 2.0; test(Arrays.asList(ExtendedOperation.values()), x, y); } // Using wildcard type for the collection private static void test(Collection<? extends Operation> operations, double x, double y) { for (Operation op : operations) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); }

    Differences:

    • Approach 1: Requires all operations to be from same enum type
    • Approach 2: More flexible; allows mixing operations from different enum types
    • Approach 1: Uses reflection and enforces enum type
    • Approach 2: Simpler code but loses enum type constraints
    • Approach 1: Could use EnumSet if limited to one enum type
    • Approach 2: Cannot use EnumSet across different enum implementations
    Card 4

    Front

    What is a cascaded collector sequence and how is it used to initialize a nested EnumMap?

    Back

    A cascaded collector sequence chains multiple collectors to transform stream elements into a complex data structure. For initializing a nested EnumMap:

    // Creating Map<Phase, Map<Phase, Transition>> private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values()) // Stream of Transition values .collect( // First collector: Group by "from" phase groupingBy(t -> t.from, // Factory for outer map () -> new EnumMap<>(Phase.class), // Second collector: Create inner map toMap( t -> t.to, // Key mapper (destination phase) t -> t, // Value mapper (transition itself) (x, y) -> y, // Merge function (unused but required) () -> new EnumMap<>(Phase.class) // Factory for inner map ) ) );

    Breakdown:

    1. First collector (groupingBy):

      • Groups transitions by source phase
      • Uses EnumMap<Phase, ...> as the outer map
    2. Second collector (toMap):

      • Maps destination phases to transitions
      • Uses EnumMap<Phase, Transition> as inner map
      • Merge function needed for collector API but unused here

    The result is a type-safe nested map structure with the performance benefits of EnumMap, representing phase transitions as from → to → transition.

    Card 5

    Front

    What is the difference between EnumSet and EnumMap, and when should you use each?

    Back

    EnumSet:

    • Implements the Set interface for enum elements
    • High-performance bit vector representation
    • Use when you need a set of enum constants with fast operations
    • Good for representing flags, states, or options
    • Example:
      EnumSet<DayOfWeek> weekend = EnumSet.of(SATURDAY, SUNDAY);

    EnumMap:

    • Implements the Map interface with enum keys
    • Internally uses an array indexed by ordinal values
    • More efficient than HashMap when keys are enums
    • Use when you need to associate data with enum constants
    • Example:
      EnumMap<DayOfWeek, Integer> hoursOpen = new EnumMap<>(DayOfWeek.class); hoursOpen.put(MONDAY, 9);

    When to use each:

    • Use EnumSet when you need a collection of enum constants without associated values
    • Use EnumMap when you need to map from enum constants to values
    • Use nested EnumMap when mapping between pairs of enum constants
    • Both offer better performance, type safety, and space efficiency than general-purpose collections

    Neither should be used across different enum types - use one EnumSet/EnumMap per enum type.

    Card 6

    Front

    What happens when you use isAnnotationPresent() with a repeatable annotation type instead of its containing annotation type?

    Back

    Using isAnnotationPresent() with a repeatable annotation type will return false for elements that have the repeatable annotation applied multiple times, leading to silently ignored annotations. This happens because:

    • A repeated annotation generates a synthetic annotation of the containing annotation type
    • The repeated annotations themselves are not directly present on the element
    • You must check for both the annotation type AND its containing type:
    // Correct way to detect both repeated and non-repeated annotations if (m.isAnnotationPresent(ExceptionTest.class) || m.isAnnotationPresent(ExceptionTestContainer.class)) { // Process the annotations }
    Card 7

    Front

    What happens when a method with parameters is annotated with @Test in the example framework, and how could this be prevented?

    Back

    When a parameterized method is annotated with @Test:

    1. Compilation succeeds - The compiler doesn't validate annotation semantics
    2. Runtime failure - The test runner fails with an exception when trying to invoke the method:
      Invalid @Test: public void SampleClass.parameterizedMethod(String s)
      

    Prevention approaches:

    1. Documentation comments (weak)

      /** * Use only on parameterless static methods. */
    2. Annotation processor (strong)

      // Create a compile-time validator @SupportedAnnotationTypes("Test") public class TestAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) { if (element.getKind() != ElementKind.METHOD) { processingEnv.getMessager().printError("@Test only allowed on methods", element); return true; } ExecutableElement method = (ExecutableElement) element; if (!method.getParameters().isEmpty()) { processingEnv.getMessager().printError("@Test methods must have no parameters", method); } if (!method.getModifiers().contains(Modifier.STATIC)) { processingEnv.getMessager().printError("@Test methods must be static", method); } } return true; } }
    Card 8

    Front

    Explain how bounded type tokens work with annotation parameters, using the example of exception type checking.

    Back

    Bounded type tokens with annotations:

    // Annotation with bounded type token parameter public @interface ExceptionTest { Class<? extends Throwable> value(); // Bounded type token } // Usage @ExceptionTest(ArithmeticException.class) public static void testMethod() { // Method implementation } // Processing Class<? extends Throwable> expectedExcType = method.getAnnotation(ExceptionTest.class).value(); // Type checking with isInstance if (expectedExcType.isInstance(thrownException)) { // Exception matches expected type }

    Key concepts:

    • Class<? extends Throwable> restricts the type parameter to Throwable subtypes
    • This provides compile-time type safety (can't use non-exception types)
    • No unsafe casts needed when working with the exception
    • isInstance() allows checking if an object is an instance of the class or a subclass
    • Enables type-safe generic operations without runtime casts

    The bounded type token ensures that only exception types can be specified as the annotation value while maintaining type safety throughout the code.

    Card 9

    Front

    When the @Override annotation is applied to a method that doesn't actually override a superclass/interface method, what specifically happens?

    Back

    When @Override is applied to a method that doesn't actually override anything, the compiler generates an error message. For example:

    @Override public boolean equals(Bigram b) { ... }

    Will produce a compile-time error similar to:

    error: method does not override or implement a method from a supertype
    @Override public boolean equals(Bigram b) { ... }
             ^
    

    This compiler behavior is extremely valuable because it:

    • Catches incorrect method signatures immediately
    • Reveals unintentional method overloading vs. overriding
    • Prevents subtle runtime bugs where both methods exist but the wrong one gets called
    • Forces you to examine your inheritance structure when misunderstandings occur
    Card 10

    Front

    Contrast the compile-time benefits of marker interfaces with the runtime properties of marker annotations. Provide an example where using a marker interface would prevent a bug that a marker annotation would not catch until runtime.

    Back

    Compile-time Benefits of Marker Interfaces vs. Runtime Properties of Marker Annotations:

    Marker Interfaces:

    • Define a type in the type system
    • Enable compile-time checking through method signatures
    • Allow instanceof checks for type safety
    • Prevent inappropriate objects from being passed to methods

    Marker Annotations:

    • Checked at runtime through reflection
    • Require explicit code to verify annotations
    • Cannot be used for method parameter type constraints
    • Errors only detected during execution

    Example Where Marker Interface Prevents a Bug:

    // With marker interface public interface Processable { /* marker interface */ } public class Document implements Processable { /* ... */ } public class Image implements Processable { /* ... */ } public class Audio { /* doesn't implement Processable */ } // Method requires Processable objects public void process(Processable item) { /* ... */ } // COMPILE ERROR: Audio doesn't implement Processable process(new Audio()); // Caught at compile time! // With marker annotation instead @interface Processable { } @Processable class Document { /* ... */ } @Processable class Image { /* ... */ } class Audio { /* not annotated */ } // Method must check annotation at runtime public void process(Object item) { if (!item.getClass().isAnnotationPresent(Processable.class)) { throw new IllegalArgumentException("Not processable"); // Runtime error! } // ... } // No compile error, fails at runtime process(new Audio()); // Runtime exception!

    Showing 10 of 43 cards. Add this deck to your collection to see all cards.