Flashcards for topic Classes and Interfaces
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?
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:
All boxed primitives (Integer, Boolean, etc.) and BigInteger use this pattern for frequently requested values.
What critical issue can occur when overriding clone() in a subclass?
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:
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; } }
What is "simulated multiple inheritance" and how does it work to take advantage of skeletal implementations?
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:
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:
What is the process for creating an effective skeletal implementation of an interface?
Process for creating a skeletal implementation:
Study the interface and identify primitive methods:
Identify methods that can be implemented in terms of primitives:
Create the skeletal implementation class:
Add comprehensive documentation:
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()); } }
What are the key advantages of interfaces over abstract classes for defining types?
Advantages of interfaces over abstract classes:
Multiple implementations:
Retrofitting existing classes:
Defining mixins:
Non-hierarchical type frameworks:
Enable safer functionality 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.
What is the difference between a skeletal implementation and a simple implementation?
Skeletal Implementation vs. Simple Implementation:
Skeletal Implementation:
Simple Implementation:
Key differences:
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 }
What technique can be used to safely extend a class that wasn't designed for inheritance but must be extended?
Technique for safely extending a non-inheritable class:
The Helper Method Pattern:
What problem can occur when adding default methods to existing interfaces, and why does this happen specifically with the Apache Commons SynchronizedCollection implementation?
SynchronizedCollection
class doesn't override the default removeIf
methodremoveIf
implementation cannot maintain the synchronization promise because:
ConcurrentModificationException
This illustrates a fundamental limitation of Java's default method feature - they cannot ensure behavioral consistency with class-specific implementation guarantees.
What are the best practices for exporting constants in Java, and why is each approach preferred over the constant interface pattern? Provide code examples.
Best Practices for Exporting Constants:
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; // ... }
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; } }
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; }
import static com.example.PhysicalConstants.*;
Each approach avoids the key problems of constant interfaces:
What are the appropriate uses and limitations of default methods in Java interfaces, and how might they impact existing implementations?
Compatibility Issues:
Technical Constraints:
Problematic Cases (example: Collection.removeIf):
default boolean removeIf(Predicate<? super E> filter) { // Implementation using iterator() and remove() }
Breaks invariants in:
DO USE when:
AVOID when:
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.