Guía rápida de inferencia de tipos locales para Java 10 y versiones posteriores
Stuart Marks
26 de abril de 2018
0 minutos de lectura
Te damos la bienvenida a la primera entrega de una nueva serie de guías rápidas que publicaremos en el blog de Snyk. Compartiremos contenido que podrás imprimir y colocar en un lugar visible para ayudarte a ser mejor desarrollador. En esta primera entrega, justo después del lanzamiento de Java 10, nos centraremos en la tan comentada inferencia de tipos para variables locales. Puedes descargar la versión en PDF haciendo clic en la imagen de arriba o en este enlace. La premisa principal de la función de inferencia de tipos locales es bastante sencilla: reemplazar el tipo explícito de la declaración por el nuevo nombre de tipo reservado «var» para que el tipo se infiera. Así, podríamos reemplazar:
por:
Y el tipo de outputStream se inferirá como ByteArrayOutputStream. Un momento, ¿estamos diciendo que Java ahora permite el tipado dinámico? ¡En absoluto! TODA inferencia de tipos ocurre en tiempo de compilación, y el compilador incorpora los tipos explícitos en el bytecode. En tiempo de ejecución, Java sigue siendo tan estático como siempre. Como su uso es muy sencillo, esta guía rápida se enfocará en el aspecto más importante de la inferencia de tipos locales: su uso práctico. Te orientará sobre cuándo usar tipado explícito y cuándo considerar la inferencia de tipos.
Desde que quise escribir esta guía rápida, Stuart Marks, ingeniero de JDK en Oracle, escribió el artículo perfecto, con principios de programación y recomendaciones sobre el uso de la inferencia de tipos. Así que, cuando quise crear una guía rápida, acudí directamente a Stuart para ver si podíamos incluir sus ideas y resumirlas en una guía para que los desarrolladores la tuvieran a mano y la usaran a diario. Te recomiendo encarecidamente leer el artículo completo de Stuart. ¡De verdad vale la pena!
Principios
1. Leer código > Escribir código.
Ya sea que te tome 10 minutos o 10 días escribir una línea de código, casi con toda seguridad la leerás durante muchos años. El código solo será fácil de mantener y comprender en el futuro si es claro, conciso y, sobre todo, contiene toda la información necesaria para entender su propósito. El objetivo es maximizar la facilidad de comprensión.
2. El código debe ser claro a partir del análisis local.
Incluye en el código toda la información posible para evitar que quien lo lea tenga que revisar distintas partes de la base de código para entender qué está pasando. Puedes lograrlo, por ejemplo, con los nombres de métodos o variables.
3. La legibilidad del código no debería depender de los IDE.
Los IDE pueden ser geniales. ¡Me refiero a realmente geniales! Pueden hacer que un desarrollador sea más productivo o más preciso al programar. El código debe poder leerse y entenderse sin depender de un IDE. A menudo se lee código fuera de un IDE. Además, los IDE pueden diferir en la cantidad de información que muestran. El código debe explicarse por sí mismo. Debe entenderse a simple vista, sin ayuda de herramientas.
La decisión es tuya.
Elegir entre asignar un tipo explícito a una variable o dejar que el compilador de Java lo deduzca es un equilibrio. Por un lado, quieres reducir el desorden, el código repetitivo y la formalidad. Por el otro, no quieres perjudicar la comprensión del código. La declaración del tipo no es la única manera de transmitir información a quien lee el código. También cuentan el nombre de la variable y la expresión de inicialización. Al decidir si está bien omitir el tipado explícito, debemos tener en cuenta todos los recursos disponibles para cada variable.
Recomendaciones
1. Elige nombres de variables que aporten información útil.
Esta es una buena práctica en general, pero es mucho más importante al usar var. En una declaración con var, el nombre de la variable puede transmitir información sobre su significado y uso. Reemplazar un tipo explícito por var suele ser una buena oportunidad para mejorar el nombre de la variable. A veces puede ser útil incluir el tipo de la variable en su nombre. Por ejemplo:
2. Reduce al mínimo el alcance de las variables locales.
La recomendación de limitar el alcance de las variables locales se describe en Effective Java (3.ª edición), punto 57. Es aún más importante si se usa var. El problema surge cuando la variable tiene un alcance amplio, es decir, cuando hay muchas líneas de código entre su declaración y su uso. A medida que se mantiene el código, los cambios de tipos y otros ajustes pueden generar comportamientos distintos. Por ejemplo, pasar de una List a un Set podría parecer correcto, pero ¿tu código depende del orden más adelante, dentro del mismo alcance? Aunque los tipos siempre se establecen de forma estática, las diferencias sutiles entre implementaciones que usan la misma interfaz pueden causar problemas. En lugar de simplemente evitar var en estos casos, conviene modificar el código para reducir el alcance de las variables locales y solo entonces declararlas con var. Considera el siguiente código:
Ahora este código tiene un error, porque los conjuntos no tienen un orden de iteración definido. Sin embargo, es probable que el programador corrija el error enseguida, ya que los usos de la variable items están junto a su declaración. Ahora supongamos que este código forma parte de un método extenso y que, por consiguiente, la variable items tiene un alcance amplio:
Ahora es mucho más difícil detectar este error, porque la línea que intenta agregar un elemento al final del conjunto no está lo suficientemente cerca de la declaración del tipo como para que el problema resulte evidente.
3. Considera usar var cuando la inicialización proporcione suficiente información.
Las variables locales suelen inicializarse con constructores. A menudo, el nombre de la clase que se instancia se repite como tipo explícito a la izquierda. Si el nombre del tipo es largo, usar var hace el código más conciso sin perder información:
También es razonable usar var cuando la inicialización es una llamada a un método, como Files.newBufferedReader(…) o List stringList = List.of("a", "b", "c").
4. Usa var para dividir expresiones encadenadas o anidadas en variables locales.
Considera el código que recibe una colección de cadenas y busca la que aparece con mayor frecuencia. Podría verse así:
Este código es correcto, pero resulta más legible si se distribuye en varias instrucciones. El problema de dividirlo en instrucciones, como se muestra aquí, es el siguiente.
Pero probablemente el autor evitó hacerlo porque el tipado explícito se ve extremadamente desordenado y distrae de lo importante del código. Con var podemos expresar el código de forma más natural sin pagar el alto costo de declarar explícitamente los tipos de las variables intermedias:
Es válido preferir el primer fragmento, con su larga cadena de llamadas a métodos. Sin embargo, en algunos casos conviene dividir las cadenas largas de métodos.
5. No te preocupes demasiado por «programar para la interfaz» con variables locales.
Un modismo común en la programación con Java es crear una instancia de un tipo concreto y asignarla a una variable de tipo interfaz. Por ejemplo:
Sin embargo, si se usa var, se infiere el tipo concreto en lugar de la interfaz:
El código que usa la variable list ahora puede depender de la implementación concreta. Si en el futuro cambia la inicialización de la variable, también podría cambiar el tipo inferido y provocar errores o fallos en el código posterior que la utiliza.
Esto representa un problema menor si se sigue la recomendación 2: cuando el alcance de la variable local es pequeño, se limita el riesgo de que la implementación concreta se «filtre» y afecte al código posterior.
6. Ten cuidado al usar var con el diamante o con métodos genéricos.
Tanto var como la función «diamante» permiten omitir información de tipo explícita cuando esta puede deducirse de la información existente. Sin embargo, si se usan juntas, podrían omitir toda la información útil que el compilador necesita para inferir correctamente el tipo deseado.
Considera lo siguiente:
Los métodos genéricos también han aprovechado la inferencia de tipos con tanto éxito que es muy poco común que los programadores proporcionen argumentos de tipo explícitos. Si no hay argumentos reales para el método que aporten suficiente información de tipo, la inferencia de métodos genéricos depende del tipo de destino. En una declaración con var no hay tipo de destino, así que puede surgir un problema similar al del diamante. Por ejemplo:
Tanto con el diamante como con los métodos genéricos, se puede proporcionar información de tipo adicional mediante argumentos reales del constructor o del método, lo que permite inferir el tipo deseado. Esto agrega un nivel adicional de indirección, pero sigue siendo predecible. Por lo tanto:
7. Ten cuidado al usar var con literales.
Es poco probable que usar var con literales aporte una gran ventaja, ya que los nombres de los tipos suelen ser cortos. Sin embargo, var puede ser útil en algunos casos, por ejemplo, para alinear los nombres de las variables.
No hay ningún problema con los literales booleanos, de caracteres, long y de cadenas. El tipo que se infiere a partir de estos literales es preciso, por lo que el significado de var es inequívoco. Hay que tener especial cuidado cuando la inicialización es un valor numérico, en particular un literal entero. Si se especifica explícitamente el tipo a la izquierda, el valor numérico puede convertirse silenciosamente a tipos distintos de int, ya sea ampliándose o reduciéndose. Con var, el valor se inferirá como int, lo que quizá no sea lo deseado.
Conclusión
Usar var en las declaraciones puede mejorar el código al reducir el desorden y hacer que destaque la información más importante. Por otro lado, usar var indiscriminadamente puede empeorar las cosas. Si se usa correctamente, var puede mejorar un buen código: hacerlo más breve y claro sin perjudicar su comprensión. Al usar var, pregúntate si el código se volvió más ambiguo o si se entiende con claridad sin tener que investigarlo demasiado.
¡Descarga la guía rápida ahora!
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.


