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