Releases

Avance de las nuevas funcionalidades de lenguaje en Kotlin 1.4.30

Read this post in other languages:

Planeamos añadir nuevas funcionalidades de lenguaje en Kotlin 1.5, y ya puede probarlas en Kotlin 1.4.30:

Para probar estas nuevas funcionalidades, tiene que especificar la versión de lenguaje 1.5.

La nueva cadencia de lanzamientos significa que Kotlin 1.5 se publicará dentro de pocos meses, pero ya hay nuevas funcionalidades disponibles para probarlas en la versión 1.4.30. Sus comentarios iniciales son cruciales para nosotros, ¡así que no dude en probar estas funcionalidades nuevas ya!

Estabilización de clases inline value

Las clases inline llevan disponibles en Alpha desde Kotlin 1.3, y en la versión 1.4.30 avanzan a Beta.

Kotlin 1.5 estabiliza el concepto de clases inline, pero lo incluye como parte de una funcionalidad más general, las clases value, que describiremos más adelante en esta publicación.

Comenzaremos con un repaso del funcionamiento de las clases inline. Si ya está familiarizado con las clases inline, puede omitir esta sección e ir directamente a los nuevos cambios.

Como recordatorio rápido, una clase inline elimina un contenedor alrededor de un valor:

Una clase inline puede ser un contenedor tanto para un tipo primitivo como para cualquier tipo de referencia, como String.

El compilador reemplaza las instancias de clase inline (en nuestro ejemplo, la instancia Color) con el tipo subyacente (Int) en el bytecode, cuando es posible:

Entre bastidores, el compilador genera la función changeBackground con un nombre alterado que usa Int como un parámetro, y transmite la constante 255 directamente sin crear un contenedor en el sitio de la llamada:

El nombre se altera para permitir la sobrecarga continua de funciones que usan instancias de diferentes clases inline y para evitar invocaciones accidentales desde el código Java que podría infringir las restricciones internas de una clase inline. Lea a continuación para saber cómo hacer que se pueda usar en Java.

El contenedor no siempre se elimina en el bytecode. Esto ocurre solo cuando es posible, y funciona de manera muy similar a los tipos primitivos integrados. Cuando define una variable del tipo Color o la transmite directamente a una función, se reemplaza con el valor subyacente:

En este ejemplo, la variable color tiene el tipo Color durante la compilación, pero se reemplaza por Int en el bytecode.

Sin embargo, si la almacena en una colección o la transmite a una función genérica, se se le aplica la conversión boxing en un objeto regular del tipo Color:

El compilador aplica de forma automática la conversión boxing y unboxing. No necesita hacer nada al respecto, pero resulta útil comprender el funcionamiento interno.

Cambio del nombre JVM para las llamadas Java

A partir de la versión 1.4.30, puede cambiar el nombre JVM de una función que use una clase inline como parámetro para que sea utilizable en Java. De forma predeterminada, esos nombres se alteran para evitar utilizaciones accidentales de Java o sobrecargas conflictivas (como changeBackground-euwHqFQ en el ejemplo de arriba).

Si anota una función con @JvmName, cambia el nombre de esta función en el bytecode y permite que se pueda llamar desde Java y transmitir un valor directamente:

Como siempre con una función anotada con @JvmName, desde Kotlin la llama por su nombre de Kotlin. La utilización de Kotlin es segura para los tipos, ya que solo puede transmitir un valor del tipo Timeout como argumento, y las unidades son obvias por la utilización.

En Java, puede transmitir un valor long directamente. Ya no es seguro para los tipos, y por eso no funciona de forma predeterminada. Si ve greetAfterTimeout(2) en el código, no resulta obvio de inmediato si se refiere a 2 segundos, 2 milisegundos o 2 años.

Al proporcionar la anotación, hace hincapié explícitamente en que pretende que se llame a esta función desde Java. Un nombre descriptivo le ayuda a evitar confusiones: si añade el sufijo «Millis» al nombre JVM, las unidades serán claras para los usuarios de Java.

Bloques Init

Otra mejora para las clases inline en la versión 1.4.30 es que ahora puede definir la lógica de inicialización en el bloque init:

Esto estaba prohibido previamente.

Puede consultar más detalles sobre las clases inline en KEEP, en la documentación y en la conversación debajo de esta incidencia.

Clases inline value

Kotlin 1.5 estabiliza el concepto de clases inline y lo convierte en parte de una funcionalidad más general: clases value.

Hasta ahora, las clases «inline» constituían una funcionalidad de lenguaje independiente, pero ahora se están volviendo una optimización JVM específica para una clase value con un parámetro. Las clases value representan un concepto más general y admitirán diferentes optimizaciones: las clases inline ahora, y las clases primitivas Valhalla en el futuro cuando el proyecto Valhalla esté disponible (puede leer más información sobre esto abajo).

Lo único que cambia para usted en este momento es la sintaxis. Como una clase inline es un clase value optimizada, tiene que declararla de manera diferente a como lo hacía:

Usted define una clase value con un parámetro de constructor y la anota con @JvmInline. Esperamos que todo el mundo utilice esta nueva sintaxis a partir de Kotlin 1.5. La sintaxis inline class anterior seguirá funcionando durante un tiempo. Dejará de emplearse con una advertencia en la versión 1.5 que incluirá la opción de migrar todas sus declaraciones de forma automática. Posteriormente, quedará obsoleta y devolverá un error.

Clases value

Una clase value representa una entidad immutable con datos. Actualmente, una clase value puede contener solo una propiedad para admitir el caso de uso de las clases inline «antiguas».

En futuras versiones de Kotlin con compatibilidad total con esta funcionalidad, será posible definir clases value con muchas propiedades. Todos los valores deberían ser vals de solo lectura:

Las clases value no tienen identidad: están definidas completamente por los datos almacenados y no admiten las comprobaciones de identidad ===. La comprobación de igualdad == compara de forma automática los datos subyacentes.

Esta calidad «sin identidad» de las clases value permiten futuras optimizaciones considerables: la llegada del proyecto Valhalla a JVM permitirá la implementación de las clases value como clases primitivas JVM entre bastidores.

La restricción de inmutabilidad, y por ello la posibilidad de optimizaciones de Valhalla, hace que las clases value sean diferentes de las clases data.

Optimizaciones futuras de Valhalla

El proyecto Valhalla introduce un nuevo concepto para Java y JVM: las clases primitivas.

El objetivo principal de las clases primitivas es combinar primitivas con rendimiento con las ventajas orientadas a objetos de las clases JVM habituales. Las clases primitivas son poseedoras de datos cuyas instancias se pueden almacenar en variables, en la pila de computación, y operarse directamente, sin encabezados y pointers. A este respecto, son similares a los valores primitivos como int, long, etc. (en Kotlin, no trabaja con tipos primitivos directamente, sino que el compilador los genera entre bastidores).

Una ventaja importante de las clases primitivas es que permiten la disposición plana y densa de objetos en la memoria. Actualmente, Array<Point> es un conjunto de referencias. Con la compatibilidad con Valhalla, al definir Point como una clase primitiva (en terminología Java) o como una clase value con la optimización subyacente (en terminología Kotlin), JVM puede optimizarlo y almacenar un conjunto de Points en una distribución «plana», como un conjunto de muchas xs e ys directamente, no como un conjunto de referencias.

Estamos deseando que lleguen los próximos cambios en JVM y queremos que Kotlin los aproveche. Al mismo tiempo, no queremos obligar a nuestra comunidad a depender de las nuevas versiones de JVM para usar las clases value, por lo que vamos a admitirlas también para las versiones de JVM anteriores. Al compilar el código para JVM con compatibilidad con Valhalla, las optimizaciones JVM más recientes funcionarán para las clases value.

Métodos mutantes

Hay mucho más que decir sobre la funcionalidad de las clases value. Como las clases value representan datos «inmutables», los métodos mutantes, como los de Swift, son posibles para ellos. Un método mutante es cuando una función de miembro o setter de propiedad devuelve una nueva instancia en lugar de actualizar una existente, y la ventaja principal es que usted lo utiliza con una sintaxis familiar. Esto aún tiene que hacerse prototipo en el lenguaje.

Más detalles

La anotación @JvmInline es específica de JVM. En otros backends, las clases value pueden implementarse de manera diferente. Por ejemplo, como structs Swift en Kotlin/Native.

Puede leer los detalles sobre las clases value en Design Notes on Kotlin Value Classes, o ver un extracto de la charla «A look into the future» de Roman Elizarov.

Compatibilidad con los registros JVM

Otra mejora próxima en el ecosistema JVM son los registros Java. Son análogos a las clases data de Kotlin y son, básicamente, titulares de los datos.

Los registros Java no siguen la convención de JavaBeans, y tienen métodos x() and y() en lugar de los habituales getX() and getY().

La interoperabilidad con Java siempre ha sido y será una prioridad para Kotlin. Por ello, el código de Kotlin «comprende» los nuevos registros Java y los ve como clases con propiedades de Kotlin. Esto funciona como lo hace para las clases Java regulares siguiendo la convención JavaBeans:

Principalmente por motivos de interoperabilidad, puede anotar su clase data con @JvmRecord para generar nuevos métodos de registro JVM:

La anotación @JvmRecord hace que el compilador genere métodos x() y y() en lugar de los métodos estándar getX() y getY(). Asumimos que solo necesita utilizar esta anotación para preservar la API de la clase al convertirla de Java a Kotlin. En el resto de casos de uso, las clases data familiares de Kotlin se pueden utilizar sin problema.

Esta anotación solo está disponible si compila código Kotlin para la versión 15+ de JVM. Puede consultar más detalles sobre esta funcionalidad en KEEP o en la documentación, así como en la conversación de esta incidencia.

Mejoras en interfaces sealed y clases sealed

Cuando hace una clase sealed, restringe la jerarquía para definir subclases, lo que permite comprobaciones exhaustivas en las ramas when. En Kotlin 1.4, la jerarquía de clase sealed incluye dos restricciones. En primer lugar, la clase superior no puede ser una interfaz sealed, sino una clase. En segundo lugar, todas las subclases deberían estar localizadas en el mismo archivo.

Kotlin 1.5 elimina las dos restricciones: ahora puede hacer una interfaz sealed. Las subclases (tanto para clases sealed e interfaces sealed) deberían estar ubicadas en la misma unidad de compilación y en el mismo paquete que la clase superior, pero ahora pueden localizarse en diferentes archivos.

Las clases sealed, y ahora las interfaces, son útiles para definir jerarquías de tipo de datos abstractos (ADT).

Otro caso de uso importante que ahora se puede abordar bien con interfaces selladas es cerrar una interfaz para herencia e implementación fuera de la biblioteca. Definir una interfaz como sealed restringe su implementación a la misma unidad de compilación y el mismo paquete, lo que en el caso de una biblioteca impide la implementación fuera de la biblioteca.

Por ejemplo, la interfaz Job del paquete kotlinx.coroutines solo está destinado a implementarse dentro de la biblioteca kotlinx.coroutines. Al hacerla sellada con sealed se explicita esta intención:

Como usuario de la biblioteca, usted ya no tiene permiso para definir su propia subclase de Job. Esto siempre estaba «implícito», pero con las interfaces sealed, el compilador puede prohibirlo oficialmente.

Utilizar la compatibilidad con JVM en el futuro

La compatibilidad de avance de las clases sealed se ha introducido en Java 15 y en JVM. En el futuro, vamos a utilizar la compatibilidad natural con JVM para las clases sealed si compila código Kotlin para el JVM más reciente (probablemente JVM 17 o posterior cuando esta funcionalidad sea estable).

En Java, usted enumera explícitamente todas las subclases de la clase o interfaz sealed en cuestión:

Esta información se almacena en el archivo de clase con el nuevo atributo PermittedSubclasses. JVM reconoce las clases sealed en el tiempo de ejecución y evita su extensión por subclases no autorizadas.

En el futuro, cuando compile Kotlin al nuevo JVM, se utilizará esta nueva compatibilidad con JVM de las clases sealed. Entre bastidores, el compilador generará una lista de subclases permitidas en el bytecode para garantizar que haya compatibilidad con JVM y comprobaciones adicionales del tiempo de ejecución.

En Kotlin, no necesita especificar la lista de subclases. El compilador generará la lista sobre la base de las subclases declaradas en el mismo paquete.

La habilidad para especificar explícitamente las subclases para una clase superior o interfaz podría añadirse más adelante como una especificación opcional. Actualmente, sospechamos que no será necesario, pero estaremos encantados de conocer sus casos de uso y si necesita esta funcionalidad.

Tenga en cuenta que, para las versiones JVM más antiguas, es teóricamente posible definir una subclase Java para la interfaz sealed de Kotlin, pero ¡no lo haga! Dado que la compatibilidad de JVM con las subclases permitidas aún no está disponible, esta restricción solo la aplica el compilador de Kotlin. Añadiremos avisos del IDE para evitar que eso ocurra accidentalmente. En el futuro, el nuevo mecanismo se utilizará para las versiones JVM más recientes para garantizar que no haya subclases «no autorizadas» de Java.

Puede consultar más información sobre las interfaces sealed y las restricciones de clases sealed sueltas en KEEP o en la documentación, y ver la conversación de esta incidencia.

Cómo probar las nuevas funcionalidades

Necesita utilizar Kotlin 1.4.30. Especifique la versión de lenguaje 1.5 para habilitar las nuevas funcionalidades:

Para probar los registros JVM, también necesita utilizar jvmTarget 15 y habilitar las funcionalidades de avance de JVM: añada las opciones de compilador -language-version 1.5 y -Xjvm-enable-preview.

Notas previas al lanzamiento

Tenga en cuenta que la compatibilidad con las nuevas funcionalidades es experimental y la compatibilidad con la versión de lenguaje 1.5 está en estado previo al lanzamiento. Configurar la versión de lenguaje a 1.5 en el compilador Kotlin 1.4.30 es equivalente a probar el avance 1.5-M0. Las garantías de compatibilidad con versiones anteriores no cubren las versiones previas al lanzamiento. Las funcionalidades y la API pueden cambiar en lanzamientos posteriores. Cuando llegamos a un Kotlin 1.5-RC final, el compilador expulsará todos los binarios producidos por las versiones previas al lanzamiento y se le pedirá que vuelva a compilar todo lo que compiló con la 1.5‑Mx.

Envíenos sus comentarios

¡Pruebe las nuevas funcionalidades descritas en esta publicación y comparta su opinión! Puede obtener más información en KEEPs y participar en las conversaciones en YouTrack, y puede informar de nuevas incidencias si algo no le funciona correctamente. ¡Comparta sus resultados sobre el éxito de las nuevas funcionalidades para abordar los casos de uso de sus proyectos!

Más información y debates:

image description