Creating and Destroying Objects
Flashcards for topic Creating and Destroying Objects
Preview Cards
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:
-
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
-
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
-
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.
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:
-
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()
- Telescoping:
-
Parameter identification issues:
- Telescoping: What does
0
mean in position 4? - Builder: Self-documenting parameter names
- Telescoping: What does
-
Parameter order dependency:
- Telescoping: Reversing parameters with same type causes runtime errors
- Builder: Named methods eliminate order dependency
-
Inextensible parameter sets:
- Telescoping: New optional parameter requires new constructors
- Builder: Just add another optional method to Builder
-
Combinatorial explosion of constructors:
- Telescoping: 2ⁿ constructors for n optional parameters
- Builder: Single builder handles all combinations
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 parameterself()
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
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:
-
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)
-
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)
-
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.
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:
-
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; }
-
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); }
-
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
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
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)
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.
Front
What security vulnerability do finalizers introduce to Java classes?
Back
Finalizers create vulnerability to "finalizer attacks":
- If an exception is thrown from a constructor, the finalizer of a malicious subclass can still run
- The finalizer can record a reference to the partially constructed object in a static field
- This prevents the malformed object from being garbage collected
- 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.
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.