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

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

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

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

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

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

Ответы [ 7 ]

7 голосов
/ 28 ноября 2010

Лично я с тобой полностью согласен.В основе проблемы лежит тот факт, что дженерики 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 нет

4 голосов
/ 28 ноября 2010

Я думаю, что оба преимущества есть, но они не так полезны. Основные проблемы остаются прежними: UnmodifiableList по-прежнему является списком, и, таким образом, все установщики доступны, а базовые коллекции по-прежнему модифицируемы. Обнародование класса UnmodifiableList добавило бы иллюзию того, что он не может быть изменен.

Лучше было бы помочь компилятору, но для этого иерархии классов коллекции должны были бы сильно измениться. Например, API коллекции Scala намного более продвинут в этом отношении.

Недостатком было бы введение в API как минимум трех дополнительных классов / интерфейсов. Поскольку они не слишком полезны, я думаю, что оставить их вне API - хороший выбор.

3 голосов
/ 03 января 2011

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

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

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

Пример:

private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
  return constants;
}

Что касается самого проекта UnmodifiableXxx, можно было бы сделать следующее:1010 *

public static final class UnmodifiableXxx implements Xxx { // don't allow extend
  // static if inside Collections
  UnmodifiableXxx (Xxx backend) { // don't allow direct instanciation
    ...
  }
  ...
}
3 голосов
/ 28 ноября 2010

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

private static Class UNMODIFIABLE_LIST_CLASS = 
    Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
    ...
} 
1 голос
/ 28 ноября 2010
  • возможность указать более конкретное возвращаемое значение (например, UnmodifiableList), поэтому пользователь API не придет к мысли изменить эту коллекцию;

В надлежащем API это уже должно быть задокументировано в javadoc метода, возвращающего неизменяемый список.

  • способность проверять во время выполнения, если List равен instanceof UnmodifiableList.

Такая необходимость указывает, что проблема фактическая лежит где-то еще. Это недостаток в дизайне кода. Спросите себя, вам когда-нибудь приходилось проверять, является ли List экземпляром ArrayList или LinkedList? Будь то ArrayList, LinkedList или UnmodifiableList - это определенно решение, которое должно быть принято во время записи кода, а не во время выполнения кода. Если вы столкнулись с проблемами, потому что пытаетесь изменить UnmodifiableList (для которого у разработчика API могут быть очень хорошие причины, которые должны быть уже задокументированы), то это скорее ваша собственная ошибка, а не ошибка времени выполнения.

Всё со всем, в этом нет смысла. Collections#unmodifiableXXX(), synchronizedXXX() и checkedXXX() никоим образом не представляют конкретные реализации. Все они являются просто декораторами , которые могут применяться независимо от конкретной конкретной реализации.

0 голосов
/ 28 ноября 2010

Я думаю, что ответ заключается в том, что форма метода правильно знает используемые обобщения и не требует дополнительного программирования для передачи этой информации, в то время как форма класса потребует больше возни. Форма метода для unmodifiableMap имеет два плавающих универсальных аргумента, которые отображаются как на универсальные аргументы возвращаемого типа, так и переданного аргумента.

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
    return new UnmodifiableMap<K,V>(m);
}
0 голосов
/ 28 ноября 2010

Предположим, UnmodifiableList был публичным классом. Я подозреваю, что это приведет программистов к ложному чувству безопасности. Помните, что UnmodifiableList - это представление из модифицируемого List. Это означает, что содержимое UnmodifiableList все еще может изменяться с помощью любых изменений, внесенных в его базовый List. Наивный программист может не понимать этот нюанс и может ожидать, что экземпляры UnmodifiableList будут неизменными.

...