Этот пост — вольно-краткий перевод документа 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