Я выполняю ниже фрагмент кода
System.out.println(List.of(1, 2).getClass());
System.out.println(List.of(1, 2, 3).getClass());
вывод этого кода:
class java.util.ImmutableCollections$List2
class java.util.ImmutableCollections$ListN
Я ожидаю, что java.util.ImmutableCollections$List3 будет выводом для второго оператора, потому что есть метод of(), который принимает три параметра: Почему Java создает ImmutableCollections$ListN, но не ImmutableCollections$List3?
Отредактировано: это вопрос Java-9. Всего в интерфейсе List имеется 11 перегруженных методов of (), каждый из которых принимает переменное количество параметров от нуля до 10, а одиннадцатый - varargs для обработки N списка. Поэтому я ожидаю, что от List0 до List10 будут реализованы первые 10 перегруженных методов, но он возвращает ListN с тремя параметрами. Да, это детали реализации, но мне просто любопытно узнать больше об этом.
Вы знаете, что Зачем вызов в первом случае возвращает ImmutableCollections$List2? Чего вы ожидаете, вызывая его с 200 аргументами? Вы (ошибочно) ожидаете возврата ImmutableCollections$List200?
Похоже, создатели Java решили предоставить только классы ImmutableCollections.List0, ImmutableCollections.List1, ImmutableCollections.List2 и ImmutableCollections.ListN.
FWIW, в Java 11 List0, List1 и List2 исчезнут. Будут только List12 и ListN. Ссылка: bugs.openjdk.java.net/browse/JDK-8193128
@StefanZobel Right, и ListN будет использоваться для пустого списка




Оба возвращаются классы. т.е. существует отдельный класс для ImmutableCollections$List2 и ImmutableCollections$ListN ($ указывает на внутренний класс)
Это деталь реализации, и (предположительно) List2 существует (возможно) по какой-то причине оптимизации. Я подозреваю, что если вы посмотрите на исходный код (через свою IDE или подобное), вы увидите два разных внутренних класса.
Ни ImmutableCollections$List2, ни ImmutableCollections$ListN не генерируются во время выполнения. Уже написано четыре класса:
static final class List0<E> extends AbstractImmutableList<E> { ... }
static final class List1<E> extends AbstractImmutableList<E> { ... }
static final class List2<E> extends AbstractImmutableList<E> { ... }
static final class ListN<E> extends AbstractImmutableList<E> { ... }
Начиная с of(E e1, E e2, E e3) и до of(E e1, ..., E e10), будет создан экземпляр ImmutableCollections.ListN<>.
Why java creating
ImmutableCollections$ListNbut notImmutableCollections$List3?
Вероятно, дизайнеры решили, что корпуса 3 и N похожи и писать отдельный класс для 3 не стоит. Судя по всему, они не получат достаточных преимуществ от $List3, $List7, $List10, как от версий $List0, $List1 и $List2. Они специально оптимизированы.
В настоящее время 4 класса охватывают 10 методов. Если бы они решили добавить еще несколько методов (например, с 22 аргументами), эти 4 класса все равно остались бы. Представьте, что вы пишете 22 класса для 22 методов. Насколько это повлечет за собой ненужное дублирование кода?
и что касается вашего последнего пункта, вы должны где-нибудь провести черту. Если бы они добавили List3, то почему бы не List4, 5, 6 ...?
Не могли бы вы пояснить, почему это не дубликат ранее отмеченного вопроса? Ответ здесь ничего не добавляет к ответу ZhekaKozlov в существующей ветке.
@nullpointer, OP уже знает, что типы ListX существуют, и их экземпляры возвращаются из List.of
ListN - универсальная версия. List2 - это оптимизированная реализация. Для списка из трех элементов такой оптимизированной реализации не существует.
В настоящее время существуют * оптимизированные версии для списков и наборов с нулевым, одним и двумя элементами. List0, List1, List2, Set0 и т. д.
Также существует оптимизированная реализация для пустой карты, Map0, и для карты, содержащей одну пару ключ-значение, Map1.
Обсуждение того, как эти реализации могут обеспечивать улучшения производительности, можно увидеть в JDK-8166365.
*bear in mind this is an implementation detail which may be subject to change, and actually is due to change fairly soon
«имейте в виду, что это деталь реализации, которая может быть изменена». Да, это изменится. Они собираются заменить List1 и List2 одной реализацией List12: mail.openjdk.java.net/pipermail/core-libs-dev/2018-March/…
@ZhekaKozlov Очень интересно. Спасибо! Я отредактировал это в своем ответе
Как правильно заметил Джон Скит, это деталь реализации. В спецификации List.of сказано, что он возвращает неизменяемый список, и это все, что имеет значение.
Разработчики, вероятно, решили, что они могут предоставить эффективные реализации одноэлементных (List1) и двухэлементных списков (List2), и что все другие размеры могут обрабатываться одним типом (ListN). Это может измениться в какой-то момент в будущем - может быть, в какой-то момент они представят List3, а может, и нет.
Согласно правилам полиморфизма и инкапсуляции, все это не имеет значения. Пока возвращаемый объект является списком, вам не следует беспокоиться о его фактической реализации.
Основная причина иметь несколько различных частных реализаций List - это экономия места.
Рассмотрим реализацию, в которой элементы хранятся в массиве. (По сути, это то, что делает ListN.) В Hotspot (64-разрядная версия со сжатыми указателями объектов, каждые 4 байта) каждому объекту требуется 12-байтовый заголовок. Объект ListN имеет одно поле, содержащее массив, всего 16 байтов. Массив - это отдельный объект, поэтому у него есть еще 12-байтовый заголовок плюс 4-байтовая длина. Это еще 16 байт, не считая фактически сохраненных элементов. Если мы храним два элемента, они занимают 8 байтов. В результате получается 40 байт для хранения двухэлементного списка. Это довольно много накладных расходов!
Если бы мы должны были хранить элементы небольшого списка в полях, а не в массиве, этот объект имел бы заголовок (12 байтов) плюс два поля (8 байтов), всего 20 байтов - половину размера. Для небольших списков есть значительная экономия за счет хранения элементов в полях самого объекта List, а не в массиве, который является отдельным объектом. Это то, что делала старая реализация List2. Недавно его заменила реализация List12, которая может хранить списки из одного или двух элементов в полях.
Теперь в API есть 12 перегруженных методов List.of(): от нуля до десяти фиксированных аргументов плюс переменные. Разве не должны быть соответствующие реализации List0 - List10 и ListN?
Может быть, но не обязательно. Ранний прототип этих реализаций имел оптимизированные реализации небольшого списка, привязанные к API. Таким образом, методы of() с нулевым, одним и двумя фиксированными аргументами создали экземпляры List0, List1 и List2, а метод varargs List.of() создал экземпляр ListN. Это было довольно просто, но было довольно ограничительно. Мы хотели иметь возможность добавлять, удалять или переупорядочивать реализации по желанию. Менять API значительно сложнее, поскольку мы должны оставаться совместимыми. Таким образом, мы решили разделить вещи так, чтобы количество аргументов в API в значительной степени не зависело от реализации, созданной ниже.
В JDK 9 мы закончили с 12 перегрузками в API, но только с четырьмя реализациями: реализация на основе поля, содержащая 0, 1 и 2 элемента, и реализация на основе массива, содержащая произвольное число. Почему бы не добавить больше реализаций на основе полей? Уменьшение отдачи и раздувание кода. В большинстве списков мало элементов, и количество экземпляров списков уменьшается по экспоненте по мере увеличения количества элементов. Экономия места становится относительно меньшей по сравнению с реализацией на основе массива. Затем нужно поддерживать все эти дополнительные реализации. Либо их нужно было вводить прямо в исходный код (громоздкий), либо мы перешли на схему генерации кода (сложную). Ни то, ни другое не казалось оправданным.
Наш гуру производительности запуска Клаас Редестад провел некоторые измерения и обнаружил, что реализация списка меньше была ускорена. Причина - мегаморфная рассылка. Вкратце, если JVM компилирует код для виртуального сайта вызова и может определить, что вызываются только одна или две разные реализации, она может хорошо это оптимизировать. Но если есть много разных реализаций, которые можно вызвать, они должны пройти более медленный путь. (Подробнее о Черная магия см. В этой статье.)
Что касается реализаций списка, оказывается, что мы можем обойтись меньшим количеством реализаций, не теряя много места. Реализации List1 и List2 могут быть объединены в реализацию List12 с двумя полями, при этом второе поле имеет значение NULL, если имеется только один элемент. Нам нужен только один список нулевой длины, поскольку он неизменяемый! Для списка нулевой длины мы можем избавиться от List0, просто используя ListN с массивом нулевой длины. Он больше, чем старый экземпляр List0, но нас это не волнует, так как есть только один из них.
Эти изменения только что вошли в основную ветку JDK 11. Поскольку API полностью отделен от реализаций, проблем с совместимостью нет.
Есть дополнительные возможности для будущих улучшений. Одна из возможных оптимизаций - объединить массив с концом объекта, чтобы объект имел фиксированную часть и часть переменной длины. Это позволит избежать необходимости в заголовке объекта массива и, вероятно, улучшит локальность ссылки. Другая потенциальная оптимизация связана с типами значений. С типами значений можно полностью избежать выделения кучи, по крайней мере, для небольших списков. Конечно, все это в высшей степени умозрительно. Но если в JVM появятся новые функции, мы сможем воспользоваться ими в реализациях, поскольку они полностью скрыты за API.
Это деталь реализации - стоит спросить себя, зачем вам использовать ожидать в частности. В будущей версии второй может возвращать List3 или первый может возвращать ListN.