Подтвердить что ты не робот

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

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

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

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

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

EDIT: не было представлено никаких убедительных аргументов, поэтому я выбираю наиболее подходящий ответ.

4b9b3361

Ответ 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);
}