Лучше ли возвращать ImmutableMap или карту?

Скажем, я пишу метод, который должен возвращать Map. Например:

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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Тем не менее у вас есть еще два варианта решения этой проблемы:

  • Использовать 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

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

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

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

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

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

Ответ 5

Определенно возвращаем ImmutableMap, оправдание которого:

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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));
}

Ответ 8

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

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

Ответ 9

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

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

andy gibson