发送数据的防御性拷贝最佳方式及Effective Java规则39实现疑问
Great question! Let's unpack this, since defensive copying can feel counterintuitive at first—especially when you start seeing multiple copies pop up in your code. First, let's tie this back to the core of Effective Java Item 39: defensive copying exists to protect a class's internal state from being modified unexpectedly by external code. Whether multiple copies are necessary depends on your design goals and the mutability of the data class.
Why Your Code Has Two Copies (And If They're Redundant)
Let's break down your example step by step:
SomeClass.getData()returns a copy: This is correct! SinceMyDatais mutable (it has asetName()method), returning a direct reference tosomeClass's internalmyDatawould let external code callsetName()and modifySomeClass's private state—breaking encapsulation. This copy is non-negotiable if you want to protectSomeClass's integrity.OtherClassconstructor makes a second copy: This one depends on whatOtherClassneeds. IfOtherClasswants to hold its own independent copy ofMyData(so external changes to the original passed-in instance don't affectOtherClass's internal state), this copy is necessary. But ifOtherClassonly needs to use the data temporarily, or you can guarantee the passed-inMyDatainstance won't be modified elsewhere, this copy is redundant.
The Optimal Fix: Make MyData Immutable
The cleanest way to eliminate redundant copying (and simplify your code entirely) is to turn MyData into an immutable class. Immutable objects are inherently safe to share—you never need to copy them, because their state can't be changed after creation. This aligns perfectly with Effective Java's emphasis on preferring immutable classes when possible.
Here's how to refactor your code:
// Immutable MyData class: no setters, all fields final, no mutable state final class MyData { private final String name; public MyData(String name) { this.name = name; } // Only a getter, no setter to modify state public String getName() { return name; } // No need for a copy constructor anymore! } public class SomeClass { private final MyData myData; SomeClass() { myData = new MyData("1"); } // Safe to return the direct reference—MyData can't be modified public MyData getData() { return myData; } public static void main(String[] args) { SomeClass someClass = new SomeClass(); OtherClass otherClass = new OtherClass(someClass.getData()); } } class OtherClass { private final MyData myData; // Safe to assign directly—no risk of external modification OtherClass(MyData data) { this.myData = data; } }
If MyData Must Stay Mutable
If you can't make MyData immutable (e.g., it needs to support state changes), you can optimize the copying based on your needs:
- Keep
SomeClass.getData()'s copy: This is still required to protectSomeClass's internal state. - Skip
OtherClass's copy only if: You don't care if external code modifies theMyDatainstance thatOtherClassholds. For example, ifOtherClassonly reads the data once and doesn't store it long-term, you can avoid the second copy. But ifOtherClassneeds a stable, unmodifiable copy of the data, keep the second copy.
Key Takeaways
- Defensive copying isn't about "copying every time you pass data"—it's about protecting your class's internal state.
- Immutable classes are the gold standard for safe data transfer: they eliminate the need for copying entirely while maintaining encapsulation.
- If mutability is unavoidable, only copy when necessary to prevent external code from altering your class's state.
内容的提问来源于stack exchange,提问作者myoldgrandpa

