Я хочу обеспечить неизменность в одном из моих методов.
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
Как это сделать?
Возможно, вы имеете в виду неизменяемый список вместо неизменного?
Кто использует этот метод? Только себя? Тогда это действительно имеет значение? Если другие, то вы можете задокументировать, что будут побочные эффекты (но если вы сами написали этот метод, решать вам). Итак, опять же, зачем пытаться навязать это?
Этот метод будет внедрен в библиотеку и будет использоваться многими службами и проектами. Я хочу гарантировать, что эта библиотека не будет производить мутацию в будущем. Поэтому я хочу применить это в самом контракте метода (параметр)
Но если вы фактически реализуете метод, то проверка компилятора будет после того, как вы уже написали код внутри... Вам понадобится статический анализ кода, чтобы определить, изменили ли вы параметр/коллекцию или нет
Вместо того, чтобы пытаться сделать это на уровне типа, я бы подумал о добавлении модульного теста, который вызывает topRankingStudentName и проверяет, остается ли содержимое списка одинаковым после вызова.




Вы не можете. List API не имеет ни общедоступного доступного неизменяемого подтипа (и такого не может быть в java, в лучшем случае это может быть договорная сделка без компилятора/среды выполнения), ни метода, который сообщает вам .
У вас есть несколько вариантов, и все они плохие.
Используйте класс 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); . Спасибо. это очень полезно
ImmutableListродом из Гуавы. Что вы хотите сделать со списком? Если вам нужно только выполнить итерацию, тоIterable<Student>— это то, что вам нужно (если только ваши клиенты снова не приведут итерацию к списку).