Параметр метода Java - получить только неизменяемый список

Я хочу обеспечить неизменность в одном из моих методов.

public record Student (String name, int rank, String school, String district){}

public String topRankingStudentName (List<Student> students) {
    // I want this incoming students to be Immutable List
    // How to enforce ?
}

Здесь я получаю students в виде списка. Я хочу, чтобы мои вызывающие абоненты передавали только Immutable collection. Я думал получить ImmutableList, но этот класс недоступен. Использование JDK-17

Как это сделать?

ImmutableList родом из Гуавы. Что вы хотите сделать со списком? Если вам нужно только выполнить итерацию, то Iterable<Student> — это то, что вам нужно (если только ваши клиенты снова не приведут итерацию к списку).
knittl 17.04.2023 14:50

Возможно, вы имеете в виду неизменяемый список вместо неизменного?

SquidXTV 17.04.2023 14:52

Кто использует этот метод? Только себя? Тогда это действительно имеет значение? Если другие, то вы можете задокументировать, что будут побочные эффекты (но если вы сами написали этот метод, решать вам). Итак, опять же, зачем пытаться навязать это?

OneCricketeer 17.04.2023 14:57

Этот метод будет внедрен в библиотеку и будет использоваться многими службами и проектами. Я хочу гарантировать, что эта библиотека не будет производить мутацию в будущем. Поэтому я хочу применить это в самом контракте метода (параметр)

Arun 17.04.2023 14:58

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

OneCricketeer 17.04.2023 15:01

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

tgdavies 17.04.2023 23:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не можете. List API не имеет ни общедоступного доступного неизменяемого подтипа (и такого не может быть в java, в лучшем случае это может быть договорная сделка без компилятора/среды выполнения), ни метода, который сообщает вам .

У вас есть несколько вариантов, и все они плохие.

использовать ImmutableList гуавы

Используйте класс ImmutableList из библиотеки guava. Обратите внимание, что это означает, что неизменяемый список, отличный от гуавы, также не будет разрешен, например. List.of("a", "b") неизменяемо, но не является com.google.common.collect.ImmutableList. У гуавы ужасный дизайн API в отношении использования типов гуавы на границе между отдельными модулями (из-за того, что они нарушают обратную совместимость с отказом, что нормально, но у гуавы нет отдельного варианта с обратной блокировкой только с классами, используемыми в общедоступных API, делает это страшно за это). Я настоятельно рекомендую не делать это таким образом.

Напишите метод, который проверяет во время выполнения

Вы можете попытаться вызвать .set(DUMMY_STUDENT), а затем снова удалить этого учащегося (или установить то же значение), сгенерировав исключение, если это удастся, и продолжить, если вы получите UnsupportedOperationException. Это немного хакерски (нет гарантии, что получение UOEx обязательно означает его неизменность). Используйте set, а не add, потому что Arrays.asList поддерживает set, а не add:

public String topRankingStudentName(List<? extends Student> students) {
  ensureImmutable(students);
  ....
}

/**
 * This method relies on the fact that the vast majority
 * of immutable list impls fail-fast when calling
 * mutable methods even if they would have no effect,
 * and that they fail-fast by throwing UnsupportedOperationException.
 */
public static <T> void ensureImmutable(List<T> list) {
  try {
    if (list.size() > 0) {
      list.set(0, list.get(0));
    } else {
      list.clear();
    }
  } catch (UnsupportedOperationException expected) {
    // This is good news - we want this to happen
    return;
  }
  throw new IllegalArgumentException("list should be immutable");
}

Я бы тоже не стал этого делать; в какой-то момент вам лучше указать в документации (javadoc), какие должны быть входные данные, вместо того, чтобы проверять все; есть способы обойти это, если кто-то намеренно хочет написать неработающий код. И если кто-то намеренно пытается все испортить, никакие проверки их не остановят. Вывод: Написание проверок, которые выходят за рамки попыток остановить честное неправомерное использование API и пытаются остановить преднамеренное неправомерное использование API, вряд ли сработает, поэтому не пытайтесь.

Просто напишите это в документах

Просто укажите в документах, что список нельзя изменять после передачи, и порекомендуйте вызывающим абонентам использовать List.copyOf(someMutableList), если они не могут гарантировать это.

Сделать копию

Вы всегда можете написать:

public String topRankingStudentName(List<? extends Student> studentsIn) {
  List<? extends Student> students = List.copyOf(studentsIn);
 // Code here
}

Бывают ситуации, когда этот код «умный» и на самом деле не делает копии, особенно если studentsIn, например. сделанный с помощью List.of(), приведенное выше не копирует (поскольку код API List знает, что в этом нет смысла). Однако это не делает то, что вы хотите (то есть: убедитесь, что вызывающие абоненты проходят только в неизменяемых списках), это просто решает другую проблему (а именно: вы не хотите, чтобы ваш код делал шаткие вещи, потому что список изменяется в середине бега). Только вы можете определить, достаточно ли хорош здесь этот альтернативный эффект.

Обратите внимание, что это также добавляет требование, чтобы предоставленный список не содержал значений null.

Взломать по-другому

Учитывая, что List.copyOf является «умным» и возвращает аргумент напрямую, если это неизменяемый список из основных библиотек (это не будет делать для различных неизменяемых списков, таких как Arrays.asList(someZeroLenArray) или экземпляры ImmutableList гуавы) - вы можете использовать это:

public String topRankingStudentName(List<? extends Student> students) {
  /* Check immutable */ {
    List<? extends Students> s = List.copyOf(students);
    if (s != students) throw new IllegalArgumentException("students is not immutable");
  }

  // code here
}

Однако исключать различные неизменяемые списки, не основанные на List.of, это не очень хорошо.

Заключительные примечания

Большинство из этих хаков «работают» в соответствии с оговорками, которые они описывают, но большинство из них на самом деле не программируются против спецификации, а просто против известного на данный момент поведения. У меня нет особых причин полагать, что это поведение когда-либо изменится, но если это произойдет, вы не сможете сообщать об ошибках, например, команда OpenJDK, что они изменили принцип работы List.copyOf.

По этой и еще нескольким причинам я настоятельно рекомендую использовать варианты «сделать копию» или «просто задокументировать».

Также обратите внимание, что, учитывая (предполагается) неизменяемость входных данных, List<? extends Student> является правильным типом, а не List<Student>. Первый также позволяет людям проходить List<SomeSpecificStudentSubC;ass>, и единственное, что List<? extends Student> не может сделать из того, что может сделать List<Student>, — это «добавлять в него что-то» (это не означает, что список неизменяем, просто ваш код не может добавить ничего, кроме null , он все еще может удалять вещи), которые, учитывая, что вам нужен неизменяемый ввод, вас не интересуют.

Люблю это решение .. List<? extends Students> s = List.copyOf(students); . Спасибо. это очень полезно

Arun 17.04.2023 15:28

Другие вопросы по теме