2016-06-28 6 views
81

Предположим, я пишу метод, который должен вернуть Map. Например:Лучше ли вернуть ImmutableMap или карту?

public Map<String, Integer> foo() { 
    return new HashMap<String, Integer>(); 
} 

Подумав некоторое время, я решил, что нет никаких причин, чтобы изменить эту карту, как только она будет создана. Таким образом, я хотел бы вернуть ImmutableMap.

public Map<String, Integer> foo() { 
    return ImmutableMap.of(); 
} 

Должен ли я оставить возвращаемый тип как родовое карту, или я должен указать, что я возвращаюсь в ImmutableMap?

С одной стороны, именно поэтому были созданы интерфейсы; чтобы скрыть детали реализации.
С другой стороны, если я оставлю это так, другие разработчики могут упустить тот факт, что этот объект неизменен. Таким образом, я не стану достигать главной цели неизменяемых объектов; чтобы сделать код более понятным, минимизируя количество объектов, которые могут измениться. Даже самое худшее, через некоторое время кто-то может попытаться изменить этот объект, и это приведет к ошибке времени выполнения (компилятор не будет предупреждать об этом).

+2

Я подозреваю, что кто-то в конце концов пометит этот вопрос слишком открытым для аргументов. Можете ли вы задать более конкретные вопросы вместо «WDYT»? и «все в порядке ...?» Например, «есть ли какие-либо проблемы или соображения при возвращении обычной карты?» и «какие существуют альтернативы?» – ash

+0

Что, если позже вы решите, что он должен фактически вернуть измененную карту? – immibis

+3

@immibis lol, или список в этом отношении? – djechlin

ответ

63
  • Если вы пишете общественно-облицовочный API и что неизменность является важным аспектом вашего дизайна, я бы, безусловно, сделать его явным либо имеющим название методы четко обозначает, что возвращаемый карта будет неизменен или путем возврата конкретного типа карты. Упоминать это в javadoc недостаточно, на мой взгляд.

    Поскольку вы, по-видимому, используете реализацию Guava, я посмотрел на документ, и это абстрактный класс, поэтому он дает вам немного гибкости в конкретном конкретном типе.

  • Если вы пишете внутренний инструмент/библиотеку, становится гораздо более приемлемым просто вернуть простой Map. Люди будут знать о внутренностях кода, который они звонят, или, по крайней мере, будут иметь легкий доступ к нему.

Мое заключение заключается в том, что явное - это хорошо, не оставляйте вещи на волю случая.

+1

Один дополнительный интерфейс, один дополнительный абстрактный класс и один класс, который расширяет этот абстрактный класс. Это слишком много хлопот для такой простой вещи, как запрос OP, то есть если метод shoud возвращает тип интерфейса «Map» или тип реализации «ImmutableMap». –

+0

Ммм это правда, что подклассификация этого была бы громоздкой. Композиция может быть лучше, чем абстрактный класс здесь, но вы потеряете определенные типы, такие как 'SortedMap' и т. Д. В зависимости от того, насколько важна неизменность в API, я также предложил дешевые вещи, такие как явное имя для метода – Dici

+0

Я новичок хотя я не согласен, потому что вы предлагаете альтернативы, и все основывается. ИМХО, тем проще, и метод, который возвращает карту, не должен заставлять пользователей не изменять ее. Если они не хотят изменять его или сделать его потоком/безопасным, тогда они могут обернуть возвращенную карту любой желаемой реализацией. Если вы вернете неизменяемую карту, но пользователь хочет добавить запись (потому что это полезно для него), тогда ему нужно будет обернуть ее только для добавления одной записи. Я не вижу смысла в неизменной карте. –

16

С другой стороны, если я оставлю это так, другие разработчики могут упустить тот факт, что этот объект неизменен.

Следует упомянуть об этом в javadocs. Вы знаете, разработчики читают их.

Таким образом, я не стану достигать главной цели непреложных объектов; чтобы сделать код более понятным, минимизируя количество объектов, которые могут измениться. Даже в худшем случае, кто-то может попытаться изменить этот объект, , и это приведет к ошибке выполнения (компилятор не предупредит об этом ).

Ни один разработчик не публикует свой код непроверенным. И когда он проверяет это, он получает исключение, когда он видит не только причину, но и файл и строку, где он пытался записать неизменную карту.

Помните, что только сам Map будет неизменен, а не объекты, которые он содержит.

+10

* «Разработчик не публикует свой код непроверенным». * Вы хотите сделать ставку на это? Было бы гораздо меньше (моя презумпция) ошибок в общедоступном программном обеспечении, если бы это предложение было правдой. Я бы даже не был уверен, что «большинство разработчиков ...» было бы правдой. И да, это неутешительно. – Tom

+1

Хорошо, Том прав, я не уверен, что ты пытаешься сказать, но то, что ты на самом деле написал, явно ложно. (Также: подтолкнуть к гендерной включенности) – djechlin

+0

@Tom Я думаю, вы пропустили сарказм в этом ответе –

-1

Неизменяемая карта - это тип карты. Таким образом, оставляя возвращаемый тип Карты в порядке.

Чтобы гарантировать, что пользователи не изменяют возвращаемый объект, документация метода может описывать характеристики возвращаемого объекта.

-1

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

Взгляните на следующей статье:

andy gibson

+1

Ненужные обертки считаются вредными. – djechlin

+0

Вы правы, я бы тоже не использовал обертку, так как интерфейса достаточно. – WSimpson

+0

У меня такое чувство, что если бы это было правильно, то ImmutableMap уже реализовал ImmutableMapInterface, но я не понимаю этого технически. – djechlin

8

Определенно вернуть ImmutableMap, оправдание бытия:

  • Метод подписи (включая тип возвращаемого значения) должны быть самодокументирован. Комментарии похожи на обслуживание клиентов: если ваши клиенты должны полагаться на них, тогда ваш основной продукт неисправен.
  • Является ли что-то интерфейсом или классом, имеет значение только при его расширении или реализации. Учитывая экземпляр (объект), 99% клиентского кода времени не знают и не заботятся, является ли что-то интерфейсом или классом. Сначала я предположил, что ImmutableMap является интерфейсом. Только после того, как я нажал ссылку, я понял, что это класс.
34

В качестве вашего типа возврата вы должны иметь ImmutableMap. Map содержит методы, которые не поддерживаются реализацией ImmutableMap (например, put) и обозначены @deprecated в ImmutableMap.

Использование устаревших методов приведет к предупреждению компилятора &, большинство IDE будут предупреждать, когда люди пытаются использовать устаревшие методы.

Это предварительное предупреждение предпочтительнее, если у вас есть исключения в качестве первого намека на то, что что-то не так.

+1

Это, на мой взгляд, самый разумный ответ. Интерфейс карты - это контракт, и ImmutableMap явно нарушает важную часть. Использование Map в качестве возвращаемого типа в этом случае не имеет смысла. – TonioElGringo

+0

@TonioElGringo, что о 'Collections.immutableMap' то? – OrangeDog

+0

@TonioElGringo обратите внимание, что методы, которые ImmutableMap не реализует, явно помечены как необязательные в документации интерфейса https://docs.oracle.com/javase/7/docs/api/java/util/Map.html# поместить (K,% 20V). Таким образом, я думаю, что все же имеет смысл вернуть карту (с соответствующими javadocs). Несмотря на это, я решил явно вернуть ImmutableMap из-за причин, о которых говорилось выше. – AMT

10

, если я оставлю это так, другие разработчики могли бы пропустить тот факт, что этот объект является непреложным

Это верно, но другие разработчики должны проверить свой код и убедитесь, что она покрыта.

Тем не менее у вас есть еще 2 варианта решения этого:

  • Используйте Javadoc

    @return a immutable map 
    
  • Выберите описательное имя метода

    public Map<String, Integer> getImmutableMap() 
    public Map<String, Integer> getUnmodifiableEntries() 
    

    Для конкретного варианта использования вы можете назвать методы лучше. Например.

    public Map<String, Integer> getUnmodifiableCountByWords() 
    

Что еще можно сделать ?!

Вы можете вернуть

  • копию

    private Map<String, Integer> myMap; 
    
    public Map<String, Integer> foo() { 
        return new HashMap<String, Integer>(myMap); 
    } 
    

    Этот подход следует использовать, если вы ожидаете, что многие клиенты будут изменять карту и до тех пор, как карта содержит только несколько записей.

  • CopyOnWriteMap

    копию на сборниках записи, как правило, используется, когда вам приходится иметь дело с
    параллелизм. Но эта концепция также поможет вам в вашей ситуации, так как копия CopyOnWriteMap создает копию внутренней структуры данных в мутативной операции (например, добавляет, удаляет).

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

    Этот подход следует использовать, если вы ожидаете, что некоторые клиенты изменят карту.

    К сожалению, у java нет таких CopyOnWriteMap. Но вы можете найти третью сторону или реализовать ее самостоятельно.

Наконец, вы должны иметь в виду, что элементы на вашей карте могут быть изменены.

5

Это зависит от самого класса. Guava ImmutableMap не предназначен для неизменного представления в изменяемом классе. Если ваш класс неизменен и имеет некоторую структуру, которая в основном является ImmutableMap, тогда введите возвращаемый тип ImmutableMap. Однако, если ваш класс изменен, не делайте этого. Если у вас есть это:

public ImmutableMap<String, Integer> foo() { 
    return ImmutableMap.copyOf(internalMap); 
} 

Guava скопирует карту каждый раз. Это медленно. Но если internalMap уже был ImmutableMap, тогда все в порядке.

Если вы не ограничиваете свой класс для возвращения ImmutableMap, то вы могли бы вместо того, чтобы вернуться Collections.unmodifiableMap так:

public Map<String, Integer> foo() { 
    return Collections.unmodifiableMap(internalMap); 
} 

Обратите внимание, что это непреложный вид на карту. Если изменяется internalMap, то будет сохранена кешированная копия Collections.unmodifiableMap(internalMap). Однако я все же предпочитаю его для геттеров.

+1

Это хорошие моменты, но для полноты мне нравится [этот ответ] (http://stackoverflow.com/a/22636750/751579), в котором объясняется, что 'unmodifiableMap' только не поддается изменению владельцем ссылки - это вид и держатель карты поддержки могут изменить карту поддержки, а затем изменить вид. Так что это важное соображение. – davidbak

+0

@ Как сказал davidback, будьте очень осторожны с использованием ['Collections.unmodifiableMap'] (http://docs.oracle.ком/JavaSE/7/документы/API/Java/Util/Collections.html # unmodifiableMap (java.util.Map)). Этот метод возвращает * вид * на исходную карту, а не создает отдельный отдельный новый объект карты. Таким образом, любой другой код, ссылающийся на исходную карту, может изменить его, а затем держатель якобы немодифицируемой карты узнает, что карта действительно может быть изменена из-под них. Я сам по этому поводу был немного. Я научился либо возвращать копию Карты, либо использовать Guava 'ImmutableMap'. –

4

Это не отвечает на точный вопрос, но по-прежнему стоит рассмотреть вопрос о возврате карты вообще.Если карта неизменна, то основной метод должны быть предоставлен на основе ГЕТ (ключ):

public Integer fooOf(String key) { 
    return map.get(key); 
} 

Это делает API гораздо плотнее. Если карта на самом деле требуется, это можно было бы оставить до клиента по API, обеспечивая поток записей:

public Stream<Map.Entry<String, Integer>> foos() { 
    map.entrySet().stream() 
} 

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

public Optional<Integer> fooOf(String key) { 
    return Optional.ofNullable(map.get(key)); 
} 
Смежные вопросы