Restricción de nulidad y tipos anulables
(bugs.openjdk.org)- Java se prepara para introducir una función preliminar del lenguaje que añade marcadores de nulidad a los tipos:
Foo!rechazanullyFoo?lo permite de forma intencional Foosin marcador deja la nulidad como no especificada para mantener compatibilidad con el código existente, y las conversiones entre tipos con distinta nulidad pueden generar advertencias o comprobaciones en tiempo de ejecución- Los campos y arreglos que rechazan
nullno pueden usarnullcomo valor predeterminado inicial, por lo que los campos de instancia deben quedar asignados de forma definitiva antes de la llamada asuper(...)y los arreglos necesitan un valor inicial para sus componentes - Si
nullse estrecha aFoo!, se produce unaNullPointerException, y en las rutas de almacenamiento en arreglos y campos se evita la contaminación con ArrayStoreException y FieldStoreException - Optimizaciones como el aplanamiento de value class de Valhalla dependen de poder confiar en la exclusión de
null, y por ahora no se busca aplicar esto a la biblioteca estándar ni reinterpretar automáticamente el código existente
Función preliminar para explicitar la nulidad en los tipos de Java
- Java expresará si
nullforma parte del conjunto de valores de un tipo agregando marcadores de nulidad a los tipos Foo!es un tipo null-restricted, que excluyenullde su conjunto de valoresFoo?es un tipo nullable, que incluye intencionalmentenullen su conjunto de valoresFoosin marcador tiene nulidad unspecified; puede aparecernull, pero no se sabe si su presencia es intencional- Esta es una función preliminar que se activa con las banderas de compilación y ejecución
--enable-preview
Objetivos y no objetivos
- Expresar si los tipos por referencia de Java esperan
nully ofrecer advertencias y comprobaciones para conversiones entre tipos con distinta nulidad - Debe ser compatible con el código Java existente y poder introducirse gradualmente sin generar incompatibilidades de código fuente ni binarias
- Las variables de tipos que rechazan
nulldeben inicializarse antes de ser leídas por primera vez, y el rechazo denulltambién debe aplicarse en tiempo de ejecución incluso entre clases compiladas por separado - Las optimizaciones de ejecución, como el aplanamiento de value class de Valhalla, deben contar con metadatos y garantías de integridad para poder confiar en los tipos con restricción de nulidad
- Lo siguiente no es objetivo por ahora
- No reinterpretar automáticamente el código existente
- No convertir todos los posibles
nullen errores de compilación - No agregar formas anulables a tipos primitivos como
int - No aplicar todavía esta extensión del lenguaje a la biblioteca estándar
Por qué hace falta
- Una variable
Stringen Java puede contener una referencia a un objetoStringonull, pero el lenguaje no ofrece una forma de expresar cuál de las dos opciones era la intención - Muchos programas asumen que no habrá
null, pero para imponerlo de forma consistente entre la especificación en Javadoc y el código de implementación hace falta trabajo adicional - Cuando esa expectativa se rompe, un valor
nullpuede propagarse por el código y terminar provocando una excepción lejos del punto donde se originó el bug - Si el desarrollador puede expresar como parte del tipo su intención de rechazar o permitir
null, la retroalimentación en compilación y las comprobaciones en ejecución ayudan a encontrar antes losnullinesperados - En Valhalla, una variable de tipo value class puede optimizarse con una representación aplanada del valor, pero si hace falta un bit adicional para codificar
null, eso puede aumentar el uso de memoria o impedir optimizaciones de almacenamiento - En Amber, la nulidad de los candidatos de pattern matching puede afectar la exhaustividad de
switch, y la nulidad de un patrón de tipo puede determinar si coincide o no connull
Sintaxis de marcadores de nulidad y estructura de tipos
- La nulidad se trata como una parte intrínseca del tipo, y
Foo?yFooson tipos distintos porque tienen nulidad diferente - Tanto el tipo del arreglo como el tipo de sus componentes pueden llevar marcador de nulidad
Foo?[]!es un tipo donde el arreglo en sí es null-restricted y los componentes sonFoonullable- En arreglos multidimensionales, se puede poner un marcador después de cada par de corchetes, y por convención se interpreta de izquierda a derecha, de afuera hacia adentro
- Los tipos parametrizados y los argumentos de tipo también pueden llevar marcadores de nulidad
Predicate!<Foo?>es unPredicatenull-restricted cuyo argumento de tipo esFoonullable
- Para expresar un tipo con restricción o con permiso de
null,!o?debe aparecer explícitamente en el código fuente - A futuro podría evaluarse interpretar por defecto todos los tipos de una clase o unidad de compilación como null-restricted y usar
?solo como excepción, pero eso se trataría por separado
Reglas de inicialización para campos y arreglos
- En Java actual, el valor predeterminado de los campos por referencia y de los componentes de arreglos es
null, pero eso no sirve como valor inicial para campos o componentes null-restricted - Los campos y arreglos null-restricted siempre deben ser inicializados por el programa antes de ser leídos
- Si un campo de instancia null-restricted no tiene inicializador, debe quedar asignado de forma definitiva antes de la llamada explícita o implícita a
super(...)en cada constructor- Flexible Constructor Bodies JEP permite escribir al inicio del constructor el código de inicialización necesario
- En ese contexto de construcción temprana no se permiten operaciones que hagan referencia a
thiso que puedan leer campos aún no inicializados
- Si un campo de instancia null-restricted sí tiene inicializador, este se ejecuta al inicio de cada constructor antes de la llamada a
super(...)- Un constructor que llama a
this(...)sigue siendo un caso especial y no ejecuta el inicializador, como en las reglas actuales
- Un constructor que llama a
- Los campos estáticos null-restricted deben quedar asignados de forma definitiva cuando terminen todos los inicializadores y bloques de inicialización estáticos de la clase
- Si otra clase intenta leer ese campo durante la inicialización de la clase, una comprobación en tiempo de ejecución detectará la lectura prematura y lanzará una excepción
- Un arreglo con tipo de componente null-restricted debe proporcionar un valor inicial para cada componente en la expresión de creación del arreglo
- Se pueden enumerar todos los valores con un inicializador de arreglo
- También podría existir una nueva sintaxis abreviada, aunque por ahora sigue TBD
Nulidad de expresiones y conversiones de nulidad
- El compilador de Java determina la nulidad de cada expresión durante la verificación de tipos
- La nulidad de una referencia de variable proviene de la declaración de la variable, y la nulidad de una llamada a método proviene del tipo de retorno del método referenciado
- El literal
nulles nullable - La mayoría de las demás expresiones de tipo por referencia son null-restricted
- Incluye literales, concatenación de cadenas,
this, creación de instancias de clase, creación de arreglos, referencias a métodos y expresiones lambda
- Incluye literales, concatenación de cadenas,
- Las conversiones de nulidad se permiten en contextos de asignación, invocación y cast
- La conversión de nulidad de ampliación incluye
Foo!→Foo?Foo!→Foono especificadoFoo?→Foono especificadoFoono especificado →Foo?
- La conversión de nulidad de estrechamiento incluye
Foo?→Foo!Foono especificado →Foo!
- Al igual que el unboxing, el compilador realiza automáticamente la conversión de nulidad de estrechamiento, pero en ejecución hace una comprobación dinámica que puede lanzar
NullPointerException - Intentar convertir directamente el literal
nulla un tipo null-restricted es un error de compilación
Comprobaciones en tiempo de ejecución y excepciones
- Si en tiempo de ejecución un valor
nullse convierte por estrechamiento a un tipo null-restricted, se produce una NullPointerException - También pueden ocurrir durante la ejecución conversiones de nulidad de estrechamiento que no son visibles de forma explícita en el código fuente
- Un arreglo cuyo tipo de componente fue asignado como null-restricted seguirá rechazando valores
nullmediante la comprobación normal de almacenamiento en arreglos, incluso si en el código fuente se trata con un tipo menos específico- Ese fallo de conversión provoca ArrayStoreException
- Si un campo que no era null-restricted al momento de compilar luego pasa a serlo mediante compilación separada, una nueva comprobación de almacenamiento en campo rechazará guardar
null- Ese fallo de conversión provoca FieldStoreException
- En llamadas a métodos dentro de una relación de override, puede ocurrir primero una conversión al tipo de invocación del parámetro en el método superior y luego otra al tipo de parámetro del método que sobrescribe
- El valor de retorno de un método también puede convertirse primero al tipo de retorno declarado del método y luego al tipo de retorno esperado en el punto de llamada
Genéricos, argumentos de tipo y override
- Los usos de variables de tipo también pueden llevar marcadores de nulidad:
T!es un tipo null-restricted yT?es un tipo nullable - Los tipos de variables null-restricted y nullable permiten afirmar una nulidad específica dentro del código genérico
- Los tipos usados como argumentos de tipo también pueden expresar nulidad, y un marcador de nulidad colocado sobre un tipo de variable de tipo sobrescribe la nulidad afirmada por el argumento de tipo
- Dentro de la implementación erased de una API genérica no se puede imponer la restricción de nulidad
- Aun así, los casts implícitos habituales en el límite de una API genérica sí hacen cumplir en tiempo de ejecución los argumentos de tipo null-restricted
- Por interoperabilidad, la nulidad dentro de los argumentos de tipo no se aplica con fuerza total
Predicate<String!>puede convertirse aPredicate<String>oPredicate<String?>- Esa conversión de nulidad sin comprobación puede generar advertencias
- Cambiar la nulidad del tipo de componente de un arreglo también se permite como una conversión de nulidad sin comprobación, y sigue TBD en qué condiciones habrá verificaciones en tiempo de ejecución
- Al determinar la igualdad de firmas de métodos, la nulidad se ignora
- Un método puede sobrescribir a otro aunque no coincidan las nulidades de los parámetros o del tipo de retorno
- Si distintas APIs introducen marcadores de nulidad de manera independiente, estas discrepancias pueden ser comunes
- La nulidad no afecta la aplicabilidad de métodos ni puede hacer fallar la inferencia de argumentos de tipo, aunque sí puede influir en la nulidad inferida para el tipo de retorno de métodos genéricos
- Los detalles del algoritmo de inferencia siguen TBD
Advertencias y errores del compilador
- Convertir un tipo en null-restricted puede introducir nuevos errores de compilación
- Cuando campos o arreglos de ese tipo no están inicializados
- Cuando se intenta convertir el literal
nulla ese tipo - Comparar el literal
nullcon una expresión de tipo null-restricted también podría ser error de compilación
- Fuera de eso, el análisis de nulidad es complementario y no genera errores de compilación
javacofrecerá advertencias para ayudar a evitar errores en tiempo de ejecución, y se recomienda que los IDE y otras herramientas de análisis sigan la misma dirección- Posibles causas de advertencia
- Conversiones de nulidad de estrechamiento, incluidas las que provienen de tipos
? - Usar una expresión de tipo
?para acceder a miembros u otras operaciones hostiles anull - Casos donde la nulidad de un argumento de tipo no es consistente con su bound
- Casos donde la nulidad de parámetros o retorno de un método no coincide con la del método sobrescrito
- Conversiones sin comprobación que cambian la nulidad de un tipo
- Conversiones de nulidad de estrechamiento, incluidas las que provienen de tipos
Archivos de clase, reflexión y cambios auxiliares
- La mayoría de los usos de marcadores de nulidad se eliminan en el archivo
class, y las conversiones de ejecución asociadas se representan directamente en el bytecode - La gramática del atributo
Signaturese actualizará para permitir!y?dentro de los tipos - La nulidad no se codifica en los descriptor de métodos y campos
- Para evitar contaminación de campos, un nuevo atributo
NullRestrictedindicará que el campo no acepta valoresnull- Ese campo también deberá marcarse con
ACC_STRICTy tendrá que inicializarse de forma estricta - El verificador comprobará que todos los campos de instancia con inicialización estricta estén asignados cuando el constructor llegue al punto de llamar a
super(...) - Todo intento de escribir en el campo comprobará si el valor es
nully, si lo es, lanzaráFieldStoreException
- Ese campo también deberá marcarse con
- La creación de arreglos null-restricted no será compatible con la instrucción
anewarrayy deberá hacerse mediante llamadas a la API de reflexión - No existirán literales
Foo!.classniFoo?.class, ni instancias correspondientes dejava.lang.Class - Una nueva RuntimeType API describirá los conjuntos de tipos que se hacen cumplir en tiempo de ejecución en las comprobaciones de almacenamiento en arreglos y campos, incluidas las variantes null-restricted de todos los tipos de clase e interfaz
- La API
Fieldpermitirá consultar elRuntimeTypede un campo, y ese valor podrá diferir del resultado degetType - La API
Arrayadmitirá una variante denewInstanceque exprese el tipo de componente conRuntimeType- Esa variante también permitirá proporcionar valores iniciales para los componentes del arreglo
- Rechazará intentos de crear arreglos null-restricted sin valores iniciales
- La deserialización tradicional no es compatible con campos y arreglos null-restricted, y un JEP aparte ofrecerá un mecanismo de serialización que no exponga campos ni arreglos null-restricted sin inicializar
- La documentación generada por
javadocincluirá los marcadores de nulidad - Las APIs
java.lang.reflect.Typeyjavax.lang.modelcodificarán la nulidad en la representación de tipos
Alternativas y dependencias
- Varias herramientas del ecosistema Java ya implementan su propio seguimiento de
nullen compilación, pero como no cambian el lenguaje Java, su sintaxis suele limitarse a anotaciones y su impacto queda restringido a comprobaciones en compilación - Otros lenguajes rastrean la nulidad en el sistema de tipos, y muchos de ellos son null-restricted por defecto y consideran error asignar a un tipo null-restricted sin una comprobación explícita de
null - En Java, esta función debe ser opcional y utilizable de manera gradual sin exigir una migración masiva única
- La imposición de nulidad en tiempo de ejecución también puede implementarse con comprobaciones explícitas o llamadas a
Objects.requireNonNull- Pero aplicarlo de forma consistente es engorroso, requiere documentación adicional y perjudica la legibilidad del programa
- Tampoco hay una manera de aplicarlo directamente a almacenamientos de variables como campos y arreglos
- El prerrequisito es Flexible Constructor Bodies (Second Preview)
- Permite que un constructor ejecute sentencias y asigne campos de instancia antes de la llamada a
super(...), lo que hace posible la exigencia de inicialización para campos null-restricted
- Permite que un constructor ejecute sentencias y asigne campos de instancia antes de la llamada a
- Entre los trabajos futuros están
- Null-Restricted Value Class Types (Preview): optimización de codificación para campos y arreglos tipados con value class null-restricted
- JEP 402: Enhanced Primitive Boxing (Preview): seguimiento de nulidad cuando el lenguaje use conversiones implícitas de boxing de manera más amplia
- Especialización de clases y métodos de la JVM (JEP 218 y su revisión): materializar y hacer cumplir la nulidad de algunos argumentos de tipo
- Otras posibles mejoras futuras incluyen aplicar marcadores de nulidad a partes de la API estándar, una representación más compacta de
null checken bytecode, una imposición de bajo nivel más fuerte para parámetros de métodos null-restricted y mecanismos del lenguaje para afirmar implícitamente que todos los tipos en ciertos contextos son null-restricted
1 comentarios
Opiniones de Hacker News
Es interesante en qué se diferencia este enfoque del que introdujo C# hace algunos años. En C#, si activas la nulabilidad en un proyecto, todas las variables se declaran como no nulas a menos que las marques explícitamente como nullable; en esta propuesta, las variables existentes pasan a ser, en la práctica, una de tres opciones: nullable, explícitamente nullable o explícitamente non-nullable.
Kotlin también es un lenguaje de la JVM, pero como C#, considera que algo es non-null si no se indica lo contrario, aunque Kotlin no tiene la carga de la compatibilidad hacia atrás. También ofrece una vía de escape con
lateinit var, que permite dejar vacío un tipo non-nullable hasta inicializarlo en otro método, y lanza una excepción específica si se accede antes de la inicialización.Me pregunto por qué pusieron tres opciones. ¿No bastaría con mantener como nullable las variables sin anotación y hacer explícitamente non-null solo las variables anotadas? Si, aunque no pongas nada, automáticamente es nullable, no se me ocurre por qué alguien querría declarar algo como nullable.
Me gusta más el enfoque de C#, pero este tiene la ventaja de poder usarse en una base de código legacy sin resolver todos los problemas de nulabilidad. En cambio, C# expone de inmediato los problemas de nulabilidad, mientras que esta propuesta permite mantenerlos ocultos como hasta ahora.
Además, me parece rara la parte de que “un método puede sobrescribir otro aunque no coincida la nulabilidad de sus parámetros y valores de retorno”. Parece una trampa peligrosa al sobrescribir/implementar callbacks: el método original especifica un retorno non-null, pero se termina devolviendo null.
Por falta de tiempo, por ahora elegimos solo activar las anotaciones salvo en algunos puntos clave, pero seguimos manteniendo como marcas los
NotNullAttributeyCanBeNullAttributede JetBrains para ver de inmediato dónde se tomó una decisión consciente. El segundo podría eliminarse porque nullable ya tiene una marca explícita, pero el primero choca en nombre con la funcionalidad propia de C#.En ese sentido, las tres opciones son bastante deseables. Cuando el código tiene cientos de miles de líneas, no es fácil migrarlo rápido.
En otros proyectos internos vamos ampliando gradualmente el alcance de la nulabilidad poniendo
#nullable enablealrededor del código que tocamos. También tenemos la condición de que el código nuevo debe estar en un contexto nullable. Esto también sirve para explicitar las partes ya anotadas, pero es un enfoque viable con una base de código y un equipo mucho más chicos.String!.El término “tipo de plataforma” y el símbolo de exclamación son confusos, pero fuera de eso el enfoque de Kotlin funciona bastante bien. Como Kotlin tuvo nulabilidad desde el principio, los programadores no pueden especificar tipos de plataforma directamente, pero siguen siendo necesarios para interoperar con plataformas base donde la nulabilidad es ambigua, como la JVM o JavaScript.
En este enfoque, el valor predeterminado sigue siendo razonable. Por defecto, siempre debería ser non-nullable, y quien piense distinto no aprendió nada de Tony Hoare. Al mismo tiempo, también se mantiene la compatibilidad hacia atrás. Para Kotlin fue relativamente fácil; Java y C# también deben preservar la compatibilidad con el código fuente existente.
Ni el enfoque de Java ni el de C# son ideales. El de C# cambia bastante el comportamiento del código según un flag del compilador, y el de Java convierte la peor opción en el valor predeterminado.
Aun así, me inclino más por C#. Si haces que “probablemente puede ser null” sea la opción más fácil, la mayoría de los programadores la elegirá por defecto. Sobre todo en un lenguaje tan orientado a empresas como Java, probablemente será así. Los linters y las advertencias del compilador ayudarán a largo plazo, pero creo que pasarán muchos años antes de que la mayoría del código Java anote correctamente la nulabilidad. Los usuarios de C# sufrirán más en el corto plazo, pero es mucho más probable que lleguen antes al objetivo de una nulabilidad clara.
https://kotlinlang.org/docs/java-interop.html#null-safety-an...
https://www.infoq.com/presentations/Null-References-The-Bill...
T?significara nullable,T!significara non-nullable, y que para elTnormal en código fuente recién compilado hubiera alguna declaración parecida a un pragma que fijara el valor predeterminado por archivo fuente, por archivopackage-infoo globalmente mediante un switch del compilador.Luego, en una versión LTS posterior de Java, podrían cambiar el valor predeterminado global. Eso facilitaría la transición de proyectos, permitiría insertar/actualizar pragmas automáticamente y, si hiciera falta, mantener también el valor predeterminado anterior.
Además, si se hubieran aprovechado las anotaciones existentes
@Nonnullde JSR-305 como forma de indicar apariciones de tipos non-nullable en archivos de clase, también se podría haber ofrecido compatibilidad bidireccional con JDK antiguos.!”.Creo que bastaría con algo como un flag del compilador o una etiqueta de módulo.
Aun así, supongo que aparecerá un flag del compilador que, si no se especifica otra cosa, asuma automáticamente non-nullable.
Se ve bien. Por fin hay una forma, a nivel del lenguaje, de eliminar miles de excepciones innecesarias y verificaciones de null. Pero la conversión automática que acota si algo es null se siente mal
En los ejemplos de la propuesta,
String? id(String! arg) { return arg; },String s = null;,Object! o1 = s; // NPE,Object o2 = id(s); // NPE,Object o3 = (String!) s; // NPE, me parece que al menos los dos primeros casos deberían ser errores de compilaciónEl último es ambiguo porque es explícito, pero preferiría que, dentro de
if (s != null), el compilador entienda que el tipo efectivo esString!y permitaString! ss = s;. Así no habría posibilidad de errorDe hecho, prefiero usar
Objects.requireNonNull(s)antes que el cast del último caso, porque es más explícito. Eso sí, estaría bueno tener algo comoObjects.unsafeForceNonNull(s), que omita la verificación explícita y solo excluya los casos bloqueados por optimización. Con un método unsafe, se podría implementarrequireNonNulldirectamente sin agregar análisis estático complejoLamentablemente, esto solo se comprobará en tiempo de ejecución
Por ejemplo, la biblioteca estándar indica explícitamente que, al menos por ahora, no migrará a tipos null
Parece imprescindible tener una forma de marcar todas las variables como non-null por defecto a nivel de paquete o, al menos, de archivo. De lo contrario, por seguridad se va a imponer la idea de usar la sintaxis
T!en casi todas las variables, y eso solo generará mucho ruido!”@NullEl problema solo aparece en los límites del código que interactúa con bibliotecas. Esta nueva sintaxis probablemente sea parecida
Es una lástima la parte que dice “por ahora, no es un objetivo aplicar la mejora del lenguaje a la biblioteca estándar”
Por experiencia de haber tenido que usar PHP, es molesto tener que quitar o volver a agregar propiedades ya garantizadas de los datos cada vez que uno interactúa con una biblioteca estándar enorme
Java también debería incorporar esta expresividad de forma más activa en la biblioteca estándar y convertirla en ciudadana de primera clase
Optionalse vuelve una carga frente a la propuesta de nullable/non-nullable explícito. Los tipos record también podrían haber sido non-nullable por defectoAdemás, dividirlo en dos etapas permite lanzar esta funcionalidad como preview con más facilidad y, tras recibir feedback, incorporarla al diseño de forma definitiva. Si intentan hacerlo todo de una vez, casi no quedará margen para iterar sobre la funcionalidad en la práctica
Me gustaría que esta funcionalidad llegara a Java. La posibilidad de elección explícita a nivel del lenguaje, como
T?, mejoró mucho la calidad de vida de los desarrolladores en Kotlin y TypeScript. En Java existen herramientas como NullAway, pero son engorrosasCreo que el soporte a nivel del lenguaje es mucho mejor que
Optional/Maybe, porque permite concentrarse en la lógica real en vez de poner el código sobre rieles demap/flatMaphttps://github.com/uber/NullAway
Mejor usar otro lenguaje de la JVM, ¿no?
Como el sitio está caído ahora mismo, dejo un enlace archivado: https://web.archive.org/web/20240802081039/https://bugs.open...
“Exigir que el programa maneje explícitamente todos los valores
nullque puedan ocurrir no es el objetivo, y los valoresnullno manejados pueden generar advertencias en tiempo de compilación, pero no errores” es una mala decisión.Java es, en su mayor parte, un lenguaje de tipado estático; no entiendo por qué meter otro comportamiento dinámico. Espero que haya una forma sencilla de elevar esas advertencias a errores.
nullen todas partes. Con esta nueva funcionalidad, solo se puede aplicar el manejo obligatorio denulla las operaciones relacionadas con los nuevos tipos, y por eso no se rompe nada.Los lenguajes cuyo sistema de tipos obliga a manejar
null/nilen todos los casos son mucho mejores. Pero Java, hoy, no es uno de esos lenguajes. Aun así, esto será una gran mejora.Es una lástima que estemos aprendiendo estas lecciones tan tarde. Por defecto debería ser no anulable, inmutable por defecto y con el alcance más estrecho por defecto.
En los diseños nuevos, con demasiada frecuencia se elige la comodidad inmediata en lugar de “hacer que el camino seguro sea el predeterminado”. Los valores predeterminados seguros requieren un diseño y una experiencia de usuario mucho más cuidadosos, pero el resultado es que casi todos los lenguajes, plataformas y tecnologías terminan llenos de trampas para dispararse en el pie. La ingeniería civil y la eléctrica tienen normativas, mientras que en software volvemos a aprender las mismas lecciones en nuevos lenguajes y tecnologías cada 30 años más o menos.
Java es como una aldea del tercer mundo que aprende que para matar gérmenes hay que hervir el agua.
La mayor parte de lo que hice en Facebook fue usar Hack. La nulabilidad es un elemento central del sistema de tipos de Hack y elimina muchísimos errores innecesarios.
Claro que eso no significa que nunca recibas un
nullinesperado. Como esta funcionalidad también se agregó al lenguaje más tarde, todavía había muchos tiposmixedheredados de sus raíces en PHP, lo que en la práctica significaba que podía ser cualquier cosa.Primero, me pregunto qué pasa con los arreglos anulables. El ejemplo
String![]muestra el caso en que el objeto puede sernull, pero ¿qué pasa con el arreglo en sí? En Java,String labels[] = null;es totalmente legal. Entonces, ¿habría que declararlo comoString![]! labels;?En Hack,
vec $foosignifica que nifooni sus elementos sonnull, y?vec $foosignifica que los elementos son no anulables perofoopuede sernull. En la práctica casi nunca hay una razón para usar un arreglonull, así que el valor predeterminado debería ser no anulable. El problema es que Java tiene todo el código heredado asumiendo la posibilidad denull.En los ejemplos de la propuesta
Object! o1 = s,Object o2 = id(s),Object o3 = (String!) s, me parece que el 2 y el 3 deberían ser errores de compilación.Por último, me gusta más el operador de coerción
asde Hack que el casteo de Java. Por ejemplo,foo($b)es un error de compilación,$b as Ada un error en tiempo de ejecución si esnull, y$a as ?Blo castea si esB; si no, devuelvenull.Al final, la pregunta es si esto se puede aplicar por encima al SDK de Java y cómo se verá en código heredado.
https://docs.hhvm.com/hack/types/nullable-types
String![]? labels = null;.Parece que todo lo bueno de Kotlin ahora está llegando a Java.
Aun así, prefiero seguir trabajando en Kotlin, donde no tengo que lidiar con cosas como Lombok. Los records de Java sí están buenos.