Типы-значения в Java

в 15:59, , рубрики: java, struct, value types, метки: ,

Duke
Этот пост — вольно-краткий перевод документа State of the Values, предложения по введению типов-значений в JVM и сам язык Java, который написали Джон Роуз, Брайан Гоетц и Гай Стил, разбавленный моими мыслями. Опущены детали предложения по реализации типов-значений на уровне байт-кода, что не очень интересно для большинства Java-программистов.


Важнейшая мысль: объектная (ссылочная) идентичность нужна только для поддержки изменяемости объектов. Объект может изменить состояние, но по ссылке мы всегда можем проверить, что это «тот же» объект. Поэтому типы-значения будут строго неизменяемые.

Предполагаемые варианты использования типов-значений:

  • Числа: комплексные, беззнаковые, 128-битные, с фиксированной запятой и т. д. Все, что не описывается имеющимися примитивами.
  • Алгебраические типы: Optional<T>, Choice<T,U>, перечисления
  • Кортежи: пары, тройки, ...
  • Итераторы, курсоры
  • «Уплощение» сложных структур

Целевые фичи:

  • Можно использовать типы-значения везде, где сейчас можно объекты и примитивы: локальные переменные, поля, элементы массива, аргументы методов, возвращаемый тип методов.
  • В обычной ситуации аллокация в регистрах, на стеке, поле-значение это не ссылка, а прямо байты полей в родительском объекте. Хотя у ВМ есть право таки создать значение в куче, напр. если она считает, что так будет лучше, не может иначе обеспечить атомарность, не хватает стека, и т. д. Массивы с элементами типа-значения должны быть «плоскими».
  • При необходимости (напр. при передаче в метод, который хочет Object) неявные приведение (boxing) к обычному объекту-двойнику (как сейчас с примитивами).
  • Не просто структурки с полями: можно объявлять методы как в обычном объекте, переопределять equals(), toString(). Вызов метода не (обязательно) приводит к приведению к обычному объекту. Инкапсуляция полей возможна.
  • Компилятор или ВМ генерирует equals(), hashCode(), toString(), compareTo() по полям, если не переопределены в коде.
  • Типы-значения сами (а не только их «обычные» объектные двойники) могут реализовывать интерфейсы. Т. е. передача в метод, который хочет, допустим, Comparable не обязательно приводит к оборачиванию значения в объект.
  • Можно объявлять поля типа-значения как volatile.
  • Типы-значения могут содержать и обычные объектные поля (которые не обязаны быть рекурсивно неизменяемыми, как сами значения), и поля других типов-значений. Но: не могут содержать поля своего же типа.

Ограничения (должно быть запрещено или вызывать оборачивание в объект-двойник):

  • Тип-значение не может наследовать ни классу, ни другому типу-значению, от типа-значения ничего нельзя наследовать. (Хотя, возможно, будет возможность наследовать от «чисто абстрактных» классов, например от java.lang.Number.)
  • Вызов wait(), notify(), clone() или finalize() на значении.
  • Вызов System.identityHashCode() к значению.
  • Присвоение переменной типа-значения null.
  • Приведение к Object или любому супертипу.
  • Применение рефлексии к значению.
  • Запарки с атомарностью (напр. на платформе нельзя обеспечить атомарность чтения/записи, если общий размер типа-значения больше машинного слова, или двойного слова).
Синтаксис

Объявление типа-значения максимально приближено к обычным классам:

final __ByValue class Point {
    public final int x;
    public final int y;

    public Point(int x, int y) { 
        this.x = x;
        this.y = y;
    }

    public boolean equals(Point that) {
        return this.x == that.x && this.y == that.y;
    }
}

Уродливое __ByValue специально для того, чтобы никто не воспринимал это как окончательный синтаксис. Может, будет аннотация типа @ValueType. Объявление полей финальными — либо обязательно, либо неявное, как сейчас методы в интерфейсах можно объявить без public, но они все равно будут публичными. То же касается и модификатора класса.

Создание значения:

Point p = __MakeValue(x, y);

Тут вместо __MakeValue будет либо название типа без new, либо ничего, т. е. создание значения — просто скобочки с аргументами.

Поддержки специальных литералов (типа 0 + 1i для комплексных чисел или 1u для беззнаковых), а также перегрузки операторов (что было бы полезно тоже в первую очередь для числовых типов), скорее всего, не будет.


Надо понимать, что массивы, java.lang.String, java.lang.Integer и т. д. для бинарной совместимости в типы-значения преобразованы не будут. Так что существующий код магически не ускорится. По крайней мере, не в разы. Внутренний тип java.util.HashMap.Entry, например, поменять вполне могут.

Возможно, сделают отдельную сущность для массивов-значений, которые могут заменить varargs на уровне языка.

Объект Значение
Содержит что-то разное Объект Типы-значения
Одинаковое Массивы Массивы-значения?
Generics

Самый важный вопрос. В текущем виде дженерики — фича времени компиляции, и никаких типов-значений они поддерживать не будут. Но раз уж сказал «А» (типы-значения), то говори и «Б», поэтому рано или поздно дженерики переделают.

Мой прогноз:

  • Для Java 9 уже есть одна «главная» фича — модуляризация, Oracle явно делает на нее ставку, вероятность увидеть ее в Java 9 близка к 100%.
  • Если в Java 9 дженерики оставят «как есть», шанс увидеть типы-значения в этой версии 30%.
  • Если решат выкатить типы-значения и обновленные дженерики одновременно, в лучшем случае это случится в Java 10.

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

Автор: leventov

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js