Creating and Destroying Objects

Flashcards for topic Creating and Destroying Objects

Intermediate45 cardsGeneral

Preview Cards

Card 1

Front

What makes the JavaBeans pattern problematic for concurrent applications, and why does it "preclude the possibility of making a class immutable"?

Back

JavaBeans pattern problems for concurrency:

  1. Inconsistent state exposure:

    • Objects exist in partially initialized state between setter calls
    • Any thread accessing the object during construction sees incomplete/invalid state
    • Example:
      NutritionFacts food = new NutritionFacts(); // All fields have default values food.setServingSize(240); // Thread X could see object here with only servingSize set food.setServings(8); // Thread Y might see object with different state
  2. Precludes immutability because:

    • Immutable objects must be fully initialized at construction time
    • All fields must be final
    • Setters inherently mutate state, contradicting immutability
    • Example:
      // Can't mark fields final when using setters private int servingSize; // Can't be final if using setServingSize() public void setServingSize(int val) { servingSize = val; } // Mutates state
  3. Thread safety burden:

    • Programmer must manually synchronize access to mutable JavaBean
    • Risk of race conditions during construction
    • No compiler enforcement of proper synchronization

Immutability is a powerful tool for thread safety, and JavaBeans pattern makes it impossible to achieve.

Card 2

Front

How does the telescoping constructor pattern differ from the Builder pattern, and what specific problems with telescoping constructors does the Builder pattern solve?

Back

Telescoping Constructor Pattern:

// Multiple constructors with increasing parameters public class NutritionFacts { private final int servingSize; // Required private final int servings; // Required private final int calories; // Optional private final int fat; // Optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; } }

Problems Solved by Builder Pattern:

  1. Hard-to-read client code:

    • Telescoping: new NutritionFacts(240, 8, 100, 0, 35, 27)
    • Builder: new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build()
  2. Parameter identification issues:

    • Telescoping: What does 0 mean in position 4?
    • Builder: Self-documenting parameter names
  3. Parameter order dependency:

    • Telescoping: Reversing parameters with same type causes runtime errors
    • Builder: Named methods eliminate order dependency
  4. Inextensible parameter sets:

    • Telescoping: New optional parameter requires new constructors
    • Builder: Just add another optional method to Builder
  5. Combinatorial explosion of constructors:

    • Telescoping: 2ⁿ constructors for n optional parameters
    • Builder: Single builder handles all combinations
Card 3

Front

What is an enhanced version of the Builder pattern that could be used for class hierarchies? Include a code example showing how it works with inheritance.

Back

Hierarchical Builder Pattern uses recursive generics to support inheritance in builders:

// Base class with abstract builder public abstract class Pizza { public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } final Set<Topping> toppings; // Abstract builder with recursive generic type parameter T abstract static class Builder<T extends Builder<T>> { EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); // Return this with appropriate subtype to allow method chaining public T addTopping(Topping topping) { toppings.add(topping); return self(); } abstract Pizza build(); // Subclasses must override this to return "this" protected abstract T self(); } Pizza(Builder<?> builder) { toppings = builder.toppings.clone(); } } // Concrete Pizza subclass with its Builder subclass public class NYPizza extends Pizza { public enum Size { SMALL, MEDIUM, LARGE } private final Size size; // NYPizza.Builder extends Pizza.Builder with appropriate type public static class Builder extends Pizza.Builder<Builder> { private final Size size; public Builder(Size size) { this.size = size; } @Override public NYPizza build() { return new NYPizza(this); } @Override protected Builder self() { return this; } } private NYPizza(Builder builder) { super(builder); size = builder.size; } } // Usage example NYPizza pizza = new NYPizza.Builder(Size.MEDIUM) .addTopping(Topping.SAUSAGE) .addTopping(Topping.ONION) .build();

Key features:

  • Builder<T extends Builder<T>> creates recursive type parameter
  • self() method returns appropriate subtype to allow method chaining
  • Each concrete class gets its own builder that extends the parent builder
  • Builders know exactly what type they're building
Card 4

Front

What are the trade-offs between immutability and parameter flexibility when choosing among constructor patterns, and when should each pattern be preferred?

Back

Trade-offs between construction patterns:

| Pattern | Immutability | Parameter Flexibility | Thread Safety | Complexity | Best When | |---------|--------------|----------------------|---------------|------------|-----------| | Telescoping Constructor | ✅ Supports | ❌ Poor | ✅ Thread-safe | ⚠️ Medium | Few parameters (<4) | | JavaBeans | ❌ Prevents | ✅ Excellent | ❌ Unsafe | ✅ Low | Mutable classes where consistency during construction isn't critical | | Builder | ✅ Supports | ✅ Excellent | ✅ Thread-safe | ⚠️ Medium | Many parameters (4+), especially optional ones |

When to prefer each pattern:

  1. Telescoping Constructor:

    • When you have few parameters, mostly required
    • When immutability is critical
    • When construction must be atomic
    • Example: BigInteger(int, int, Random) (used for small objects)
  2. JavaBeans:

    • When class is already mutable by design
    • When thread safety is handled separately
    • When parameters are frequently changed after construction
    • Example: GUI components like JTextArea (properties get changed repeatedly)
  3. Builder:

    • When class has many optional parameters
    • When immutability is desired
    • When parameters have same type
    • When code readability is important
    • Example: NutritionFacts food labels (many optional fields)

The Builder pattern is the modern best practice for classes with many parameters, especially when immutability matters.

Card 5

Front

How does the Builder Pattern enforce validity checks and object invariants compared to other construction approaches?

Back

The Builder Pattern provides a strategic two-stage approach for validity checks:

  1. Parameter-level validations: Performed in the builder's setter methods

    public Builder calories(int val) { if (val < 0) throw new IllegalArgumentException("Calories cannot be negative"); calories = val; return this; }
  2. Object-level invariants: Checked in the build() method or constructor

    public NutritionFacts build() { // Verify invariants involving multiple parameters if (servingSize < 0 || servings < 0) throw new IllegalArgumentException("Serving values must be positive"); return new NutritionFacts(this); }
  3. Defense against attack: When copying parameters to object fields

    private NutritionFacts(Builder builder) { // Defensive copy of mutable objects to protect invariants servingSize = builder.servingSize; servings = builder.servings; // Additional invariant checks can be performed here }

Advantages over other approaches:

  • More granular error detection than telescoping constructors
  • Can identify exactly which parameter is invalid with specific messages
  • Allows partial object creation to be validated incrementally
  • Can enforce complex multi-parameter invariants before object creation
  • More robust than JavaBeans pattern which may have objects in inconsistent states
Card 6

Front

What is the resource factory pattern in dependency injection, and how does it work with Java's Supplier interface?

Back

The resource factory pattern in dependency injection:

  • Passes a factory object instead of the resource itself
  • Factory creates instances of resources on demand
  • Enables dynamic resource creation based on runtime conditions
  • Supports creating multiple instances of resources

Implementation with Java's Supplier interface:

// Using Supplier as a factory interface public class TileMaker { // Factory method accepting a tile factory public Mosaic create(Supplier<? extends Tile> tileFactory) { Mosaic mosaic = new Mosaic(); // Create tiles on demand using the factory for (int i = 0; i < 100; i++) { mosaic.add(tileFactory.get()); // get() creates a new tile } return mosaic; } } // Usage Mosaic mosaic = tileMaker.create(() -> new BlueTile());

Key points:

  • Supplier<T> introduced in Java 8 is perfect for factory pattern
  • Use bounded wildcard types (Supplier<? extends Tile>) to allow factories for subtypes
  • get() method produces a new instance each time it's called
  • Combines flexibility of dependency injection with dynamic instantiation
  • Allows client code to control instantiation strategy
Card 7

Front

What is autoboxing in Java, how can it lead to performance issues, and what is the best practice to avoid these problems?

Back

Autoboxing in Java:

  • Automatic conversion between primitive types and wrapper classes
  • Enables mixing primitives and objects in expressions
  • Happens implicitly when primitive is assigned to wrapper type

Performance issues:

// Performance problem due to autoboxing private static long sum() { Long sum = 0L; // Uses wrapper class instead of primitive for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; // Each addition creates a new Long object } return sum; }

This creates approximately 2³¹ unnecessary Long instances because:

  • Each addition autoboxes the result into a new Long object
  • Each iteration discards the previous Long object
  • Creates massive unnecessary garbage collection overhead

Best practice solution:

// Efficient version using primitives private static long sum() { long sum = 0L; // Uses primitive type for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; // Simple primitive operation, no boxing } return sum; }

Key guidelines:

  • Prefer primitives over boxed primitives for simple values
  • Be vigilant about accidental autoboxing in performance-critical code
  • Watch for subtle type differences (Long vs long) that can trigger autoboxing
  • Use primitives for local variables, parameters, return types, and fields
  • Only use wrapper types when required (generics, nullability, reflection)
Card 8

Front

Explain why an uncaught exception in a finalizer is particularly dangerous compared to exceptions elsewhere in code.

Back

An uncaught exception thrown during finalization:

  • Is silently ignored by the JVM
  • Terminates finalization of the object without warning
  • Does not print a stack trace (unlike normal uncaught exceptions)
  • Leaves no visible indication that anything went wrong
  • Can leave other objects in a corrupt state
  • When other threads attempt to use these corrupted objects, arbitrary nondeterministic behavior may result
  • Creates bugs that are nearly impossible to diagnose and debug

This is one advantage cleaners have over finalizers - a library using a cleaner has control over its thread.

Card 9

Front

What security vulnerability do finalizers introduce to Java classes?

Back

Finalizers create vulnerability to "finalizer attacks":

  1. If an exception is thrown from a constructor, the finalizer of a malicious subclass can still run
  2. The finalizer can record a reference to the partially constructed object in a static field
  3. This prevents the malformed object from being garbage collected
  4. The attacker can then invoke arbitrary methods on an object that should never have existed

Protection strategy: Make classes final to prevent subclassing, or write a final finalize method that does nothing.

Card 10

Front

Quantify the exact performance penalty of using finalizers versus standard resource management, with specific metrics.

Back

Performance penalties:

  • Creating, using, and reclaiming an AutoCloseable object with try-with-resources: ~12ns
  • Same operations with a finalizer: ~550ns (about 50x slower)
  • Using cleaners for all instances: ~500ns per instance (comparable to finalizers)
  • Using cleaners only as safety net: ~66ns per instance (about 5x slower than try-with-resources)

Primary reason for the penalty: Finalizers inhibit efficient garbage collection by requiring special processing for finalized objects.

The performance gap makes finalizers and full-time cleaners impractical for applications requiring high throughput or low latency.

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