API коллекций Java: почему Unmodifiable[List|Set|Map] не являются публично видимыми классами?

Collections.unmodifiableList(...) возвращает новый экземпляр статического внутреннего класса UnmodifiableList, Другие классы немодифицируемых коллекций строятся таким же образом.

Были ли эти классы публичными, у одного было два преимущества:

  • возможность указать более конкретное возвращаемое значение (например, UnmodifiableList), поэтому пользователь API не придет к мысли изменить эту коллекцию;
  • возможность проверки во время выполнения, если List является instanceof UnmodifiableList,

Итак, были ли какие-то преимущества не делать эти уроки публичными?

РЕДАКТИРОВАТЬ: Никаких определенно убедительных аргументов не было представлено, поэтому я выбираю наиболее одобренный ответ.

7 ответов

Решение

Я думаю, что оба преимущества есть, но они не так полезны. Основные проблемы остаются прежними: UnmodifiableList по-прежнему является списком, и, таким образом, все установщики доступны, а базовые коллекции по-прежнему модифицируемы. Обнародование класса 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, имеет аннотации на сайте объявлений. То есть, вы можете указать тип как ковариантный (и, следовательно, неизменный), как это делает карта Scala (на самом деле она ковариантна в своем V параметр). Следовательно, ваш API может объявить, является ли его возвращаемый тип изменяемым или неизменным.

Кроме того, Scala позволяет объявлять типы пересечений, чтобы вам даже не нужно было создавать ImmutableXYZ Интерфейс как отдельная сущность, вы можете указать метод для возврата:

def foo : XYZ with Immutable

Но тогда у scala есть правильная система типов, тогда как у Java нет

Ответ на этот вопрос довольно прост: в то время, в 1998 году, эффективный дизайн был немного сложным. Люди думали, что это не было приоритетом. Но по-настоящему глубоких размышлений об этом не было.

Если вы хотите использовать такой механизм, используйте Gumm's ImmutableList/Set/Map/...

Они явно неизменны, и хорошей практикой при использовании этой библиотеки является не возвращение списка List, а ImmutableList. Таким образом, вы будете знать, что список / набор / карта /... является неизменным.

Пример:

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
    ...
  }
  ...
}

Если для вас важно проверить, был ли список создан с помощью Collections.unmodifiableList, вы можете создать экземпляр и запросить класс. Теперь вы можете сравнить этот класс с классом любого списка.

private static Class UNMODIFIABLE_LIST_CLASS = 
    Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
    ...
} 
  • возможность указать более конкретное возвращаемое значение (например, 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);
}
Другие вопросы по тегам