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

时间:2022-08-27 20:46:57

It is bad practice to call overridable methods in a constructor (see this link). This will always call the method defined in the class and not a derived class' method. In Java's HashSet there is a constructor that accepts a Collection. This method delegates to the addAll method. My question is why won't this break any derived classes of HashSet.

在构造函数中调用可覆盖的方法是不好的做法(请参阅此链接)。这将始终调用类中定义的方法,而不是派生类的方法。在Java的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

构造一个包含指定集合中元素的新集合。使用默认加载因子(0.75)创建HashMap,初始容量足以包含指定集合中的元素。参数:c要将其元素放入此集合的集合抛出:java.lang.NullPointerException如果指定的集合为null

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

2 个解决方案

#1


The answer is that it does break subclasses, potentially. It's easy to show that:

答案是它确实打破了子类。很容易证明:

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);
    }

}

Run Main, and you'll get:

运行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)

... because while the superconstructor is running, it calls BrokenSet.add which expects list to be non-null.

...因为在超级构造器运行时,它调用BrokenSet.add,它要求list为非null。

Subclasses would need to be written carefully to avoid this being an issue. (You might want to look at what LinkedHashSet does, for example.)

需要仔细编写子类以避免这是一个问题。 (例如,您可能希望查看LinkedHashSet的功能。)

#2


As it's not the only constructor, just additional one added for convenience, then it's not that scary: you still can implement the similar constructor in the derived Set calling default superclass constructor (with no arguments), initializing your instance and then calling addAll(). Probably it's implemented that way because it was easier to do so.

因为它不是唯一的构造函数,为了方便起见只添加了一个,所以它并不可怕:你仍然可以在派生的Set调用默认超类构造函数(没有参数)中实现类似的构造函数,初始化你的实例然后调用addAll() 。可能它是以这种方式实现的,因为它更容易实现。

#1


The answer is that it does break subclasses, potentially. It's easy to show that:

答案是它确实打破了子类。很容易证明:

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);
    }

}

Run Main, and you'll get:

运行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)

... because while the superconstructor is running, it calls BrokenSet.add which expects list to be non-null.

...因为在超级构造器运行时,它调用BrokenSet.add,它要求list为非null。

Subclasses would need to be written carefully to avoid this being an issue. (You might want to look at what LinkedHashSet does, for example.)

需要仔细编写子类以避免这是一个问题。 (例如,您可能希望查看LinkedHashSet的功能。)

#2


As it's not the only constructor, just additional one added for convenience, then it's not that scary: you still can implement the similar constructor in the derived Set calling default superclass constructor (with no arguments), initializing your instance and then calling addAll(). Probably it's implemented that way because it was easier to do so.

因为它不是唯一的构造函数,为了方便起见只添加了一个,所以它并不可怕:你仍然可以在派生的Set调用默认超类构造函数(没有参数)中实现类似的构造函数,初始化你的实例然后调用addAll() 。可能它是以这种方式实现的,因为它更容易实现。