Card 05/ 08

GotchaDifficulty: Advanced1 min

Why the Same Leak Happens Through a Constructor

Every getter now returns a copy. The audit log still gains entries nobody added, and this time the class never hands anything out at all.

java
class Audit {
    private final List<String> entries;

    Audit(List<String> initial) {
        this.entries = initial;
    }

    List<String> entries() { return List.copyOf(entries); }
}
java
List<String> mine = new ArrayList<>();
Audit audit = new Audit(mine);

mine.add("");   // and the audit has it too

Line 5 stored the address it was handed. The caller kept their own copy of that address, so both the caller and the object now reach one list — and the validation in addEntry never sees anything the caller does directly.

Put this beside the getter and the shared fact is the whole lesson: private protects the variable, never the object it points at, and it does not matter which direction the reference crossed the boundary. Out through a getter or in through a constructor, the result is two names for one object.

java
Audit(List<String> initial) {
    this.entries = new ArrayList<>(initial);
}

None of this applies to a field holding a String, an int or any other immutable value. There is nothing to reach back into, which is why immutable types make encapsulation cheap.