ProMind
SearchFor TeachersFor Parents
ProMind
Privacy PolicyTerms of ServiceRefund Policy

© 2025 DataGrid Softwares LLP. All rights reserved.

    Classes and Interfaces

    Flashcards for topic Classes and Interfaces

    Intermediate68 cardsGeneral

    Preview Cards

    Card 1

    Front

    What approach should be used to provide public constants for frequently used values in immutable classes, and why is it superior to creating new instances?

    Back

    Approach: Provide public static final constants for commonly used values and consider implementing static factories with instance caching.

    public final class Complex { private final double re; private final double im; // Public constants for commonly used values public static final Complex ZERO = new Complex(0, 0); public static final Complex ONE = new Complex(1, 0); public static final Complex I = new Complex(0, 1); // Private constructor private Complex(double re, double im) { this.re = re; this.im = im; } // Static factory with potential for caching public static Complex valueOf(double re, double im) { // Check if it's a common value if (re == 0 && im == 0) return ZERO; if (re == 1 && im == 0) return ONE; if (re == 0 && im == 1) return I; // If not a common value, create a new instance return new Complex(re, im); } // Rest of implementation... }

    This approach is superior because:

    1. It reduces memory usage by reusing instances
    2. Decreases garbage collection pressure
    3. Allows instance sharing and identity comparison optimization
    4. Improves performance by avoiding redundant object creation
    5. Provides flexibility to add caching strategies later without changing the API

    All boxed primitives (Integer, Boolean, etc.) and BigInteger use this pattern for frequently requested values.

    Card 2

    Front

    What critical issue can occur when overriding clone() in a subclass?

    Back

    When overriding clone() in a subclass, the overriding method will run before the subclass's clone method has a chance to fix the clone's state. This can damage both the original object and the clone.

    This happens when:

    • The overriding method assumes it's modifying the clone's copy of the object's deep structure
    • But the copy hasn't been made yet by the subclass's clone implementation

    For example:

    public class SuperClass implements Cloneable { private List<String> items = new ArrayList<>(); @Override public SuperClass clone() { try { SuperClass result = (SuperClass) super.clone(); // Deep copy operation result.items = new ArrayList<>(items); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } public class SubClass extends SuperClass { private Map<String, Date> dates = new HashMap<>(); // DANGEROUS: This will run before SuperClass.clone() completes @Override public SubClass clone() { SubClass result = (SubClass) super.clone(); // This might manipulate structures not yet deep-copied result.dates = new HashMap<>(dates); return result; } }
    Card 3

    Front

    What is "simulated multiple inheritance" and how does it work to take advantage of skeletal implementations?

    Back

    Simulated multiple inheritance is a technique that allows a class to benefit from skeletal implementations while avoiding the constraints of extending an abstract class.

    Implementation steps:

    1. Create a class that directly implements the target interface
    2. Create a private inner class that extends the skeletal implementation
    3. Forward interface method invocations to the contained inner class instance

    Example:

    // Interface with skeletal implementation public interface MyInterface { void methodA(); void methodB(); } // Skeletal implementation public abstract class AbstractMyInterface implements MyInterface { public void methodB() { // Default implementation built on methodA() System.out.println("Default methodB implementation"); methodA(); } } // Class using simulated multiple inheritance public class MyClass implements MyInterface { // Private inner class extends the skeletal implementation private class InnerImplementation extends AbstractMyInterface { @Override public void methodA() { // Implementation specific to MyClass System.out.println("MyClass methodA implementation"); } } // Instance of inner class private final InnerImplementation impl = new InnerImplementation(); // Forward method calls to inner implementation @Override public void methodA() { impl.methodA(); } @Override public void methodB() { impl.methodB(); } }

    This technique:

    • Provides benefits of multiple inheritance
    • Avoids inheritance pitfalls
    • Is closely related to the wrapper class idiom
    • Allows use of skeletal implementations even when direct inheritance isn't possible
    Card 4

    Front

    What is the process for creating an effective skeletal implementation of an interface?

    Back

    Process for creating a skeletal implementation:

    1. Study the interface and identify primitive methods:

      • Determine which methods can't be implemented in terms of other methods
      • These will be abstract methods in your skeletal implementation
    2. Identify methods that can be implemented in terms of primitives:

      • Add default methods to the interface where possible
      • Implement remaining methods in the skeletal implementation class
    3. Create the skeletal implementation class:

      • Declare the class to implement the interface
      • Make the class abstract
      • Implement all non-primitive interface methods
      • Keep primitive methods abstract
      • Add any necessary non-public fields and methods
    4. Add comprehensive documentation:

      • Document intended usage patterns
      • Document self-use patterns
      • Provide @implSpec Javadoc tags for inheritance guidance

    Example workflow for creating a skeletal implementation of a Shape interface:

    // 1. Define the interface with defaults where possible public interface Shape { // Primitive methods (can't be implemented in terms of others) double area(); double perimeter(); // Non-primitive method with default implementation default boolean isLargerThan(Shape other) { return this.area() > other.area(); } } // 2. Create the skeletal implementation public abstract class AbstractShape implements Shape { // Primitive methods remain abstract public abstract double area(); public abstract double perimeter(); // Additional implementation methods not in interface protected static boolean closeEnough(double a, double b) { return Math.abs(a - b) < 0.0001; } // Implementation of Object methods @Override public boolean equals(Object obj) { if (!(obj instanceof Shape)) return false; Shape other = (Shape) obj; return closeEnough(area(), other.area()) && closeEnough(perimeter(), other.perimeter()); } @Override public int hashCode() { return Double.hashCode(area()) ^ Double.hashCode(perimeter()); } }
    Card 5

    Front

    What are the key advantages of interfaces over abstract classes for defining types?

    Back

    Advantages of interfaces over abstract classes:

    1. Multiple implementations:

      • A class can implement multiple interfaces
      • Java only allows single inheritance for classes
    2. Retrofitting existing classes:

      • Easy to add an interface to existing classes
      • Just implement required methods and add "implements" clause
      • Can't retrofit an abstract class without restructuring the hierarchy
    3. Defining mixins:

      • Interfaces ideal for defining optional capabilities
      • Example: Comparable, Iterable, Autocloseable
      • Abstract classes can't define mixins due to single inheritance
    4. Non-hierarchical type frameworks:

      • Interfaces allow combinations of capabilities
      • With n attributes, interfaces can represent all 2ⁿ combinations
      • Class hierarchies would require a separate class for each combination
    5. Enable safer functionality enhancements:

      • Wrapper class idiom works better with interfaces
      • Abstract class hierarchies force inheritance for enhancements

    Example showing combination capabilities:

    // Base capability interfaces public interface Swimmer { void swim(); } public interface Flyer { void fly(); } public interface Runner { void run(); } // Combined capabilities - doesn't force a rigid hierarchy public interface FlyingFish extends Swimmer, Flyer { } public interface Duck extends Swimmer, Flyer, Runner { } // Any class can implement these as needed public class Penguin implements Swimmer, Runner { /*...*/ } public class Eagle implements Flyer { /*...*/ } public class Platypus implements Swimmer, Runner { /*...*/ }

    With abstract classes, this flexibility would be impossible without massive code duplication.

    Card 6

    Front

    What is the difference between a skeletal implementation and a simple implementation?

    Back

    Skeletal Implementation vs. Simple Implementation:

    Skeletal Implementation:

    • Abstract class implementing an interface
    • Leaves some methods abstract (typically primitive operations)
    • Designed for extension/inheritance
    • Provides partial implementation to simplify implementing the interface
    • Example: AbstractList, AbstractSet

    Simple Implementation:

    • Non-abstract class implementing an interface
    • Implements all methods (nothing left abstract)
    • Provides simplest possible working implementation
    • Can be used directly or subclassed as needed
    • Example: AbstractMap.SimpleEntry

    Key differences:

    1. Abstractness: Skeletal implementations have abstract methods, simple implementations don't
    2. Completeness: Simple implementations are fully functional alone
    3. Intent: Skeletal implementations expect to be extended, simple implementations can stand alone

    Example:

    // Interface public interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); } // Skeletal implementation (abstract) public abstract class AbstractEntry<K,V> implements Entry<K,V> { // Primitive operations left abstract public abstract K getKey(); public abstract V getValue(); // Default implementation with optional operation public V setValue(V value) { throw new UnsupportedOperationException(); } // Implements equals, hashCode, toString @Override public boolean equals(Object o) { /*...*/ } @Override public int hashCode() { /*...*/ } @Override public String toString() { /*...*/ } } // Simple implementation (concrete) public class SimpleEntry<K,V> implements Entry<K,V> { private final K key; private V value; public SimpleEntry(K key, V value) { this.key = key; this.value = value; } // All methods implemented public K getKey() { return key; } public V getValue() { return value; } public V setValue(V newValue) { V oldValue = this.value; this.value = newValue; return oldValue; } // Same equals, hashCode, toString as AbstractEntry }
    Card 7

    Front

    What technique can be used to safely extend a class that wasn't designed for inheritance but must be extended?

    Back

    Technique for safely extending a non-inheritable class:

    The Helper Method Pattern:

    1. Never override any methods in the superclass
    Card 8

    Front

    What problem can occur when adding default methods to existing interfaces, and why does this happen specifically with the Apache Commons SynchronizedCollection implementation?

    Back

    • Existing implementations may compile without error but fail at runtime
    • Example: Apache Commons SynchronizedCollection class doesn't override the default removeIf method
    • The default removeIf implementation cannot maintain the synchronization promise because:
      • It knows nothing about synchronization
      • It has no access to the locking object field
    • Result: Concurrent modification by another thread causes ConcurrentModificationException
    • Root cause: Default implementations cannot access implementation-specific details (like synchronization mechanisms) of implementing classes

    This illustrates a fundamental limitation of Java's default method feature - they cannot ensure behavioral consistency with class-specific implementation guarantees.

    Card 9

    Front

    What are the best practices for exporting constants in Java, and why is each approach preferred over the constant interface pattern? Provide code examples.

    Back

    Best Practices for Exporting Constants:

    1. Add to Relevant Existing Class/Interface:

      // Constants relevant to Integer operations public final class Integer extends Number { public static final int MIN_VALUE = 0x80000000; public static final int MAX_VALUE = 0x7fffffff; // ... }
      • Why better: Logically associates constants with related functionality
    2. Enumerated Type (for related constants):

      public enum Planet { MERCURY(3.302e23, 2.439e6), VENUS(4.869e24, 6.052e6); private final double mass; // In kilograms private final double radius; // In meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } public double mass() { return mass; } public double radius() { return radius; } }
      • Why better: Type-safe, has behavior, can be evolved
    3. Noninstantiable Utility Class:

      public class PhysicalConstants { private PhysicalConstants() { } // Prevents instantiation public static final double AVOGADROS_NUMBER = 6.022_140_857e23; public static final double BOLTZMANN_CONST = 1.380_648_52e-23; public static final double ELECTRON_MASS = 9.109_383_56e-31; }
      • Why better: No implementation inheritance issues, clearer intent
      • Use with static import: import static com.example.PhysicalConstants.*;

    Each approach avoids the key problems of constant interfaces:

    • No implementation inheritance issues
    • No namespace pollution in subclasses
    • No public API commitment to constants
    • Clear separation of interfaces (types) from constants (implementation details)
    Card 10

    Front

    What are the appropriate uses and limitations of default methods in Java interfaces, and how might they impact existing implementations?

    Back

    Key Limitations of Default Methods

    1. Compatibility Issues:

      • Injected into existing implementations without their knowledge
      • May violate assumptions in pre-Java 8 implementations
      • Cannot guarantee preservation of implementation-specific invariants
    2. Technical Constraints:

      • Cannot implement Object methods (equals, hashCode, toString)
      • Cannot contain instance fields
      • Cannot have non-public static members (except private methods in Java 9+)
      • Limited access to implementation state
    3. Problematic Cases (example: Collection.removeIf):

      default boolean removeIf(Predicate<? super E> filter) { // Implementation using iterator() and remove() }

      Breaks invariants in:

      • Synchronized collections (no synchronization)
      • Immutable collections (attempts modification)
      • Collections with value restrictions

    When Default Methods Are Appropriate

    • DO USE when:

      • Creating new interfaces with standard implementations
      • Methods can be implemented using only other interface methods
      • No implementation-specific state access is needed
      • Simplifies implementation of the interface
    • AVOID when:

      • Adding to widely-implemented existing interfaces
      • Implementation requires internal state access
      • Cannot maintain behavioral guarantees (thread safety, etc.)
      • Risk of breaking existing implementations is high

    Decision Rule: Before adding default methods, assess whether they could break existing implementations' assumptions or invariants.

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