openjdk.ruOpenJDK на русском

JEP draft: Type operator expressions in the JVM

Выражения операторов типов в JVM

ОтветственныйJohn Rose
ТипFeature
ОбластьJDK
СтатусDraft
Компонентhotspot / runtime
Создан2018/06/13 06:48
Обновлён2024/09/25 17:27
Задача8204937

ЧЕРНОВИК ЧЕРНОВИК ЧЕРНОВИК

Аннотация

Расширить пространство дескрипторов типов JVM, включив в него операторы типов — символические ссылки на типы, создаваемые фабриками. Это отделимый компонент шаблонных классов.

Цели

Позволить дескрипторам типов (для методов, полей и констант) в JVM проводить новые различия между типами, которых ещё нет в системе классов, примитивов и массивов. Поддержать будущие стратегии трансляции, которым нужно различать разные использования одного и того же базового типа JVM или нужен способ задавать входные данные для фабрики классов или фабрики species шаблона.

Что не является целью

Эта работа — низкоуровневая точка расширения (hook) в VM, как invokedynamic, а не возможность языка, как лямбды. Поэтому в ней не будет предложено никакого конкретного механизма для представления параметризованных типов; она лишь даст необходимую «точку расширения», чтобы именовать такие типы. Она не даст нового способа определять классы; она лишь даст способ связать такие классы с публичным символическим дескриптором. Она не определяет ни возможностей языка, ни стратегий трансляции. Она не будет пытаться расширить текущий синтаксис статических generic-сигнатур (JVMS 4.7.9.1), вступить с ним в конфликт или рационализировать его.

Критерии успеха

Можно будет создавать экспериментальные стратегии трансляции, которые различают List<Integer> и List<String> в class-файлах. Экспериментальные механизмы шаблонизации классов смогут создавать species, которые можно обозначить дескрипторами типов JVM. Разработчики возможностей языка и стратегий трансляции смогут менять кодировки новых типов уровня исходного кода, изменяя bootstrap-метод, а не базовую логику JVM. Доказательства безопасности будет проще строить, поскольку операторы типов — это «чёрный ящик», отделённый от сложных деталей шаблонов и других продвинутых возможностей языка. Экспериментальные стратегии миграции можно будет проверять, не реализуя полностью новые возможности языка, поскольку новые типы-заглушки легко вводятся простыми изменениями в javac.

Мотивация

Дескрипторы, способные обозначать сложные экземпляры типов, такие как List<int> или List<ComplexDouble>, — необходимый компонент «reified generics», которые, в свою очередь, являются одной из целей проекта Valhalla. Если value type должен «кодироваться как класс, а работать как int», то, по-видимому, необходимо уметь обозначать типы-контейнеры, специализированные под этот value type, а не стираемые до Object, как ссылочный тип.

Описание

Мы расширим фундаментальный синтаксис дескрипторов полей JVM — один раз для всех будущих схем типов (мы надеемся!). Синтаксис позволит модифицировать любой отдельный дескриптор типа необязательным суффиксом, который ограничивает исходный дескриптор типа произвольным (ad hoc) программируемым образом. Сочетание исходного дескриптора типа и суффикса называется выражением оператора типа.

Разрешаемые семантические элементы этого выражения:

  • тип-носитель: исходный дескриптор типа (до суффикса)
  • имя оператора типа: разрешённое имя класса и/или простой идентификатор
  • аргументы типа: один или несколько дескрипторов типов и/или другие константы

Все перечисленные семантические элементы необязательны; любой из них можно опустить. Если имя оператора типа опущено, оно выводится из типа-носителя, как в случае шаблонного класса, верхний тип которого — сам неспециализированный класс. Если тип-носитель опущен, он по определению равен Object — обычному носителю нетипизированных значений в JVM.

Вот, например, некоторые возможные сценарии использования выражений операторов типов:

  • reified generics: тип-носитель — Map, имя оператора типа опущено, аргументы — int и String. Всё выражение обозначает Map<int,String>.

  • wildcard-типы: тип-носитель — List, имя оператора типа опущено, аргумент — символ (не тип) ?. Всё выражение обозначает List<?>, в отличие от сырого List. Поскольку wildcard-типы — частный случай понятия «экзистенциальные типы», примечательно, что выражения операторов типов дают способ обернуть любой ограниченный тип (тип-носитель) в символически помеченный экзистенциальный тип.

  • ссылки, не допускающие null: тип-носитель — String, оператор типа — ! (или java/lang/NotNull) без аргументов. Всё выражение обозначает String! — ссылку на строку, не допускающую null.

  • значения, допускающие null: опущенный тип-носитель по умолчанию равен Object, оператор типа — ? (или java/lang/Nullable) с одним аргументом int. Всё выражение обозначает int? — целое число, допускающее null.

  • reified-пересечения: тип-носитель — некоторый интерфейс I, оператор типа — & с аргументом типа J. Всё выражение обозначает тип-пересечение I&J.

  • reified-объединения: опущенный тип-носитель по умолчанию равен Object, имя оператора типа — | с двумя или более аргументами типа I, J. Всё выражение обозначает тип-пересечение I|J.

  • массивы фиксированного размера: тип-носитель — тип массива double[], имя оператора типа — Array.length с одним аргументом 5. Всё выражение обозначает double[5] — массив с ограниченной длиной.

  • ограничения диапазона: тип-носитель — примитивный тип int, имя оператора типа — Integer.interval с аргументами ge и 0. Всё выражение обозначает int, ограниченный неотрицательными значениями.

  • маркеры типов null и notreached: опущенный тип-носитель по умолчанию равен Object, оператор типа — java/lang/Null или java/lang/NotReached. Всё выражение обозначает ссылку, которая обязана быть null, или ссылку, которая никогда не передаётся своему потребителю (то есть ограничение всегда нарушается).

Конкретная грамматика таких дескрипторов, включая новые продукции, будет примерно такой:

MethodType: '(' (FieldType)* ')' (FieldType | 'V')
FieldType: PrimitiveType | ArrayType | ObjectType | *TypeExpr
PrimitiveType: 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z'
ArrayType: '[' (PrimitiveType | ArrayType | ObjectType)
ObjectType: 'L' ClassName ';'
*TypeExpr: TypeCarrier '/' (TypeOpName)? (';' | '[' (TypeArg)+ ']' )
*TypeCarrier: FieldType | `L`
*TypeOpName: '$' Identifier | ('L' ClassName) (';' '$' Identifier)?
*TypeArg: FieldType | MethodType | NameArg | NumberArg
*NumberArg: ('-')? DigitNotZero (Digit)* ';' | '0' ';'
*NameArg: '$' Identifier ';'
Identifier: (any character except '.' ';' '[' '/' '<' '>' ':')*

Эта грамматика построена на слегка отредактированной версии грамматики из JVMS 4.3. Новые продукции, поддерживающие операторы типов, — это TypeExpr, TypeCarrier, TypeOpName, TypeArg, NumberArg и NameArg. (Они отмечены звёздочкой.) Продукция для Identifier взята из JVMS 4.7.9.1.

TypeExpr обозначает новый тип, который JVM считает отличным от любого другого типа с другой строкой дескриптора, включая примитивы, массивы, классы и другие TypeExpr.

Синтаксические компоненты TypeExpr — это TypeCarrier, TypeOpName и последовательность из нуля или более TypeArg. Они обозначают разрешаемые семантические компоненты разрешённого выражения оператора типа — соответственно тип-носитель, имя оператора типа и аргументы типа.

Два TypeExpr с точно одинаковым написанием обозначают один и тот же тип. Любой FieldType, являющийся собственным префиксом другого FieldType, является собственным супертипом более длинного FieldType. Кроме этих отношений, JVM не признаёт никаких эквивалентностей или отношений между типами с по-разному записанными TypeExpr.

В частности, верификатор считает каждое отдельное выражение оператора типа обобщённым типом-«чёрным ящиком», который начинается с типа-носителя и ограничивает его каким-то образом, неизвестным верификатору.

Таким образом, верификатор разрешит неявно преобразовывать значения типа оператора типа в его тип-носитель или в любые супертипы его типа-носителя, но не разрешит преобразовывать такие значения ни в какой другой тип. Кроме того, верификатор не будет неявно преобразовывать тип-носитель в тип оператора типа, построенный поверх этого типа-носителя; такие преобразования должны выполняться явным исполнением байт-кода.

Вот несколько примеров синтаксиса дескрипторов, содержащих выражения операторов типов (вместе с некоторыми гипотетическими значениями):

  • Ljava/util/Map;/[ID] (species типа Map<int,double>)
  • Ljava/util/List;/[I] (species типа List<int>)
  • Ljava/util/List;/[[I] (species типа List<int[]>)
  • Ljava/util/List;/[$wild;] (wildcard-species для List)
  • Ljava/util/List;/$Wild; (альтернативное написание wildcard)
  • Ljava/util/List;/$Wild[Ljava/lang/Object;] (альтернативное написание wildcard)
  • [D/$length[5;] (массив фиксированного размера double[5], а не массив из double длины 5)
  • I/$interval[$ge;0;] (int, значение которого неотрицательно)
  • L/Ljava/util/TupleTemplate[Ljava/lang/String;I] (пара из String и int)
  • L/Ljava/util/TupleTemplate[FFF] (тройка float)
  • Ljava/lang/String;/$N; (вариант N для String)
  • (Ljava/lang/String;)Ljava/lang/String;/$N; (метод, оборачивающий N-String)
  • (Ljava/lang/String;/$N;)Ljava/lang/String; (метод, разворачивающий N-String)
  • L/; (кратчайшее возможное выражение, тривиально ограниченный Object)
  • LFoo;/; (только тип-носитель, с тривиальной модификацией)
  • L/$N; (только оператор типа, N-Object)
  • L/[$Arg;] (единственный аргумент без оператора типа: гипотетического значения нет)
  • L/LFoo[LBar;/$N;] (species типа Foo<N-Bar>)
  • L/LFoo[LBar;]/$N; (species типа N-Foo<Bar>)
  • [D/$length[5;]/$N; (вариант N массива фиксированного размера double[5])
  • [D/$N/$length[5;]; (вариант фиксированного размера для N-варианта double[])

Последние четыре примера показывают, что выражения операторов типов могут быть вложенными. Например, L/LFoo[LBar;/$N;] обозначает тип, который выводится сначала из Bar модификацией с помощью N, а затем передачей модифицированного типа параметризованному конструктору типа Foo. (Тип-носитель результата — Object, а не Foo.) Последние два примера показывают, что выражения типов могут вкладываться за счёт наращивания нескольких суффиксов TypeOp. Порядок этих суффиксов важен исключительно потому, что строки дескрипторов различаются: I/$J;/$K; — это другой тип верификатора, чем I/$K;/$J;, даже если вычислительные эффекты модификаторов типа J и K коммутируют.

JVM будет принимать выражения операторов типов, структурированные как строки TypeExpr, в следующих контекстах:

  • типы полей класса — размещаются как ссылки-«чёрные ящики»
  • типы методов класса — рассматриваются как аргументы и возвращаемые значения-«чёрные ящики»
  • типы CONSTANT_NameAndType — разрешаемые типы-«чёрные ящики» (поля или метода)
  • имена CONSTANT_Class — разрешаемые выражения типов с программируемым разрешением
  • операнды instanceof (через CONSTANT_Class) — разрешаемые типы с программируемым поведением
  • операнды checkcast, anewarray — аналогично instanceof
  • получатели invokevirtual (через CONSTANT_Methodref) — методы с программируемым разрешением
  • getstatic, putfield и т. д. — аналогично invokevirtual

Обычно синтаксис дескрипторов не пересекается с синтаксисом имён классов, которые встречаются в константах CONSTANT_Class. Например, дескриптор I сильно отличается от имени класса I. Однако в некоторых случаях синтаксисы могут пересекаться: имя класса массива совпадает с его дескриптором, включая завершающую точку с запятой. Мы используем этот приём и для выражений операторов типов, чтобы одно и то же выражение оператора типа можно было вставить прямо в дескриптор и также использовать как имя класса.

Строку имени класса можно однозначно распознать как выражение оператора типа за три шага:

  • проверить, является ли последний символ ] или «;» (иначе — неудача)
  • если строка начинается с [, разобрать имя типа массива и найти следующий за ним /
  • иначе просмотреть строку и проверить, встречается ли в ней символ ; или [

Если первый шаг и любой из остальных шагов пройдены, то доказано, что строка имени класса не является простым именем класса или именем класса массива, и её можно считать выражением оператора типа (или же ошибочными входными данными). В противном случае её можно считать простым именем класса (или именем класса массива). Другой, более простой приём (хотя, возможно, более медленный) — просто разобрать строку имени класса как простое имя класса или массива и посмотреть, достигнут ли конец или же следующий оставшийся символ — косая черта /, начинающая суффикс оператора типа; в этом случае второй шаг должен выполняться первым.

Второй и третий шаги дорогие, но необходимые, однако их можно отложить до выполнения первого шага, который дешёвый. Обратите внимание, что JVMS требует, чтобы имя класса не содержало открывающую квадратную скобку [, если это не имя типа массива, и в этом случае скобка не будет следовать за разделителем пакетов /. Поэтому грамматика имён классов остаётся однозначной даже после добавления выражений операторов типов.

Некоторые операции над выражением типа требуют доступа к внутреннему устройству «чёрного ящика». К ним относятся загрузка рефлексивной константы для выражения типа, проверка типа (checkcast), создание типа массива, компонентом которого является выражение типа, вызов метода у экземпляра, верифицированный тип которого — выражение типа, и т. д.

Встроенный механизм разрешения выражений операторов типов будет выполнять следующие задачи:

  • Получить bootstrap-метод («BSM») из TypeCarrier и TypeOpName.
  • Вызвать BSM для TypeArg, соответствующим образом разобранных и материализованных.
  • (Также передать нужный контекст, например текущий класс, носитель и имя оператора.)
  • Получить в ответ от BSM разрешённый дескриптор типа для этого типа.
  • Навсегда и атомарно записать этот дескриптор для этого конкретного выражения типа.
  • С помощью дескриптора получить различные варианты поведения, необходимые для этого типа.

Подробности этих шагов и связанные с ними API определены в другом месте и со временем могут расширяться. Набросок разрешённых дескрипторов типов и их поведения приведён ниже. Операторы типов именуются необязательным классом и необязательным идентификатором. Если класс указан, он поможет определить bootstrap-метод; например, если это шаблон, шаблон будет специализирован заданными аргументами. Если указан только идентификатор, BSM будет централизованным и будет присваивать фиксированные стандартные значения небольшому числу имён.

Когда станут доступны value types, выражения операторов типов также смогут взаимодействовать с value types. Каждому выражению оператора типа всегда будет однозначно назначаться вид: значение или ссылка. Если будут придуманы другие виды, выражения операторов типов будут получать вид тем же способом. Например, за '$' мог бы следовать символ вида, или же, помимо '$', можно было бы выделить дополнительные символы, начинающие выражения операторов типов различных видов.

Дескриптор не будет Class, а будет иметь собственный рефлексивный тип и API. Дескриптор будет сообщать конкретный класс-носитель Class, совместимый со всеми значениями, описываемыми исходным выражением оператора типа. BSM оператора типа может вернуть разрешённый дескриптор типа, который сообщает в качестве класса-носителя только Object, или же может сгенерировать и загрузить новый анонимный класс и использовать его. В любом случае JVM сможет использовать класс-носитель как безопасный супертип для выражения оператора типа. JVM не будет свободно выполнять преобразование из класса-носителя в тип оператора типа, кроме как через байт-код checkcast, поведение которого определяется разрешённым дескриптором типа, выбранным BSM.

Обратите внимание, что язык выражений операторов типов самодостаточен и заранее нормализован. Он не ссылается ни на какой пул констант, и в нём нет никакого «исчисления» для доказательства того, что два различных выражения типов обозначают один и тот же тип.

API разрешённых дескрипторов типов будет выглядеть примерно так:

interface ResolvedTypeDescriptor<T extends C, C> {
  Class<T> resolvedType();
  Class<C> carrierType();
  static <T> ResolvedTypeDescriptor<T,?> of(Class<T> clazz);

  // These defaults may be wired into the JVM bytecodes if desired.
  default boolean isInstance(C x) {
    if (this != of(carrierClass()))  throw subclassResponsibility();
    return carrierClass().isInstance(x);
  }
  default boolean isAssignableFrom(ResolvedTypeDescriptor<?,?> subDesc) {
    if (this != of(carrierClass()))  throw subclassResponsibility();
    return carrierClass().isAssignableFrom(subDesc.resolvedType());
  }
  default T cast(C x) {
    if (this != of(carrierClass()))  throw subclassResponsibility();
    return carrierClass().cast(x);
  }
  default T newArray(int length) {
    if (this != of(carrierClass()))  throw subclassResponsibility();
    return java.lang.reflect.Array.newInstance(carrierClass().getComponentType(), length);
  }
  default MethodHandle findVirtual(Lookup lookup, String name, MethodTypeDescriptor type) {
    if (this != of(carrierClass()))  throw subclassResponsibility();
    return lookup.findVirtual(carrierClass(), name, type.asMethodType());
  }
  private static RuntimeException subclassResponsibility() {
    throw new IllegalArgumentException();
  }
  /**
   * Initial entry point called from the VM when a type operator
   * expression must be resolved.
   */
  static <C> ResolvedTypeDescriptor<?,C> initialMetafactory(
    Lookup lookup, TypeDescriptorBootstrapCallInfo<C> bci
  ) throws BootstrapMethodError {
    String descriptor = bci.invocationName();
    Class<C> carrierType = bci.invocationType();
    Class<?> typeOpClass = bci.typeOperatorClass();
    String typeOpName = bci.typeOperatorName();
    List<Object> typeOpArgs = bci.asList();
    ...
  }
}

Открытым остаётся вопрос, следует ли объединить какую-либо часть API ResolvedTypeDescriptor с API Class. Такое решение могло бы создать набор вторичных «crasses» (квазиклассов типов времени выполнения), которые не представляют файл класса напрямую, а представляют тип, так или иначе производный от одного или нескольких файлов классов или связанный с ними. Для этого есть определённый прецедент: существующие экземпляры Class для примитивов и void, а также для массивов, можно рассматривать как «crasses». В этом случае API carrierClass, вероятно, назывался бы getPrimaryClass и сопоставлял бы «crass» с его ближайшим собственным супертипом (или Object, или интерфейсом), и появился бы новый запрос isTypeExpression.

Если держать API ResolvedTypeDescriptor отдельно от унаследованного API Class, решение было бы чище, но нам пришлось бы также дублировать или расширять многие API, например Lookup, где Class служит заменой дескриптора типа JVM. Интерфейс TypeDescriptor (предложенный проектом Constable) может дать нам возможность обобщить эти API, а не грубо дублировать их, и без введения «crasses».

Альтернативы

Этот дизайн можно рассматривать как доработку более раннего экспериментального механизма под названием «class-dynamic», который декодировал подъязык из строк имён классов и на лету генерировал файлы классов в ответ на запросы разрешения. Этот механизм пропускал выражение оператора типа через имя класса, что похоже на описанный выше дизайн, но не делал различия между обычной ссылкой на класс и выражением оператора типа.

Интеграция операторов типов в JVM, по-видимому, получается чище, если различие между обычными именованными классами и выражениями типов явно задано с самого начала. Кроме того, мы не хотим обязываться генерировать файлы классов в ответ на операторы типов; в некоторых сценариях использования операторы типов намеренно служат псевдонимами обычных классов, но с добавлением некоторой дополнительной полезной нагрузки — «аннотации». Это невозможно сделать во фреймворке, который смешивает имена классов с выражениями типов.

При проектировании классов-шаблонов мы могли бы попытаться добавить специальный синтаксис дескрипторов, разработанный именно для шаблонов. Однако дизайн, подобный описанному в этом JEP, всё равно понадобился бы.

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

Тестирование

// Какие виды разработки и выполнения тестов потребуются // для проверки этого улучшения, помимо обычных обязательных модульных тестов?

Риски и допущения

// Опишите все риски и допущения, которые необходимо учитывать вместе // с этим предложением.

Зависимости

// Опишите все зависимости этого JEP от других JEP, задач JBS, // компонентов, продуктов или чего-либо ещё.

Вопросы и ответы о дизайне

DRAFT DRAFT DRAFT Следующий раздел войдёт в комментарии, а не в сам JEP.

  • Вы не использовали точку . в синтаксисе операторов типов; почему? Потому что на некоторых путях дескрипторы проходят через имена классов, а косые черты преобразуются в точки и обратно. В этот момент любое различие между косой чертой и точкой было бы потеряно, если не вводить сложные контекстно-зависимые правила сохранения или восстановления точек.

  • Грамматика сложная: кажется, всё в ней необязательно. Почему бы не убрать часть необязательных элементов? Вкратце, каждый необязательный элемент обоснован следующим образом. TypeCarrier можно было бы убрать, сделав его всегда равным Object, но многие сценарии использования операторов типов работают в пределах статически ограниченного типа, и было бы расточительно не позволять этой статической границе выступать настоящим типом верификатора. При наличии TypeCarrier логично, чтобы фактический оператор типа иногда выводился непосредственно из носителя, а другие типы задавались отдельным параметром, отсюда и необязательность TypeOpName. Но если TypeOpName не связан с типом носителя, носителем часто оказывается Object, поэтому для этого частого случая предусмотрено специальное сокращение, делающее TypeCarrier необязательным. Таким образом, носитель может либо совпадать с оператором типа, либо быть полностью отдельным. Список аргументов необязателен, поскольку одни операторы типов по своей природе требуют аргументов, а другие являются «просто режимом» (как «not null»). Завершающая точка с запятой ; при отсутствии списка TypeArg — вопрос выбора; вместо неё можно было бы использовать [], но для простого модификатора вроде «not null» это выглядит чрезмерно громоздко, а требование непустого списка TypeArg в скобках добавляет тривиальную сложность.

  • Грамматика сложная: почему существуют разные способы обозначить оператор типа? Отказ от TypeOpName позволяет носителю и оператору типа происходить из одного и того же класса, как отмечено выше, а возможность задать оператор типа полностью разрешённым именем класса даёт очевидные преимущества модульности. Во втором случае возможность указать дополнительное имя для выбора члена класса позволяет одному классу предоставлять библиотеку операторов типов. Последний случай — простой идентификатор — позволяет либо выбрать член класса типа-носителя (или аргумент «режима», например «wildcarded»), либо системе глобально определить несколько операторов типов вне системы областей видимости пакетов: ! (для «not null») и ? (для «maybe null») — два таких вероятных глобальных оператора.

  • Грамматика сложная: вы допускаете слишком много видов аргументов операторов типов. Почему бы не ограничиться типами в качестве параметров? Аргументов-типов, очевидно, достаточно, чтобы модернизировать нынешние обобщения на месте и материализовать их типы внутри дескрипторов. Но это недальновидно, поскольку обобщения C++ допускают много других видов аргументов. Выбранная выше грамматика позволяет задавать разумный набор аргументов, не являющихся типами, соответствующих распространённым сценариям использования аргументов шаблонов в C++ и других языках. После типов очевидный следующий кандидат — строки; строками действительно можно обозначить всё остальное, что нам нужно, и они удачно являются фундаментальными в JVM. Мы добавили MethodType, потому что это фундаментальная конструкция JVM и её не следует передавать через строковый канал кодирования. Мы добавили NumberArg, потому что небольшие целые числа фундаментальны в различных сценариях использования, например для массивов определённой длины. Всё перечисленное соответствует естественно закодированным элементам пула констант (кроме целых чисел, превышающих long).

  • Вы забыли аргументы MethodHandle и Double, разве они тоже не фундаментальны? Да, фундаментальны, но их легко закодировать для bootstrap-методов с помощью комбинаций других типов аргументов, а разработка жёстко заданного строкового кодирования для них была бы излишне сложной. Для method handle достаточно передать несколько аргументов, обозначающих его класс, имя и тип, и, возможно, ещё ref-kind. Для числа с плавающей точкой рассмотрите использование строки, содержащей его шестнадцатеричное представление (hex-float), чтобы избежать проблем с округлением и неоднозначностью.

  • Эти строки-идентификаторы бесполезны без способа экранировать недопустимые символы; почему бы не ввести строки с полноценным экранированием? Ограничения для строк TypeArg такие же, как для имён классов, и существуют стандартные системы (например, кодирование «Symbolic Freedom») для представления немногих недопустимых символов с помощью escape-последовательностей. Bootstrap-методы, которым нужны произвольные строки, должны использовать такую схему. Это гораздо проще, чем каким-либо образом сообщать JVM, что она должна начать допускать ранее «опасные символы» в небольших частях грамматики дескрипторов.

  • Ограниченные примитивные типы, серьёзно? В более ранней версии грамматики предполагалось, что единственный тип-носитель — Object, и «головой» выражения оператора типа могло быть имя оператора типа (например, шаблонного класса). У этого было два серьёзных недостатка: во-первых, это не учитывало того, что шаблон вполне может быть супертипом всех своих экземпляров; это безусловно верно для контейнеров вроде List<int>; если отбросить эту границу типа, понадобится больше байт-кодов checkcast, чтобы восстановить её в коде методов, а это выглядит досадной расточительностью. Во-вторых, букву дескриптора L могут (в какой-то момент) дополнить другие дескрипторы классовых типов (например, дескриптор Q из прототипа «minimal value type»). Поэтому представляется разумным разрешить типам-носителям быть любыми уже существующими типами верификатора. С учётом этого примитивные типы и массивы достаются практически «бесплатно», хотя было бы разумно запретить ограниченные примитивы, если их окажется трудно реализовать, и добавить их позже, когда примитивы будут более полно унифицированы с другими типами.

  • Почему в правиле грамматики ArrayType больше не упоминается FieldType? Синтаксис типов массивов — наш единственный унаследованный синтаксис, похожий на оператор типа. Из заданного типа компонента он создаёт новый сложный тип объекта-массива. Мы не хотим делать вид, будто этот тип объекта-массива можно настроить, добавляя к типу его компонента произвольные «доработки», — управлять ограниченными скалярными типами и так достаточно сложно, даже не встраивая их во «внутренности» встроенного в JVM механизма объектов-массивов. Мы выбираем более простой вариант: разрешаем ограничивать экземпляры массивов, не задаваясь вопросом, что у них внутри. Когда массивы будут виртуализированы (станут экземплярами интерфейсов), мы сможем полностью вкладывать ограничения в типы компонентов массивов, но не раньше.