4 puntos por GN⁺ 4 시간 전 | 2 comentarios | Compartir por WhatsApp
  • JEP 401: Value Classes and Objects ha llegado al punto de entrar como preview real en el JDK
  • El objetivo central es hacer que los objetos de Java “se codifiquen como una clase y funcionen como un int”, para reducir los costos de encabezados de objeto, asignación en heap, GC e indirección por punteros
  • La value class de JDK 28 sigue siendo todavía un tipo de referencia anulable; no incluye tipos non-null, genéricos especializados ni codificación de 128 bits, y requiere --enable-preview
  • La JVM puede escalarizar un value object o hacer aplanamiento en heap en campos y arreglos, pero en tipos superiores como generics con erased types u Object puede materializarse como objeto en heap
  • Los desarrolladores de Java deben reflejar en el diseño del código la diferencia entre identidad y valor, y el impacto se extiende a ==, synchronized, primitive wrappers, rendimiento de arreglos y futura especialización de genéricos

El alcance de Valhalla que llega a JDK 28

  • El 15 de junio, la ingeniera de Oracle Lois Foltan confirmó la integración de JEP 401: Value Classes and Objects en el repositorio principal de OpenJDK y su objetivo para JDK 28
  • El pull request relacionado agrega más de 197 mil líneas en 1,816 archivos
  • Como el cambio es de gran escala, durante la integración se pidió a otros committers que retuvieran temporalmente commits grandes
  • JEP 401 es una función preview desactivada por defecto
    • Para usar la sintaxis se necesita --enable-preview
    • Brian Goetz marcó el límite al llamarlo “la primera parte de Valhalla”
  • JDK 28 está previsto para lanzarse en marzo de 2027, y la integración en mainline está planeada aproximadamente para julio de 2026

El costo del modelo de objetos de Java al que apunta Valhalla

  • El lema de Valhalla es “codes like a class, works like an int
    • El objetivo es usar una clase normal con métodos, validación en constructores y nombres de campos significativos, pero permitir que la JVM la maneje con la eficiencia de un primitive
  • En Java, salvo los 8 primitives, casi todo es un tipo de referencia
    • En Point p = new Point(1, 2), p no es el point en sí, sino un puntero que señala a un objeto en el heap
    • Cada vez que se lee un campo, la JVM tiene que seguir ese puntero
  • Cuando aumenta la cantidad de objetos, el costo crece rápidamente
    • Cada objeto tiene un encabezado de objeto para el tipo, el estado de sincronización y otros datos
    • Los objetos se asignan en el heap y luego pasan a ser objetivo del GC
    • Un arreglo de un millón de Point está compuesto en realidad por un millón de punteros y un millón de objetos distribuidos por todo el heap
  • El texto “State of Valhalla” de Brian Goetz llama a esta disposición de memoria fluffy
    • Lo que Valhalla busca es una disposición dense, con los datos colocados uno junto al otro

La brecha con el hardware y los límites del escape analysis

  • La razón por la que importa una disposición de memoria densa es la brecha de velocidad entre CPU y memoria
    • En 1995, el costo de acceso a memoria era parecido al de una operación de CPU
    • Hoy la CPU es varias veces más rápida que la memoria principal, y los cachés llenan esa diferencia
  • La CPU suele leer memoria en unidades de cache line de 64 bytes
    • Si los datos son densos y están en orden, en una sola lectura se traen muchos valores útiles
    • Si se accede a objetos dispersos siguiendo punteros, pueden producirse cache misses, mucho más lentos que un hit
  • El escape analysis de la JVM puede eliminar algunas asignaciones de objetos
    • Si determina que un objeto no “escapa” fuera de un fragmento local de código, puede no asignarlo en el heap y expandir sus campos en variables o registros
  • Sin embargo, el escape analysis es poco predecible y frágil
    • La optimización puede detenerse si el objeto entra en un campo de otra clase, se guarda en un arreglo, se pasa a un método complejo o cruza un límite que el JIT no puede analizar
    • Un refactor pequeño, una actualización del JDK o un cambio en la estructura del código pueden hacer que el objeto vuelva al heap
  • Si se abandonan los objetos por rendimiento y se codifica directamente con bytes crudos como r, g y b, se puede ganar velocidad, pero se pierde seguridad, legibilidad, validación y métodos

El inicio en 2014 y la transición de Q World a L World

  • Project Valhalla comenzó oficialmente en 2014
  • James Gosling lo describió en ese momento como “six PhDs tied into a single knot”
  • Los creadores de Java querían value types desde la época de Java 1.0, pero en 1995 el problema era demasiado difícil y lo dejaron de lado
  • El objetivo inicial era recuperar la alineación entre el modelo de programación y las características de rendimiento del hardware moderno
    • La dirección era permitir que los usuarios declararan tipos flat y dense como primitives, pero que se vieran y funcionaran como clases normales
  • El prototype inicial seguía la dirección de Q World
    • Veía el nuevo value type como una entidad fundamentalmente distinta de los objetos, con type descriptor, bytecode y top type separados
    • Como todo el sistema de tipos de la JVM debía tener dos variantes, la complejidad aumentó
  • L World, que apareció alrededor de 2019, fue el punto de inflexión
    • El value type comparte el mismo “L carrier” que una referencia normal
    • El equipo esperaba que esta integración fuera difícil, pero funcionó sin grandes concesiones y resolvió varios problemas de prototypes anteriores
  • En L World surgió una separación importante
    • El modelo de la JVM y el modelo del lenguaje no tienen que coincidir al 100%
    • Se puede tener el modelo L World en la JVM y ofrecer a los programadores un modelo de lenguaje más conveniente
  • Desde entonces, el trabajo se dividió en dos etapas: value classes y genéricos especializados

Cambios en los nombres y en el modelo

  • La terminología de Valhalla cambió varias veces, y no fue solo un cambio de nombre sino un reflejo de cambios en el modelo
  • El término inicial era value types
    • En ese momento todavía no estaba claro con precisión qué eran exactamente estos tipos
  • Hacia 2019~2020 se consolidó el modelo de inline classes
    • Las clases existentes se distinguían como identity classes, y las nuevas como inline classes sin identidad
    • Se establecieron restricciones como que una inline class es final por defecto, sus campos son final y no puede sincronizarse
  • El “State of Valhalla” de 2021 trató las primitive classes y un modelo de dos proyecciones
    • La idea era que un tipo tuviera una variante value, compacta y no anulable, y una variante reference que permitiera null
    • También se experimentó con sintaxis como Point.val / Point.ref, y después Point! / Point?
  • Este modelo era potente, pero implicaba una carga cognitiva alta
    • Los programadores tenían que entender de forma cotidiana dos formas del mismo tipo y cuándo ocurrían las conversiones
    • Al final, el dualismo se redujo para simplificar el modelo para el usuario
  • Actualmente, JEP 401 usa value class y value object
    • Se declara una value class con el modificador value
    • Sus instancias son value objects sin identidad
    • Una value class sigue siendo un reference type
  • La no nulabilidad se separó en un JEP opcional aparte, Null-Restricted Value Class Types
    • No está incluido en JDK 28
  • Los textos antiguos que explican el modelo previo de “primitive classes” pueden diferir del estado actual de OpenJDK
  • JEP 401 también viene acompañado del preview de JEP 402: Enhanced Primitive Boxing
    • Va en la dirección de hacer más fluida la conversión entre primitive y wrapper
    • No debe asumirse que todo entra junto con JEP 401 en una forma ya terminada

El modelo de value class en JDK 28

  • Una value class se declara con el modificador value
value class USDCurrency implements Comparable<USDCurrency> {  
    private int cents; // implicitly final  
    public USDCurrency(int dollars, int cents) {  
        this.cents = dollars * 100 + cents;  
    }  
  
    public USDCurrency plus(USDCurrency that) {  
        return new USDCurrency(0, this.cents + that.cents);  
    }  
  
    // dollars(), cents(), compareTo(), toString()...  
}  
  • También puede haber value records
  • Las reglas principales son las siguientes
    • Todos los instance fields son implícitamente final
    • Un método no puede ser synchronized
    • La clase es final por defecto
    • Es posible una jerarquía compuesta por value class y abstract value class
    • No puede heredar de una clase con identidad
    • Sí puede implementar interfaces
  • La característica clave es que no tiene identidad
    • En un objeto normal, aunque tenga el mismo contenido, si se crea dos veces con new Point(1, 2), son objetos distintos
    • Un value object no tiene identidad, igual que con el valor int 4 no existen “dos cuatros distintos”

Cambios en ==, synchronized y null

  • En un value object, == deja de ser una comparación de identidad y pasa a ser una verificación de substitutability
    • Compara recursivamente si son de la misma clase y tienen los mismos valores de campo
    • Los campos primitivos se comparan bit a bit, y los campos de objeto se vuelven a comparar con ==
    • new USDCurrency(3,95) == new USDCurrency(3,95) pasa a ser true
  • Aun así, como == inspecciona el estado interno, para “representa los mismos datos” normalmente equals sigue siendo más apropiado
  • Un value object no tiene identidad sobre la cual sincronizar
    • Si se intenta sincronizar, se produce una IdentityException
    • Cuando hace falta verificar explícitamente la identidad, pueden usarse Objects.requireIdentity y Objects.hasIdentity
  • En JDK 28, una value class sigue siendo nullable
    • USDCurrency d = null; es válido
    • Los tipos que prohíben null quedan para un JEP futuro aparte y no están en JDK 28
  • La no nulabilidad no es solo un tema sintáctico, sino una palanca de rendimiento que abre la puerta a una mayor compactación de las value classes

Escalarización y compactación en heap

  • JEP 401 le da a la JVM la libertad de optimizar value objects
  • La escalarización (scalarization) es una técnica de JIT que descompone la referencia a un value object en un conjunto de campos
    • En lugar de pasar un puntero a Color, puede pasar los bytes r, g, b y un flag de nulidad
    • El costo de asignación y de GC puede desaparecer
    • Se parece al escape analysis, pero es más predecible y puede aplicarse más allá de los límites de llamadas a métodos no inlined
  • La escalarización tiene limitaciones
    • Normalmente no funciona si el tipo de la variable es Object, que es un supertipo de la value class, o un parámetro genérico con type erasure
    • En ese caso, el objeto debe materializarse en el heap
  • La compactación en heap (heap flattening) consiste en codificar los valores de campo de un value object como un vector de bits compacto y escribirlo directamente en un campo o en una celda de arreglo
    • No hace falta un puntero a otra ubicación del heap
    • Se gana densidad de datos y locality
  • Los datos compactados deben poder leerse y escribirse de forma atómica para evitar tearing en accesos concurrentes
    • En plataformas comunes, un tamaño “lo bastante pequeño” puede estar en el orden de 64 bits incluyendo el flag de null
    • Las value classes pequeñas pueden compactarse bien, pero incluso dos campos int o un solo double pueden no encajar en el tamaño de escritura atómica y terminar como objetos normales en heap
  • En el futuro, una codificación de 128 bits y los tipos null-restricted podrían permitir la compactación de value classes más grandes

Efectos en boxing, wrappers y arreglos

  • Si el preview está activado, las propias primitive wrapper classes como Integer, Long y Double se convierten en value classes
    • Como el box pierde identidad, la JVM puede escalarlo y compactarlo
    • Integer[] puede acercarse a la eficiencia de int[], en una dirección donde el overhead del boxing se reduce mucho
  • JEP 402: Enhanced Primitive Boxing amplía aún más la conversión entre primitive y box
    • Abre el camino hacia expresiones como List<int>, pero sigue siendo un trabajo aparte en proceso de maduración
  • Es en los arreglos donde el efecto se aprecia mejor
    • Un Color[] tradicional puede terminar siendo un millón de punteros y un millón de objetos dispersos por el heap
    • Un Color[] de value class puede convertirse en un bloque contiguo que almacena directamente valores de color consecutivos
    • La CPU puede leer varios valores secuencialmente por unidad de cache line

Diferencias antes y después con el ejemplo de Point[]

  • Antes de Valhalla, un ejemplo con una class normal es el siguiente
final class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Este arreglo contiene un millón de punteros
    • Cada puntero apunta a un objeto Point separado en algún lugar del heap
    • Cada objeto tiene, además de los dos int, un encabezado de objeto
    • Al recorrerlo, hay que leer el puntero, saltar a esa dirección y leer los campos
  • Después de Valhalla, un ejemplo de value class sería el siguiente
value class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • La diferencia en el código es solo la palabra value, pero la disposición en memoria cambia
    • La JVM puede almacenar densamente los valores de cada point dentro del arreglo
    • No hay encabezado por elemento ni tampoco punteros
    • Tomando x y y, dos int, como base, pueden quedar dispuestos de forma continua en 8 bytes y con un posible null flag
  • También se mantiene la mantenibilidad
    • Point sigue siendo una class con nombre, constructor, validación y methods
    • Se puede evitar el enfoque de dividirlo en int[] xs, int[] ys y hacer coincidir los índices

Por qué aún faltan los genéricos especializados

  • Los generics de Java están implementados con type erasure
    • List<String> y List<Integer> son el mismo List en runtime
    • El parámetro de tipo T se hace erasure a Object
  • El erasure fue una elección intencional para introducir generics sin romper la base de código existente de Java
    • Incluso si una class no genérica se convierte en genérica, no rompe los source files ni las compiled classes existentes
  • Valhalla y el erasure chocan en términos de rendimiento
    • Si se pone un value object en List<Point>, como T se hace erasure a Object, el objeto debe materializarse en el heap
    • La ventaja de flattening obtenida en Point[] puede desaparecer en ArrayList<Point>
  • El plan de recuperación tiene dos etapas
    • Universal Generics: en el nivel del lenguaje, permite que las variables de tipo manejen también value types
      • Sigue usando erasure
      • Puede aparecer una advertencia del compilador de “null pollution” por el problema de que los campos T empiezan por defecto en null
      • Si se resuelve la advertencia, la API queda más cerca de estar lista para specialization
    • Specialized Generics: en el nivel de la JVM, genera un layout de class especializado para cada argumento de tipo concreto
      • En la terminología del proyecto, están relacionados species y type restriction
      • Recién en esta etapa ArrayList<Point> podría usar memoria flat de verdad
  • En JDK 28 no hay full specialized generics
    • Que las colecciones, los streams y las APIs se vuelvan flat y allocation-free sobre value types es trabajo para un future release

Lo que hay y lo que no hay en JDK 28

  • Lo que entra en JDK 28 es lo siguiente
    • declaraciones value class y value record
    • migración a value class de algunas value-based classes existentes en el JDK, como las primitive wrappers
    • scalarización y flattening de classes que cumplan las condiciones
    • boxing más barato
  • Lo que no está en JDK 28 es lo siguiente
    • null-restricted types
    • full specialized generics
    • codificación de 128 bits
    • un JEP 402 completamente maduro
  • Como es una preview feature, la sintaxis y el comportamiento pueden cambiar en cada release según el feedback
  • JDK 28 no es LTS
    • Es muy probable que el siguiente LTS sea JDK 29 en septiembre de 2027
    • Muchas empresas podrán encontrarse con un Valhalla estabilizado en un LTS, pero la preview de JDK 28 inicia el ciclo de feedback con código real

Cambios que habrá en el ecosistema y en el código

  • En el mundo de Java de alto rendimiento, Valhalla abre un camino para manejar datos densos sin renunciar a la abstracción
    • Esto aplica a áreas como procesamiento de datos, vector computation, ML, desarrollo de videojuegos, finanzas y codecs
  • Los frameworks y libraries pueden empezar la migración de value-based classes
  • El código que dependía de la identidad puede experimentar diferencias de comportamiento
    • == en un value object ya no compara direcciones, sino substitutability
    • synchronized sobre un value object lleva a IdentityException
  • Aunque Integer se convierta en value class, en la mayoría de los casos el binario seguirá enlazando
    • El nuevo error de compilación aparece al intentar sincronizar sobre estos tipos
    • == o synchronized(someInteger) basados en la identidad de Integer pueden verse afectados
  • Los builds de acceso anticipado están disponibles en jdk.java.net/valhalla

Resumen de preguntas frecuentes

  • Una value class no es lo mismo que un record
    • record es la elección de que el contenido sean components
    • value es la elección de renunciar a la identidad
    • Son posibles todas las combinaciones: class normal, record, value class y value record
  • Un value object puede compararse con ==
    • El significado no es comparar direcciones, sino substitutability
    • Para la igualdad de los datos representados, normalmente equals es más adecuado
  • La value class de JDK 28 puede ser null
    • El tipo non-nullable será un future JEP
    • También es importante para el flattening de value classes más grandes
  • Un ArrayList<Point> flat y rápido todavía no
    • Debido al type erasure, los objetos dentro de colecciones genéricas deben materializarse en el heap
    • En JDK 28, los casos representativos donde el flattening funciona de forma directa son los fields y arrays de value type, como Point[]
  • El escape analysis no puede sustituirlo todo
    • Si el objeto sale hacia un field, un array o fuera de los límites del análisis, la optimización puede romperse
    • La scalarización de value objects es más predecible y puede extenderse más allá de los límites de las method calls
  • Valhalla completo se expande a lo largo de varios releases
    • JDK 28 es la primera preview de value class
    • Specialized generics, null-restricted types y codificación de 128 bits son trabajos que se extenderán a future releases

2 comentarios

 
click 1 시간 전

Los hilos virtuales de Project Loom, que se lanzaron tras un largo período de desarrollo, son convenientes y resuelven muchas cosas a nivel del runtime de la JVM, así que el desarrollador tiene relativamente menos de qué preocuparse.
Ojalá Project Valhalla también termine lanzándose con esa sensación de “almuerzo gratis”.

 
GN⁺ 4 시간 전
Comentarios en Hacker News
  • Dijeron que la diferencia de memoria era algo fundamental, pero queda la duda de si el texto fue revisado como corresponde
    ¿No acababan de explicar que los objetos con una representación mayor a 64 bits no se aplanan en el heap? El Point del ejemplo tiene 2 enteros de 32 bits más una bandera de null, así que son al menos 65 bits
    Entre la frase “podría haber una bandera de null” y las oraciones cortas de énfasis que vienen después, da la impresión de que una IA generó las frases destacadas y se desvió, y también es una pena el bloque intermedio de "[IMAGE: the same Point[] array in two variants..."

    • Frases como “Sin header por elemento. Sin punteros. Sin andar saltando por todo el heap.” tienen un claro olor a estilo de IA, y por eso se ven como escritura perezosa
      Está bien usar IA para ayudar a escribir, pero si no le pones tu propia voz, no hay motivo para leerlo
      https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
    • El tema de verdad parecía muy interesante y hasta podía tolerar las imágenes generadas por IA
      Pero después de unos pocos párrafos quedó claro que era un texto hecho pasado por un LLM o por algo todavía peor
      Sea un blog técnico o lo que sea, por favor, no dejen que la IA escriba por ustedes. Nadie quiere leer eso
    • ¿Hay 18446744073709551616 valores posibles y no se puede reservar 1 de ellos para null? :)
      Hoy me enteré de que en Rust existe NonZeroU64, y que al combinarlo con Optional se puede obtener el comportamiento necesario usando solo 64 bits por elemento
      https://doc.rust-lang.org/std/num/type.NonZeroU64.html
    • Era tan obvio que usaron demasiada IA que dejé de leer a los 2 párrafos
    • Lo de “los objetos con una representación mayor a 64 bits no se aplanan en el heap” era algo de un commit inicial
      Como deja claro el JEP, esto no es más que la primera entrega de una función enorme, y como muchas funciones recientes de Java, se está entregando por partes
      Obviamente la meta es aplanar también valores más grandes, y ese mecanismo ya existe dentro de la JVM. Lo que falta es expresar a nivel de lenguaje la intención de “permitir tearing”
  • Reconozco el enorme trabajo real que entró en Valhalla, pero me cuesta estar de acuerdo con la interpretación de que “el modelo era poderoso pero mentalmente pesado”
    Decir que una variable no puede ser null no es una distinción mentalmente pesada, y menos aún si todo está suficientemente bien anotado
    La postura de “simplificar el modelo para el usuario aunque se sacrifique el techo de rendimiento” incluso podría haber terminado simplificando las cosas para el usuario
    El sistema de tipos de un lenguaje de programación existe para ofrecer garantías convenientes a los desarrolladores sobre una CPU que en el fondo solo maneja números. No hace falta reducir garantías de seguridad opcionales por considerarlas “demasiado complejas”
    Y eso incluso después de haber llegado a reconocer que “el modelo del lenguaje y el modelo de la JVM no tienen por qué coincidir al 100%”

    • No me da mucha confianza que Java vaya a encaminar bien su dirección
      La estructura de gobierno de Java parece deficiente, sobre todo en contraste con .NET, que en general tomó decisiones mayormente correctas desde el principio
      A estas alturas incluso me pregunto si Java sigue teniendo valor o atención dentro de Oracle. La empresa ya parece más bien un negocio de datacenters/cómputo con actividades heredadas y una deuda enorme colgando de ellas
      A veces hasta da la impresión de que las únicas áreas que todavía generan ganancias en Oracle son el equipo legal y el de cortar el césped
    • ¿Ese reclamo no va más dirigido al bloguero que al lenguaje Java?
      Y también viene el marcador de nulabilidad: https://openjdk.org/jeps/8303099
      Solo que tienen que sacarlo de forma gradual; tan solo este PR que introduce clases/objetos de valor ya ronda las 200 mil líneas
    • Los tipos de valor no anulables simplemente quedaron para un JEP posterior
      No parece que estén diciendo que sea imposible; más bien es aquello de que no te puedes comer un elefante de un solo bocado
      Aun así, llevan bastante tiempo royendo esta pata en particular
    • nullable es solo otro estado de carga en la programación orientada a rieles
      Si es un concepto resuelto desde 2012, no hay motivo para meter varios sabores de estado directamente en el lenguaje. Los rieles solo van de A a B, y se desvían según el estado de carga del tren
      Cuando un concepto aparece y desaparece y termina en guerras de lenguajes, eso es señal de que hay demanda pero el lenguaje no la maneja bien, o que incluso si la maneja, crea sobrecarga mental
      Cosas como Value, Errorstates, Null, IoExceptions, WeirdOsStatesNeededToHandleUpstairs
      https://fsharpforfunandprofit.com/rop/
      Dicho al estilo Monty Python: sigamos de una vez
    • Aquí no se está hablando de seguridad contra null, sino de una proyección referencia/valor parecida a Integer/int
      En vez de tener para cada tipo una proyección con identidad y otra sin identidad, el equipo de Valhalla hizo que los tipos de valor simplemente no tengan identidad, y por eso Integer e int pasan a ser sinónimos
      La disposición en memoria se decide automáticamente según el contexto y las decisiones de optimización. Por eso también cambió el significado de == para wrappers primitivos como Integer, y ya no depende de si se usa la “proyección de referencia” o la “proyección de valor”
      Aquí no pasó que se recortaran garantías de seguridad opcionales por considerarlas “mentalmente pesadas”
  • En los comentarios de HN sobre Java/JVM se repite mucho que, sorprendentemente, muchísima gente conserva una imagen antigua de la JVM o de Java, pero casi no conoce cómo son hoy
    La JVM de 2026 es un depredador muy sano. ¿Tiene defectos? Claro, pero la base es extremadamente buena

    • En HN es difícil encontrar una buena valoración de la JVM, y aquí se la trata como una tecnología pasada de moda
      En el trabajo uso Java 26 más reciente y las funciones en vista previa, sobre todo StructuredConcurrency, y es excelente. Incluso viniendo de usar Haskell y Python en empresas anteriores, no me arrepiento para nada
    • Mucha gente sigue teniendo que ejecutar Java 8 mientras mantiene monolitos en Java que empezaron en la época en que Java estaba de moda en los 2000
      Personalmente conozco las funciones nuevas que salieron en los últimos años, pero en el trabajo real Java está, literalmente, atrapado en el pasado
  • Bastantes comentarios aquí son algo injustos frente al gran trabajo que se está haciendo ahora y los JEP aún mejores que vienen después
    Si comparáramos Java con un niño, pasó sus primeros años con un padre amoroso (Sun), luego lo tiraron al garaje junto con los otros niños y lo dejaron abandonado con un tutor malvado (Oracle)
    Como estuvo descuidado y sin cariño hasta JDK 8, básicamente ha venido jugando a ponerse al día
    Es cierto decir que “recién ahora tiene cosas como structs o value types”, pero eso fue porque su crecimiento se vio frenado por procesos corporativos enormes, burocráticos y hostiles. Ahora se liberó y recibe cariño a través de la familia OpenJDK
    Y vamos a seguir disfrutando de la alegría de escribir una vez y desplegar en cualquier parte

    • Te guste Oracle o no, esa no es una descripción correcta de la historia de Java
      Se parece más a que lo crió un padre amoroso que, por problemas financieros, lo dejó en un hogar temporal, donde sí quedó descuidado
      Después lo adoptó un nuevo padre amoroso, Oracle, y Java floreció hasta convertirse en un adulto sano y estable
      Oracle también fue quien completó la apertura del ecosistema al convertir OpenJDK en la implementación de referencia, y además liberó como open source herramientas antes privativas como JFR y Mission Control
      También mantuvo a muchos miembros fundadores del equipo del lenguaje, algo bastante raro en una adquisición así, y Java mejoró mucho tanto en el lenguaje como en el runtime
    • Java sí estuvo descuidado, pero en los últimos años de Sun
      Oracle impulsó Java a una velocidad sin precedentes mientras mantenía la mayor parte de la compatibilidad hacia atrás
      Se dice que .NET “lo hizo bien desde el principio”, pero si eso significa la separación y reescritura entre .NET Framework/.NET Core/.NET, entonces ni siquiera tiene sentido dentro de esta discusión. .NET pudo aprender viendo a Java, y aun así también arruinó partes
      Con MySQL pasó algo parecido. En este sitio decían que “estaba muerto”, pero para quienes de verdad lo conocen, revivió bajo Oracle
    • Decir que “Oracle descuidó Java” y que “estuvo descuidado hasta JDK 8” es contradictorio
      La última versión de Java bajo Sun salió en 2006, Oracle compró Sun en 2010, y JDK 7 salió en 2011 y JDK 8 en 2014
      El equipo se mantuvo en gran parte igual, y la diferencia más grande fue que Oracle terminó con el abandono y puso más dinero. Por eso Java aceleró su ritmo después de la adquisición
      Se habla de “ponerse al día”, pero ni siquiera está claro con quién se estaría poniendo al día. Los únicos lenguajes tan o más populares que Java son JS/TS y Python
      Quienes dicen que Java se quedó atrás normalmente lo comparan con lenguajes que están mucho peor que Java. A veces quienes aman cierta feature pasan por alto que los lenguajes que la tienen no van mal por esa feature, sino a pesar de ella
      A los gerentes les gusta que las cosas salgan rápido, mientras que el liderazgo técnico, incluso desde la época de Sun, ha sostenido que hay que hacerlo con cuidado, lento y bien
      Entiendo la sensación de que Java ya no es tan popular como en 2003, pero esa fue una etapa excepcionalmente unificada no solo para Java sino para todo el ecosistema de software, y ni antes ni después volvió a haber algo tan unificado
    • Dicen “escribe una vez y despliega en cualquier parte”, pero no funciona en navegadores, iOS ni sistemas embebidos
      Ahora la tecnología que de verdad permite escribir una vez y desplegar en cualquier parte es WebAssembly. La JVM tuvo su oportunidad y perdió
    • Siguiendo con la metáfora, Java no solo fue tirado al garaje, también se usó para demandar a Google por miles de millones de dólares en manutención, y al final terminó convertido en un mecanismo para recaudar efectivo
      Aun así, no diría que Java haya tenido el “crecimiento frenado”. Tomó decisiones; algunas fueron razonables y otras no, y después esas decisiones son muy difíciles de corregir
      Basta ver C++: personalmente considero su semicompatibilidad con C como un albatros imposible de arreglar de 150 pies, y muchas versiones desde C++11 han sido trabajo para hacer ese albatros un poco más llevadero
      Me parece que tratar todas las value classes en la JVM como un único tipo L similar a un tipo primitivo es una solución bastante elegante para un problema difícil
      Al final, todo esto se deriva de la decisión en Java 2 de implementar los genéricos mediante type erasure por compatibilidad hacia atrás, y C3 vio ese resultado y rechazó ese camino
  • Solo con la evolución de los value types en Java ya se podría escribir un thriller tecnológico completo
    Leí la lista de correo y vi todos los videos relacionados, y me impresionó muchísimo el proceso de integrar el diseño en algo que siempre siguiera viéndose como Java
    Al mismo tiempo, profundizaron mucho más finamente en qué significa un value type y qué optimizaciones se pueden hacer, y dónde

    • El único cambio sintáctico es agregar value
  • En las clases de valor, == termina funcionando en la práctica como memcmp()
    Eso es un poco decepcionante, porque rompe la encapsulación y expone detalles de implementación
    El código cliente puede bifurcarse según cómo esté representado internamente un valor dado. En cierto sentido, eso es peor que comparar identidad, porque la comparación de identidad al menos no expone el estado interno

    • Los tipos de valor son un concepto muy alejado de esa forma de pensar orientada a objetos como “organismos mágicos de caja negra”
      No es hacer orientación a objetos clásica de una manera nueva, sino que un lenguaje nacido de la ideología orientada a objetos da otro paso hacia un mundo post-orientado a objetos
    • Si un bloque de datos tiene estado interno, entonces ese bloque de datos en sí está mal
      Supongo que del lado de Java también habrán considerado perfectamente cosas como excluir el padding de la comparación o forzar los bytes de padding a 0
      También tendría que funcionar con cadenas. Las cadenas claramente van a seguir asignándose en el heap, y hacer memcmp de punteros dentro de una nueva “estructura” es exactamente una comparación de identidad
    • La esencia de una clase de valor es que no debe encapsular estado; es decir, que es un contenedor de datos completamente transparente
    • Si no has usado Java en la práctica, puede que no sientas el verdadero significado de este cambio. Este es uno de esos raros cambios incompatibles que hace Java
      Java separa la verificación de identidad de un objeto y la verificación de igualdad. == básicamente mira si dos punteros son el mismo, y la igualdad es un concepto subjetivo basado en interfaces como equals/hashCode
      Por eso new Integer(1000) == new Integer(1000) antes era false pero ahora pasa a ser true, y new Integer(1000).equals(new Integer(1000)) es true, mientras que new Integer(10) == new Long(10) antes era false pero ahora se vuelve un error de compilación
      En el Java anterior, los enteros por debajo de cierto valor solían sustituirse por un tipo canonizado, y si no recuerdo mal era algo cerca de 128. De ahí venía la diferencia entre 10 y 1000
      Ahora parece que esas comparaciones hacen un unboxing implícito. Viendo que la comparación Integer/Long antes era false y ahora da error de compilación, está claro que interviene el unboxing
      Puede que con variables todavía se pueda obtener el comportamiento anterior
      En cualquier caso, cuando las clases de valor pierden identidad, == pasa de significar igualdad de punteros a igualdad bit a bit. Espero que resuelvan varios de esos casos límite, pero técnicamente sí es un cambio incompatible
  • Entiendo la intención de las clases de valor, pero la implementación tiene fallas
    ¿Qué imprimiría el siguiente código? Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);
    Hasta ahora la respuesta era obvia, pero si se agregan clases de valor, la respuesta cambia según si Point es una clase de valor o una clase por referencia. Así que este diseño perjudica la legibilidad
    Esto viola el principio de uniformidad. En The Psychology of Computer Programming de Weinberg, la uniformidad se explica como el principio psicológico de que el usuario espera que las cosas que se ven parecidas se comporten de forma parecida, y las que se ven distintas, de forma distinta
    Cuando un lenguaje de programación permite que dos construcciones casi idénticas en el punto de uso tengan comportamientos semánticamente distintos, aumenta la carga cognitiva del lector. Para saber si asignación, igualdad, identidad y mutación se comportan como en un objeto de referencia normal o como un valor, hay que revisar la declaración del tipo o depender de herramientas
    Se habría podido arreglar exigiendo la palabra clave value también en el punto de uso, no solo en la declaración. Por ejemplo, algo como value Point a = new Point(10, 10);

    • Viendo los objetivos de JEP 401, eso no es posible. La idea de las clases de valor es permitir que el desarrollador elija un modelo de programación para datos inmutables
      https://openjdk.org/jeps/401
    • Si lees el artículo, la tercera línea es un error de sintaxis. Todos los campos de un tipo de valor son final
      Claro, solo viendo esas cuatro líneas no hay forma de saber que eso ocurriría, pero ese problema ya existe hoy. Si Point fuera también un record, pasaría lo mismo
    • La sentencia a.x = 100; no debería ser válida. Los tipos record son inmutables
      Por lo tanto, la situación que preocupa debería ser imposible
    • Aun así, sigue siendo algo borroso
      Si se escribiera como value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x);, quedaría mucho más claro que ocurre una clonación/copia y que cambiar un campo no afecta el campo de otro objeto
  • Ya sé que en el mundo Java casi es de mala educación reconocer que existe .NET, pero me pregunto en qué se diferencia esto de un struct de .NET
    Viendo por encima tipos de valor, especialización de genéricos y boxing, parece que tomaron decisiones parecidas

    • En C# sí hay bastantes trampas en la práctica, y Java apunta explícitamente a hacerlo más claro
      Si C# en gran medida copió a C desde una perspectiva de bajo nivel, del lado de Java lo abordaron desde un nivel más alto y analizaron en detalle qué restricciones aportan qué ventajas
      Mientras que en otros lenguajes la clasificación struct/class es binaria, Java permite un control más fino que refleja la semántica del dominio subyacente
      Y se ha visto que los structs tienen varias armas apuntadas al pie, especialmente en contextos paralelos
    • Hay una sección del artículo que habla de eso
      Personalmente, veo los structs de C/C# como mutables y pasados por copia, mientras que las clases de valor no se pueden mutar y se pasan por valor
      No creo que en Java se pueda hacer asignación en stack
    • Funcionalmente no es distinto; Java simplemente está poniéndose al día con una práctica ya antigua
      Esa falsa dicotomía de que “los structs de C# tienen identidad y mutabilidad, por lo que hay que definir con precisión la semántica de copia al asignar o pasar, y por eso imponen al programador un modelo más pesado y le dan menos libertad al runtime” no encaja muy bien con lo que se está explicando
      Puede que no haya identidad en el sentido de la semántica de referencia de una clase Java, pero en el sentido de una estructura de memoria única en una dirección concreta, por supuesto que sigue habiendo identidad. Esto se parece más a discutir por terminología propia de Java
  • La nota al pie 6, “¿En qué se diferencia de struct en C#?”, es inexacta
    Viendo que el artículo está lleno de imágenes generadas por IA, da la impresión de que también hay bastante alucinación metida en la redacción, o al menos en el proceso de investigación

  • El artículo es algo borroso y dramático, pero por suerte los documentos originales son bastante fáciles de leer
    Página principal: https://openjdk.org/projects/jdk/28/spec/
    Estado de los JEP: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
    Ojalá alguien le diera seguimiento a los avances relacionados en C#, Swift, Java y Rust. Todos han estado compitiendo por ponerse al día con el hardware y creo que se influyen entre sí
    Personalmente, me preocupa qué impacto tendrán estos cambios en el uso compartido de memoria con FFI