HashSet вызывает переопределенный метод в конструкторе
Это плохая практика для вызова переопределенных методов в конструкторе (см. Эту ссылку). Это всегда будет вызывать метод, определенный в классе, а не метод производного класса. В Java HashSet
есть конструктор, который принимает Collection
, Этот метод делегирует addAll
метод. Мой вопрос: почему это не сломает какие-либо производные классы HashSet
,
Создает новый набор, содержащий элементы в указанной коллекции. HashMap создается с коэффициентом загрузки по умолчанию (0,75) и начальной емкостью, достаточной для размещения элементов в указанной коллекции. Параметры: c коллекция, элементы которой должны быть помещены в этот набор. Throws: java.lang.NullPointerException, если указанная коллекция является нулевой
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
2 ответа
Ответ заключается в том, что он потенциально разрушает подклассы. Легко показать, что:
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
делает, например.)
Поскольку это не единственный конструктор, просто дополнительный, добавленный для удобства, это не так страшно: вы все еще можете реализовать аналогичный конструктор в производном множестве, вызывая конструктор суперкласса по умолчанию (без аргументов), инициализируя ваш экземпляр и затем вызывая addAll(), Вероятно, это реализовано таким образом, потому что это было легче сделать.