《Effective Java》转发类安全性与条目18组合优于继承技术问询
Great question—this hits right at the heart of the subclass fragility issue Joshua Bloch warns about in Effective Java 3rd Edition's Item 18: "Favor Composition Over Inheritance." To cut to the chase: yes, adding a new method to the superclass will almost certainly break your security guarantee.
Let's break down why this happens, using your scenario as an example:
The Inheritance Trap
Suppose you've inherited a collection class (say, HashSet) and overridden every method you know that adds elements (add(), addAll()) to enforce your predicate check. Your code works perfectly—until the superclass's authors release an update that adds a new element-adding method, like addIfAbsent(E).
Here's the problem: your subclass doesn't override this new method. When someone calls addIfAbsent() on your subclass instance, it will directly invoke the superclass's implementation, completely bypassing your predicate check. Suddenly, invalid elements can slip into the collection, violating the security rule your code was supposed to enforce.
Example: Unsafe Inheritance Implementation
// Flawed subclass relying on inheritance class PredicateHashSet<E> extends HashSet<E> { private final Predicate<E> validator; public PredicateHashSet(Predicate<E> validator) { this.validator = validator; } @Override public boolean add(E e) { if (!validator.test(e)) { throw new IllegalArgumentException("Invalid element"); } return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { for (E e : c) { if (!validator.test(e)) { throw new IllegalArgumentException("Invalid element in collection"); } } return super.addAll(c); } }
If HashSet adds addIfAbsent(E) in a new version, this code becomes unsafe:
PredicateHashSet<String> safeSet = new PredicateHashSet<>(s -> s.length() > 3); safeSet.addIfAbsent("foo"); // Bypasses validation! "foo" gets added to the set
Why Composition Fixes This
The solution Bloch recommends is to use composition instead of inheritance. Instead of subclassing the collection, create a wrapper class that holds an instance of the collection, implements the appropriate interface (like Set), and delegates all operations to the wrapped instance—after applying your predicate check.
With this approach, you control every method exposed by your class. If the underlying collection adds new methods, your wrapper won't automatically expose them. You can choose to add support for the new method explicitly, and when you do, you'll add your predicate check to it.
Example: Safe Composition Implementation
// Secure wrapper using composition class PredicateSet<E> implements Set<E> { private final Set<E> delegate; private final Predicate<E> validator; public PredicateSet(Set<E> delegate, Predicate<E> validator) { this.delegate = Objects.requireNonNull(delegate); this.validator = Objects.requireNonNull(validator); } @Override public boolean add(E e) { validate(e); return delegate.add(e); } @Override public boolean addAll(Collection<? extends E> c) { boolean modified = false; for (E e : c) { modified |= add(e); // Reuses our validated add() method } return modified; } // Explicitly add support for new methods, with validation public boolean addIfAbsent(E e) { validate(e); return delegate.addIfAbsent(e); } // Helper method for validation private void validate(E e) { if (!validator.test(e)) { throw new IllegalArgumentException("Invalid element"); } } // Delegate all other Set methods to the wrapped instance... @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } // ...and so on for all Set methods }
Now, even if the underlying Set implementation adds new methods, you're in full control. No invalid elements can sneak in without your validation logic running first.
Key Takeaway
Inheritance couples your subclass tightly to the superclass's implementation. When the superclass changes (like adding new methods), your subclass can break in unexpected ways—including violating critical security guarantees. Composition, on the other hand, decouples your class from the underlying implementation, giving you full control over behavior and ensuring your security rules stay intact regardless of superclass updates.
内容的提问来源于stack exchange,提问作者shmosel

