Чи краще повернути ImmutableMap або карту?


90

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

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

Подумавши про це деякий час, я вирішив, що немає жодних причин змінювати цю Карту після її створення. Таким чином, я хотів би повернути ImmutableMap .

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

Чи повинен я залишити тип повернення як загальну карту, чи слід вказати, що я повертаю ImmutableMap?

З одного боку, саме для цього були створені інтерфейси; щоб приховати деталі реалізації.
З іншого боку, якщо я залишу це так, інші розробники можуть пропустити той факт, що цей об’єкт незмінний. Таким чином, я не досягну основної мети незмінних об'єктів; щоб зробити код більш зрозумілим, мінімізуючи кількість об’єктів, які можуть змінюватися. Навіть найгірше, через деякий час хтось може спробувати змінити цей об’єкт, і це призведе до помилки виконання (компілятор про це не попередить).


2
Я підозрюю, що хтось врешті позначить це питання як занадто відкрите для аргументів. Чи можете ви поставити більш конкретні запитання замість "WDYT?" і "це нормально ...?" Наприклад, "чи є якісь проблеми чи міркування щодо повернення звичайної карти?" і "які альтернативи існують?"
попіл

Що робити, якщо пізніше ви вирішите, що це дійсно має повернути змінну карту?
user253751

3
@immibis ха-ха, або Список з цього приводу?
djechlin

3
Ви дійсно хочете запевнити залежність гуави у своєму API? Ви не зможете змінити це, не порушивши зворотну сумісність.
user2357112 підтримує Моніку

1
Я думаю, що питання занадто схематичне, і багато важливих деталей відсутні. Наприклад, чи ви вже маєте Гуаву як (видиму) залежність. Навіть якщо він у вас уже є, фрагменти коду є пезудокодом і не передають того, чого ви насправді хочете досягти. Ви б точно не писали return ImmutableMap.of(somethingElse). Натомість ви зберегли б поле ImmutableMap.of(somethingElse)як поле та повернули лише це поле. Все це впливає на дизайн тут.
Marco13

Відповіді:


70
  • Якщо ви пишете публічний API, і незмінність є важливим аспектом вашого дизайну, я точно визначив би це, якщо назва методу чітко означає, що повернута карта буде незмінною, або повернення конкретного типу карта. На мою думку, згадувати це в javadoc недостатньо.

    Оскільки ви, очевидно, використовуєте реалізацію Guava, я подивився документ, і це абстрактний клас, тому він дає вам трохи гнучкості щодо фактичного, конкретного типу.

  • Якщо ви пишете внутрішній інструмент / бібліотеку, стає набагато прийнятнішим просто повернути звичайний текст Map. Люди будуть знати про внутрішні частини коду, який вони викликають, або принаймні матимуть легкий доступ до нього.

Я б дійшов висновку, що явне - це добре, не залишайте речі на волю випадку.


1
Один додатковий інтерфейс, один додатковий абстрактний клас і один клас, який розширює цей абстрактний клас. Це занадто багато клопоту для такої простої речі, як запитує OP, тобто якщо метод повинен повертати Mapтип інтерфейсу або ImmutableMapтип реалізації.
fps

20
Якщо ви маєте на увазі Guava ImmutableMap, порада команди Guava полягає в тому, щоб розглянути ImmutableMapінтерфейс із семантичними гарантіями, і вам слід повернути цей тип безпосередньо.
Луїс Вассерман,

1
@LouisWasserman мм, не бачив, що питання дає посилання на реалізацію Гуави. Тоді я
видалю

3
Я погоджуюсь з Луїсом, якщо ви маєте намір повернути карту, яка є незмінним об'єктом, переконайтесь, що об'єкт повернення бажано такого типу. Якщо з якихось причин ви хочете приховати той факт, що це незмінний тип, визначте власний інтерфейс. Залишити це на карті як оманливе.
WSimpson

1
@WSimpson Я вважаю, що саме про це говорить моя відповідь. Луї говорив про якийсь фрагмент, який я додав, але я видалив його.
Дічі

38

Ви повинні мати ImmutableMapяк тип повернення. Mapмістить методи, які не підтримуються реалізацією ImmutableMap(наприклад put) і позначені @deprecatedв ImmutableMap.

Використання застарілих методів призведе до попередження компілятора, і більшість середовищ розробки ID попереджатимуть, коли люди намагатимуться використовувати застарілі методи.

Це попереджене попередження краще, ніж винятки під час виконання, як перший натяк на те, що щось не так.


1
Це, на мій погляд, найбільш розумна відповідь. Інтерфейс Map - це контракт, і ImmutableMap явно порушує важливу його частину. Використання Map як типу повернення в цьому випадку не має сенсу.
TonioElGringo

@TonioElGringo що Collections.immutableMapтоді?
OrangeDog

@TonioElGringo, будь ласка, зауважте, що методи, які ImmutableMap не реалізує, явно позначені як необов’язкові в документації до інтерфейсу docs.oracle.com/javase/7/docs/api/java/util/… . Таким чином, я думаю, що все одно може мати сенс повернути Map (з належними javadocs). Навіть незважаючи на те, що я вирішив повернути ImmutableMap явно, з причин, про які йшлося вище.
AMT

3
@TonioElGringo це нічого не порушує, ці методи необов’язкові. Як і в List, немає жодної гарантії, яка List::addє дійсною. Спробуйте Arrays.asList("a", "b").add("c");. Немає жодних ознак того, що він вийде з ладу під час виконання, проте це відбувається. Принаймні Гуава дає вам голову.
njzk2

14

З іншого боку, якщо я залишу це так, інші розробники можуть пропустити той факт, що цей об’єкт незмінний.

Ви повинні згадати про це в javadocs. Розумієте, розробники їх читають.

Таким чином, я не досягну основної мети незмінних об'єктів; щоб зробити код більш чітким, мінімізуючи кількість об’єктів, які можуть змінюватися. Навіть найгірше, через деякий час хтось може спробувати змінити цей об’єкт, і це призведе до помилки виконання (компілятор про це не попередить).

Жоден розробник не публікує свій код неперевіреним. І коли він протестує його, він отримує виняток, де він не тільки бачить причину, але також файл і рядок, де він намагався записати на незмінну карту.

Зверніть увагу, однак, Mapнезмінним буде лише сам, а не об’єкти, які він містить.


10
"Жоден розробник не публікує свій код неперевіреним." Ви хочете зробити ставку на це? Якби це речення відповідало дійсності, було б набагато менше (моїх припущень) помилок у публічному програмному забезпеченні. Я навіть не був би впевнений, що "більшість розробників ..." буде правдою. І так, це невтішно.
Том,

1
Гаразд, Том правий, я не впевнений, що ти хочеш сказати, але те, що ти насправді написав, явно неправдиве. (Також: підштовхування до гендерної інклюзивності)
djechlin

@Tom Я думаю, ви пропустили сарказм у цій відповіді
капітан Фогетті

10

якщо я залишу це так, інші розробники можуть пропустити той факт, що цей об'єкт незмінний

Це правда, але інші розробники повинні протестувати свій код і переконатися, що він охоплений.

Тим не менше, у вас є ще 2 варіанти вирішення цієї проблеми:

  • Використовуйте Javadoc

    @return a immutable map
  • Виберіть назву описового методу

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

    Для конкретного випадку ви можете навіть назвати методи краще. Напр

    public Map<String, Integer> getUnmodifiableCountByWords()

Що ще ти можеш зробити ?!

Ви можете повернути a

  • копію

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

    Цей підхід слід використовувати, якщо ви очікуєте, що багато клієнтів модифікують карту і якщо карта містить лише декілька записів.

  • CopyOnWriteMap

    Копіювання на колекції записів зазвичай використовується, коли доводиться мати справу з
    паралельністю. Але концепція також допоможе вам у вашій ситуації, оскільки CopyOnWriteMap створює копію внутрішньої структури даних на мутативній операції (наприклад, додавання, видалення).

    У цьому випадку вам потрібна тонка обгортка навколо вашої карти, яка делегує всі виклики методів на базову карту, крім мутативних операцій. Якщо викликається мутативна операція, вона створює копію базової карти, і всі подальші виклики будуть делеговані цій копії.

    Цей підхід слід використовувати, якщо ви очікуєте, що деякі клієнти змінять карту.

    На жаль, у Java такого немає CopyOnWriteMap. Але ви можете знайти третю сторону або реалізувати її самостійно.

Нарешті, слід пам’ятати, що елементи на вашій карті все ще можуть змінюватися.


8

Обов’язково повертає ImmutableMap, виправданням є:

  • Підпис методу (включаючи тип повернення) повинен бути самодокументованим. Коментарі схожі на обслуговування клієнтів: якщо вашим клієнтам потрібно покластися на них, то ваш основний продукт несправний.
  • Чи є щось інтерфейсом чи класом, це важливо лише при його розширенні або реалізації. Враховуючи екземпляр (об’єкт), 99% часу клієнтського коду не знатиме чи піклуватися, чи є це щось інтерфейсом чи класом. Спочатку я припустив, що ImmutableMap - це інтерфейс. Лише натиснувши посилання, я зрозумів, що це клас.

5

Це залежить від самого класу. Guava's ImmutableMapне має на меті бути незмінним видом у мінливий клас. Якщо ваш клас незмінний і має якусь структуру, яка в основному є an ImmutableMap, то зробіть тип return 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). Однак я все-таки віддаю перевагу цьому для здобувачів.


1
Це хороші моменти, але для повноти мені подобається така відповідь, яка пояснює, що unmodifiableMapнеможливо змінити лише власником посилання - це вигляд, і власник допоміжної карти може змінити підтримуючу карту, а потім вигляд зміниться. Тож це є важливим фактором.
davidbak

@ Як зауважив davidback, будьте дуже обережні з використанням Collections.unmodifiableMap. Цей метод повертає подання на вихідну карту, а не створює окремий окремий новий об’єкт карти. Отже, будь-який інший код, що посилається на оригінальну карту, може її змінити, і тоді власник нібито немодифікованої карти засвоює жорсткий шлях до того, що карту справді можна змінити з-під них. Я сам зазнав цього. Я навчився або повертати копію карти, або користуватися гуавою ImmutableMap.
Василь Бурк,

5

Це не відповідає на точне запитання, але все ж варто подумати, чи взагалі слід повертати карту. Якщо карта незмінна, то основний метод, який потрібно надати, базується на get (ключ):

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

-1

Незмінна карта - це тип Карти. Тож залишити тип повернення Map - це нормально.

Щоб користувачі не модифікували повернутий об’єкт, документація методу може описувати характеристики поверненого об’єкта.


-1

Це, безперечно, питання думки, але кращою ідеєю тут є використання інтерфейсу для класу карти. Цей інтерфейс не повинен прямо заявляти, що він незмінний, але повідомлення буде однаковим, якщо ви не виставите в інтерфейсі жодного методу встановлення батьківського класу.

Погляньте на наступну статтю:

Енді Гібсон


1
Непотрібні обгортки вважаються шкідливими.
djechlin

Ви маєте рацію, я б також не використовував обгортку тут, оскільки інтерфейсу достатньо.
WSimpson

У мене відчуття, якщо це було правильно, тоді ImmutableMap вже реалізував би ImmutableMapInterface, але я технічно цього не розумію.
djechlin

Справа не в тому, що задумали розробники Guava, а в тому, що цей розробник хотів би інкапсулювати архітектуру, а не викривати використання ImmutableMap. Одним із способів зробити це було б використання інтерфейсу. Використовувати обгортку, як ви вказали, не потрібно, а тому не рекомендується. Повернення карти небажано, оскільки вводить в оману. Отже, якщо метою є інкапсуляція, то найкращим варіантом буде Інтерфейс.
WSimpson

Недоліком повернення чогось, що не розширює (або не реалізує) Mapінтерфейс, є те, що ви не можете передати його будь-якій функції API, очікуючи a, не передаючи Mapйого. Сюди входять всі види конструкторів копій інших типів карт. На додаток до цього, якщо змінність лише "прихована" інтерфейсом, ваші користувачі завжди можуть обійти це, перекинувши і зламавши ваш код таким чином.
Халк,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.