如何正确实现含可变元素集合的防御性拷贝?如何保障类不可变性?
Hey there! Let's break down your questions about defensive copying and maintaining class immutability when working with collections that hold mutable objects (like Date or custom mutable classes).
1. How to Correctly Implement Defensive Copying for Collections with Mutable Elements?
Defensive copying isn't just about copying the collection itself—since the elements inside are mutable, you need to copy each individual element too to prevent external code from messing with your class's internal state. Here's how to do it right:
In Constructors
Never assign the incoming collection directly to your internal field. If you do, external code that passed the list can modify it later, which will change your class's state without you knowing. Instead:
- Create a new instance of the collection (e.g.,
new ArrayList<>()). - Loop through each element in the input list, make a copy of the mutable element, then add that copy to your internal collection.
For example, with Date (a classic mutable JDK class):
public DateContainer(List<Date> dates) { this.dates = new ArrayList<>(); for (Date date : dates) { // Use Date's copy constructor or getTime() to create a new, independent Date object this.dates.add(new Date(date.getTime())); } }
In Getter Methods
Don't return your internal collection directly—even if it's final, external code can still add/remove elements or modify the mutable objects inside. Instead:
- Return a new collection containing copies of each mutable element (safer, since it prevents any external modification of your internal state).
- Or, return an unmodifiable view of the collection (using
Collections.unmodifiableList()), but note this only stops changes to the collection structure (adding/removing elements)—external code can still modify the mutable elements unless you copy them first.
Example of a safe getter:
public List<Date> getDates() { List<Date> copiedDates = new ArrayList<>(); for (Date date : this.dates) { copiedDates.add(new Date(date.getTime())); } return copiedDates; }
For Custom Mutable Classes
If you're using your own mutable classes, make sure each class has a copy constructor or a copy() method that creates a deep copy of the object. Then use that method when adding elements to your internal collection to ensure independence.
2. Best Way to Ensure Class Immutability When It Contains a Collection of Mutable Elements?
To make your class truly immutable (meaning its state can't change after initialization), you need to follow these key rules—especially since your collection holds mutable elements:
Step 1: Mark the class as final
This prevents subclasses from overriding methods and breaking immutability (e.g., adding a setter method that modifies the collection).
Step 2: Make the collection field private and final
private ensures no external access, and final prevents reassignment of the collection reference.
Step 3: Defensively copy the collection and its elements in the constructor
As we covered earlier, don't use the input collection directly—create a new collection with copied mutable elements. You can even make the internal collection unmodifiable to prevent accidental changes within the class itself:
this.dates = Collections.unmodifiableList(new ArrayList<>(copiedElements));
Step 4: Never expose the internal collection directly
In getters, return copies of the elements (as shown in the getter example) to ensure external code can't modify your class's internal state.
Step 5: Remove any methods that modify the collection or its elements
Your code snippet includes an addItems() method—but if the class is supposed to be immutable, this method shouldn't exist! Modifying the collection after initialization breaks immutability. Instead, if you need to "add" an element, create a new instance of DateContainer with the updated collection:
public DateContainer withAddedDate(Date newDate) { List<Date> newDates = new ArrayList<>(this.dates); newDates.add(new Date(newDate.getTime())); return new DateContainer(newDates); }
This way, the original instance stays immutable, and you get a new instance with the desired change.
Full Example of an Immutable DateContainer
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Collections; public final class DateContainer { private final List<Date> dates; public DateContainer(List<Date> dates) { // Defensively copy input list and each Date element List<Date> copiedDates = new ArrayList<>(); for (Date date : dates) { copiedDates.add(new Date(date.getTime())); } // Make internal list unmodifiable to prevent accidental changes this.dates = Collections.unmodifiableList(copiedDates); } public List<Date> getDates() { // Return copies of each Date to prevent external modification List<Date> result = new ArrayList<>(); for (Date date : this.dates) { result.add(new Date(date.getTime())); } return result; } // Immutable way to "add" a date: return a new instance public DateContainer withAddedDate(Date newDate) { List<Date> updatedDates = new ArrayList<>(this.dates); updatedDates.add(new Date(newDate.getTime())); return new DateContainer(updatedDates); } }
内容的提问来源于stack exchange,提问作者Pavel Pavel

