HashSet 在构造函数中调用可覆盖的方法

HashSet calls overridable method in constructor

在构造函数中调用可重写的方法是不好的做法(参见 this link). This will always call the method defined in the class and not a derived class' method. In Java's HashSet 有一个接受 Collection 的构造函数。此方法委托给 addAll 方法。我的问题这就是为什么这不会破坏 HashSet.

的任何派生 类

Constructs a new set containing the elements in the specified collection. The HashMap is created with default load factor (0.75) and an initial capacity sufficient to contain the elements in the specified collection. Parameters: c the collection whose elements are to be placed into this set Throws: java.lang.NullPointerException if the specified collection is null

public HashSet(Collection<? extends E> c) {
    map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

答案是它确实 可能会破坏子类。很容易证明:

BrokenSet.java:

import java.util.*;

public class BrokenSet<E> extends HashSet<E> {
    private final List<E> list = new ArrayList<E>();

    public BrokenSet(Collection<? extends E> c) {
        super(c);
    }

    @Override public boolean add(E item) {
        if (super.add(item)) {
            list.add(item);
            return true;
        }
        return false;
    }
}

Main.java:

import java.util.*;

public class Main{

    public static void main(String[] args) {
        Collection<String> strings = Arrays.asList("x", "y");
        Set<String> set = new BrokenSet<>(strings);
    }

}

运行 Main,你会得到:

Exception in thread "main" java.lang.NullPointerException
        at BrokenSet.add(BrokenSet.java:12)
        at java.util.AbstractCollection.addAll(Unknown Source)
        at java.util.HashSet.<init>(Unknown Source)
        at BrokenSet.<init>(BrokenSet.java:7)
        at Main.main(Main.java:7)

... 因为当超构造函数是 运行 时,它调用 BrokenSet.add 期望 list 为非空。

需要仔细编写子类以避免这成为一个问题。 (例如,您可能想看看 LinkedHashSet 的作用。)

因为它不是唯一的构造函数,只是为了方便而添加了一个,所以它并不那么可怕:您仍然可以在派生 Set 中实现类似的构造函数调用默认超类构造函数(不带参数),初始化您的实例和然后调用 addAll()。可能它是这样实现的,因为这样做更容易。