API коллекций Java: почему Unmodifiable [List | Set | Map] не являются общедоступными классами?
Collections.unmodifiableList(...)
возвращает новый экземпляр статического внутреннего класса UnmodifiableList
. Другие немодифицируемые классы коллекций построены таким же образом.
Были эти классы общедоступными, у одного было два преимущества:
- способность указывать более конкретное возвращаемое значение (например,
UnmodifiableList
), поэтому пользователь API не захочет модифицировать эту коллекцию;
- возможность проверки во время выполнения, если
List
- instanceof UnmodifiableList
.
Итак, были ли преимущества не, чтобы эти классы были общедоступны?
EDIT: не было представлено никаких убедительных аргументов, поэтому я выбираю наиболее подходящий ответ.
Ответы
Ответ 1
Я думаю, что оба преимущества есть, но не так полезны. Основные проблемы остаются неизменными: UnmodifiableList по-прежнему является списком, и, следовательно, все сеттеры доступны, и основные коллекции по-прежнему могут быть изменены. Создание класса UnmodifiableList public добавило бы иллюзию немодифицируемого.
Лучше всего было бы помочь компилятору, но для этого иерархии классов коллекции пришлось бы сильно изменить. Например, API-интерфейс коллекции Scala является более продвинутым в этом отношении.
Недостатком было бы введение по меньшей мере трех дополнительных классов/интерфейсов в API. Из-за того, что они не так полезны, я думаю, что оставить их вне API - хороший выбор.
Ответ 2
Лично я полностью согласен с тобой. В основе проблемы лежит тот факт, что Java-дженерики не являются ковариантными, что, в свою очередь, связано с изменчивой коллекцией Java.
Невозможно, чтобы система типа Java могла кодифицировать тип, который, по-видимому, имеет мутаторы, на самом деле неизменен. Представьте себе, если мы приступим к разработке некоторого решения:
interface Immutable //marker for immutability
interface ImmutableMap<K, V> extends Map<K, V>, Immutable
Но тогда ImmutableMap
является подклассом Map
, и, следовательно, Map
можно присваивать из ImmutableMap
, поэтому любой метод, который возвращает такой неизменяемый Map:
public ImmutableMap<K, V> foo();
может быть присвоен карте и поэтому может быть изменен во время компиляции:
Map<K, V> m = foo();
m.put(k, v); //oh dear
Итак, вы можете видеть, что добавление этого типа на самом деле не помешало нам сделать что-то плохое. Я думаю, по этой причине было принято решение о том, что этого недостаточно, чтобы предложить.
Язык, подобный scala, содержит аннотации вариации объявления-сайта. То есть вы можете указать тип как ковариантный (и, следовательно, неизменяемый) как Scala Map (фактически он ковариантен в своем параметре V
). Следовательно, ваш API может объявить, является ли его возвращаемый тип изменчивым или неизменным.
Как и в другом случае, Scala позволяет вам объявлять типы пересечений, так что вам даже не нужно создавать интерфейс ImmutableXYZ
как отдельный объект, вы можете указать способ возврата:
def foo : XYZ with Immutable
Но тогда Scala имеет правильную систему типов, тогда как Java не
Ответ 3
Если вам важно проверить, был ли список создан с помощью Collections.unmodifiableList, вы можете создать экземпляр и спросить об этом классе. Теперь вы можете сравнить этот класс с классом любого списка.
private static Class UNMODIFIABLE_LIST_CLASS =
Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
...
}
Ответ 4
Ответ на вопрос довольно простой: в то время, в 1998 году, эффективный дизайн был немного фланговым. Люди думали об этом, это не было, по-видимому, приоритетом. Но истинного, глубокого размышления об этом не было.
Если вы хотите использовать такой механизм, используйте Guava ImmutableList/Set/Map/...
Они явно не изменяются, и хорошая практика при использовании этой библиотеки заключается не в том, чтобы возвращать список, а в ImmutableList. Поэтому вы будете знать, что List/Set/Map/... неизменен.
Пример:
private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
return constants;
}
О самом дизайне UnmodifiableXxx можно было бы сделать следующее:
public static final class UnmodifiableXxx implements Xxx { // don't allow extend
// static if inside Collections
UnmodifiableXxx (Xxx backend) { // don't allow direct instanciation
...
}
...
}
Ответ 5
- способность указывать более конкретное возвращаемое значение (например,
UnmodifiableList
), поэтому пользователь API не стал бы придумывать модификацию этой коллекции;
В правильном API это уже должно быть документировано в javadoc метода, возвращающего немодифицируемый список.
- возможность проверки во время выполнения, если
List
- instanceof UnmodifiableList
.
Такая потребность указывает на то, что фактическая проблема лежит где-то в другом месте. Это недостаток в разработке кода. Спросите себя, вам когда-либо приходилось проверять, является ли List
экземпляром ArrayList
или LinkedList
? Является ли это ArrayList
, LinkedList
или UnmodifiableList
явно решением, которое должно быть сделано во время записи кода, а не во время выполнения кода. Если вы столкнулись с проблемами, потому что пытаетесь изменить UnmodifiableList
(для которых разработчик API может иметь очень хорошие результаты, которые должны быть уже задокументированы), то это скорее ваша собственная ошибка, а не ошибка времени выполнения.
Все со всеми, это не имеет никакого смысла. Collections#unmodifiableXXX()
, synchronizedXXX()
и checkedXXX()
никак не представляют конкретные реализации. Все они просто decorators, которые могут применяться независимо от конкретной конкретной реализации.
Ответ 6
Предположим, что UnmodifiableList
- открытый класс. Я подозреваю, что это усыпляет программистов ложным чувством безопасности. Помните, UnmodifiableList
- это вид изменяемого List
. Это означает, что содержимое UnmodifiableList
все равно может быть изменено с помощью любых изменений, внесенных в его базовый List
. Наивный программист может не понимать этот нюанс и может ожидать, что экземпляры UnmodifiableList
будут неизменными.
Ответ 7
Я думаю, что ответ заключается в том, что форма метода правильно знает о используемых дженериках и не требует дополнительного программирования для передачи этой информации, в то время как форма класса потребует больше возиться. Форма метода для unmodifiableMap
имеет два плавающих общих аргумента, которые он сопоставляет как общим аргументам возвращаемого типа, так и переданного аргумента.
public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
return new UnmodifiableMap<K,V>(m);
}