Saltar al contenido
NextPatch Blog

Valhalla llega a JDK 28: value objects en preview

El JEP 401 ya está integrado en JDK 28. Con --enable-preview, == compara por valor y 30 clases de la plataforma, como Integer, Optional o LocalDate, dejan de tener identidad.

Java 4 min de lectura

El JEP 401, Value Objects, está integrado en JDK 28 como preview. Es la primera pieza de Project Valhalla que entra en el JDK, y toca algo que usas cada día sin pensarlo: qué significa == entre dos objetos.

El JEP se creó en agosto de 2020. Seis años después, ya puedes probarlo en las builds de acceso anticipado de JDK 28.

Es una preview: hace falta --enable-preview para compilar y para ejecutar, y puede cambiar antes de ser definitiva. No es para producción.

Objetos sin identidad

Un value object es inmutable y no tiene identidad: se distingue solo por el valor de sus campos. Lo declaras con el modificador value, también en un record:

java
// javac --release 28 --enable-preview Main.java
// java --enable-preview Main
value record Point(int x, int y) {}

void main() {
    var a = new Point(1, 2);
    var b = new Point(1, 2);
    IO.println(a == b); // true: compara los campos, no la identidad
}

Según el JEP, dos value objects son == si son de la misma clase, sus campos primitivos guardan los mismos bits y sus campos de referencia son, a su vez, == aplicando la misma regla. Ojo con ese último punto: si tu value class tiene un String, ese campo se sigue comparando por referencia. El JEP dice expresamente que no pretende que == sustituya a equals, así que para comparar contenido sigue usando equals.

Integer, por fin coherente

El ejemplo del JEP es de los que se cuentan en el café. Hoy, sin preview:

text
jshell> Integer x = 1996, y = 1996;
x ==> 1996
y ==> 1996

jshell> x == y
$6 ==> false

Con 1 sale true y con 1996 sale false, porque Integer usa una caché para los valores pequeños. Arrancando jshell --enable-preview en JDK 28, el mismo x == y devuelve true: dos Integer son == si representan el mismo valor.

Con la preview activada, 30 clases de la plataforma pasan a ser value classes:

  • java.lang: Integer, Long, Float, Double, Byte, Short, Character, Boolean, Number y Record.
  • java.util: Optional, OptionalInt, OptionalLong y OptionalDouble.
  • java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetTime, OffsetDateTime, Duration, Instant, Period, Year, YearMonth y MonthDay.
  • java.time.chrono: MinguoDate, HijrahDate, JapaneseDate y ThaiBuddhistDate.

Lo que ya no puedes hacer

Las reglas son pocas y estrictas. Todos los campos de instancia son implícitamente final. La clase también lo es, salvo que la declares abstract, y una value class solo puede extender Object o una value class abstracta.

Y lo que depende de la identidad deja de funcionar. El ejemplo del JEP con un LocalDate:

text
jshell> synchronized (d1) { d1.notify(); }
|  Error:
|  unexpected type
|    required: a type with identity
|    found:    java.time.LocalDate

jshell> Object o = d1
o ==> 1996-01-23

jshell> synchronized (o) { o.notify(); }
|  Exception java.lang.IdentityException: Cannot synchronize on
   an instance of value class java.time.LocalDate

Si en algún rincón de tu código se sincroniza sobre un Integer o una fecha, es buen momento para buscarlo: el JEP avisa de que ese código falla tras la migración, en compilación o con IdentityException en ejecución.

Y el rendimiento

La gracia de quitar la identidad es que la JVM gana libertad para representar estos objetos como le convenga. El JEP pone un ejemplo: cada elemento de un array de LocalDate podría ocupar una palabra de 64 bits que indica si la referencia es null y, si no, guarda directamente el año, el mes y el día. Las referencias a value objects siguen pudiendo ser null.

El JEP no da cifras de rendimiento, así que nosotros tampoco.

JEP 539, la pieza de la JVM

Junto al 401 se ha integrado el JEP 539, Strict Field Initialization in the JVM, también en preview. Añade a la JVM campos que deben inicializarse antes de leerse, de modo que nunca se observa un 0 o un null por defecto. Los compiladores marcan así todos los campos de una value class. No es una feature del lenguaje ni cambia cómo javac compila tu código actual: no tienes que hacer nada.

Cómo probarlo

Descarga una build de acceso anticipado de JDK 28 y abre jshell --enable-preview, o compila con javac --release 28 --enable-preview. Nuestro consejo: coge un par de records inmutables de tu proyecto, ponles value en una rama y mira qué se rompe.

Pasa de leerlo a practicarlo.

Un resumen de 45 segundos y un reto de dos minutos al día. La cuenta es gratis y guarda tu XP, tu racha y tus insignias.

Crear cuenta gratis