Статический метод Java List of ()

Я выполняю ниже фрагмент кода

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 с тремя параметрами. Да, это детали реализации, но мне просто любопытно узнать больше об этом.

Это деталь реализации - стоит спросить себя, зачем вам использовать ожидать в частности. В будущей версии второй может возвращать List3 или первый может возвращать ListN.

Jon Skeet 21.03.2018 11:21

Вы знаете, что Зачем вызов в первом случае возвращает ImmutableCollections$List2? Чего вы ожидаете, вызывая его с 200 аргументами? Вы (ошибочно) ожидаете возврата ImmutableCollections$List200?

luk2302 21.03.2018 11:22

Похоже, создатели Java решили предоставить только классы ImmutableCollections.List0, ImmutableCollections.List1, ImmutableCollections.List2 и ImmutableCollections.ListN.

Thomas Kläger 21.03.2018 11:30

FWIW, в Java 11 List0, List1 и List2 исчезнут. Будут только List12 и ListN. Ссылка: bugs.openjdk.java.net/browse/JDK-8193128

Stefan Zobel 23.03.2018 00:05

@StefanZobel Right, и ListN будет использоваться для пустого списка

ZhekaKozlov 28.03.2018 12:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
5
2 089
5

Ответы 5

Оба возвращаются классы. т.е. существует отдельный класс для 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$ListN but not ImmutableCollections$List3?

Вероятно, дизайнеры решили, что корпуса 3 и N похожи и писать отдельный класс для 3 не стоит. Судя по всему, они не получат достаточных преимуществ от $List3, $List7, $List10, как от версий $List0, $List1 и $List2. Они специально оптимизированы.

В настоящее время 4 класса охватывают 10 методов. Если бы они решили добавить еще несколько методов (например, с 22 аргументами), эти 4 класса все равно остались бы. Представьте, что вы пишете 22 класса для 22 методов. Насколько это повлечет за собой ненужное дублирование кода?

и что касается вашего последнего пункта, вы должны где-нибудь провести черту. Если бы они добавили List3, то почему бы не List4, 5, 6 ...?

Michael 21.03.2018 11:42

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

Naman 22.03.2018 08:30

@nullpointer, OP уже знает, что типы ListX существуют, и их экземпляры возвращаются из List.of

Andrew Tobilko 22.03.2018 10:59

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 22.03.2018 06:13

@ZhekaKozlov Очень интересно. Спасибо! Я отредактировал это в своем ответе

Michael 22.03.2018 09:22

Как правильно заметил Джон Скит, это деталь реализации. В спецификации 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.

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