You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

《Effective Java》转发类安全性与条目18组合优于继承技术问询

Will a New Superclass Method Break Inheritance-Based Security Guarantees?

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:00:13