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

为何Bloch称无法子类化集合实现类?兼谈不可变类扩展可变类问题

Great question—let's break this down step by step, since Joshua Bloch's advice ties into core OOP principles like encapsulation, the Liskov Substitution Principle (LSP), and the integrity of immutable contracts.


1. Why Bloch says you can't subclass collection framework "convenience implementations" (like ArrayList, LinkedList)

While these classes have public constructors, they were never designed for inheritance. Here's why that matters:

  • No access to internal state: Collection classes like ArrayList keep their core state (like the elementData array or size field) private. If you subclass and override a method (say, add()), you can't sync your changes with the parent's internal state—leading to inconsistent behavior (e.g., size() returning the wrong value after adding an element).
  • LSP violations are unavoidable: Collection classes have strict contracts (e.g., add() must always add the element and return true). If you subclass to modify this behavior (like blocking certain elements), code expecting a standard ArrayList will break when given your subclass. Bloch’s point is these classes are meant to be used, not extended—composition is always safer than inheritance here.
  • Hidden parent class behavior: Parent methods often call internal helper functions you can’t control. For example, ArrayList.ensureCapacity() might resize the internal array without your subclass’s knowledge, creating unexpected state changes.

As Bloch puts it directly:

"The collection framework's convenience implementations were not designed for inheritance. They have no hooks into their internal workings, so any subclass is likely to violate the LSP or produce inconsistent state."


2. Why you can't extend a mutable class to make an immutable one

This boils down to conflicting contracts and broken expectations:

  • Upcasting destroys immutability: If you create ImmutableList extends ArrayList, you can cast it to its mutable parent type (ArrayList). Any code using that ArrayList reference will try to call add() or remove()—even if your subclass overrides these methods to throw exceptions, this creates a fragile, confusing API. Users expect a mutable class to be mutable, so this violates the principle of least surprise.
  • You can't override all mutable behavior: Parent classes often have package-private or final methods that modify state, which your subclass can't override. For example, ArrayList has internal methods that adjust the array size—you can’t stop the parent from invoking these.
  • Immutability requires full control: An immutable class’s state must be fixed at construction and never change. When extending a mutable class, you don’t own the parent’s state or behavior—so you can’t guarantee nothing will modify it, intentionally or accidentally.

A quick example of how this breaks:

class ImmutableList extends ArrayList<String> {
    @Override
    public boolean add(String s) {
        throw new UnsupportedOperationException();
    }
    // Override all other mutable methods...
}

// This breaks the immutable contract:
ArrayList<String> mutableRef = new ImmutableList();
mutableRef.add("Oops"); // Throws exception, but the caller expected a working ArrayList

3. Can a mutable subclass modify an immutable class's instance if the immutable class has package-private constructors and fields?

It depends on two key factors: whether the fields are final, and whether the subclass shares the same package:

  • Same package, non-final fields: If the immutable class’s fields are package-private and not final, the subclass can directly modify them. This completely breaks the "immutable" promise:
    package mypackage;
    
    class AlmostImmutable {
        String value; // Package-private, non-final
        AlmostImmutable(String value) { this.value = value; }
        public String getValue() { return value; }
    }
    
    package mypackage;
    
    class MutableSubclass extends AlmostImmutable {
        MutableSubclass(String value) { super(value); }
        public void setValue(String newValue) { this.value = newValue; } // Works!
    }
    
  • Same package, final fields: final fields can’t be reassigned, even by subclasses. But if the field is a mutable reference type (like ArrayList), the subclass might modify the object it points to—unless the immutable class properly encapsulates it (e.g., storing an unmodifiable copy):
    package mypackage;
    
    class TrulyImmutable {
        final List<String> items;
        TrulyImmutable(List<String> items) {
            this.items = Collections.unmodifiableList(new ArrayList<>(items));
        }
        public List<String> getItems() { return items; }
    }
    
    package mypackage;
    
    class MutableSubclass extends TrulyImmutable {
        MutableSubclass(List<String> items) { super(items); }
        public void addItem(String item) {
            this.items.add(item); // Throws UnsupportedOperationException!
        }
    }
    
  • Different package: Subclasses outside the package can’t access package-private fields at all. If the immutable class exposes mutable references via getters, anyone (including subclasses) could modify internal state—but that’s a flaw in the immutable class’s design, not the subclass’s fault.

内容的提问来源于stack exchange,提问作者Pavel Pavel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:22:35