La Inferencia de tipo variable local de Java (LVTI) o, brevemente, el tipo var (el identificador var no es una palabra clave, sino un nombre de tipo reservado) se agregó a Java 10 usando JEP 286: Inferencia de tipo variable local . Al ser una función de compilación del 100%, no afecta el código de bytes, el tiempo de ejecución o el rendimiento. Básicamente, el compilador verifica el lado derecho del operador de asignación y, en base a él, determina el tipo específico de la variable y luego lo reemplaza con var .
Además, es útil para reducir la verbosidad del código repetitivo, y también acelera el proceso de programación en sí. Por ejemplo, es muy conveniente escribir var evenAndOdd =...
lugar de Map<Boolean, List<Integer>> evenAndOdd =...
La aparición de var no significa que siempre sea conveniente usarlo en todas partes, a veces será más práctico hacerlo con herramientas estándar.
En este artículo, veremos 26 situaciones, con ejemplos de cuándo puede usar var y cuándo no vale la pena.
Punto 1: intente dar nombres significativos a las variables locales
Por lo general, nos enfocamos en dar los nombres correctos a los campos de las clases, pero no prestamos la misma atención a los nombres de las variables locales. Cuando nuestros métodos se implementan perfectamente, contienen poco código y tienen buenos nombres, muy a menudo no prestamos atención a las variables locales, ni siquiera reducimos por completo sus nombres.
Cuando usamos var en lugar de escribir tipos explícitos, el compilador los detecta automáticamente y sustituye var . Pero, por otro lado, como resultado de esto, se hace más difícil para las personas leer y comprender el código, ya que el uso de var puede complicar su legibilidad y comprensión. En la mayoría de los casos, esto se debe a que tendemos a ver el tipo de una variable como información primaria y su nombre como secundario. Aunque debería ser todo lo contrario.
Ejemplo 1:
Muchos probablemente estarán de acuerdo en que en el siguiente ejemplo, los nombres de las variables locales son demasiado cortos:
Cuando se usan nombres cortos, junto con var , el código se vuelve aún menos claro:
Opción más preferida:
Ejemplo 2
Evite nombrar variables como esta:
Use nombres más significativos:
Ejemplo 3
En un esfuerzo por dar nombres más comprensibles a las variables locales, no vaya a extremos:
En cambio, puede usar una opción más corta, pero no menos comprensible:
¿Sabía que Java tiene una clase interna llamada:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Bueno, nombrar variables con este tipo puede ser complicado :)
Punto 2: use literales para ayudar a var a determinar el tipo de primitivo (int, long, float, double)
Sin el uso de literales para tipos primitivos, podemos encontrar que los tipos esperados e implícitos pueden diferir. Esto es causado por la conversión de tipo implícita utilizada por las variables var .
Por ejemplo, los siguientes dos fragmentos de código se comportan como se esperaba. Aquí declaramos explícitamente los tipos booleano y char :
boolean flag = true;
Ahora usamos var , en lugar de declarar explícitamente los tipos:
var flag = true;
Hasta ahora todo bien. Ahora haga lo mismo para los tipos int , long , float y double :
int intNumber = 20;
Aunque el fragmento de código anterior es simple y directo, ahora usemos var en lugar de especificar explícitamente los tipos.
Evitar
Las cuatro variables se mostrarán como int . Para corregir este comportamiento, necesitamos usar literales Java:
Pero, ¿qué sucede si declaramos un número decimal?
Evite esto si espera obtener una variable de tipo float :
Para evitar sorpresas, use el literal apropiado:
Punto 3: en algunos casos, las conversiones de tipo var e implícito pueden simplificar el soporte de código
Por ejemplo, supongamos que nuestro código está entre dos métodos. Un método obtiene un carrito de compras con diferentes productos y calcula el mejor precio. Para hacer esto, compara varios precios en el mercado y devuelve el precio total en forma de tipo flotante . Otro método simplemente deduce este precio de la tarjeta.
Primero, veamos un método que calcula el mejor precio:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
En segundo lugar, echemos un vistazo al método que funciona con el mapa:
public boolean debitCard(float amount, ...) { ... }
Ahora ponemos nuestro código entre estos dos métodos de servicio externo como cliente. Nuestros usuarios pueden elegir los productos para comprar, y calculamos el mejor precio para ellos, y luego cancelamos los fondos de la tarjeta:
Después de un tiempo, la compañía propietaria de la API decide abandonar la representación material de los precios a favor del decimal (en lugar de flotante , ahora se usa int ). Entonces, modificaron el código API de la siguiente manera:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
El hecho es que nuestro código usa una declaración explícita de una variable flotante como precio. En su forma actual, recibiremos un error en el momento de la compilación. Pero si hubiéramos previsto tal situación y hubiéramos utilizado var en lugar de flotante , nuestro código continuaría funcionando sin problemas, gracias a la conversión de tipo implícita:
Punto 4: cuando los literales no son una solución adecuada, use una conversión explícita o descarte var
Algunos tipos primitivos en Java no tienen literales especiales, por ejemplo, byte y tipos cortos . En este caso, usando la designación de tipo explícito, podemos crear variables sin ningún problema.
Use esto en lugar de var :
Pero, ¿por qué en esta situación da preferencia a la notación de tipo explícito en lugar de simplemente usar var ? Bueno, escribamos este código usando var . Tenga en cuenta que en ambos casos, el compilador asumirá que necesita variables de tipo int .
Evita este error:
Aquí no hay literales que nos ayuden, por lo tanto, nos vemos obligados a utilizar la conversión explícita de tipo descendente. Personalmente, evitaré tales situaciones, porque no veo ninguna ventaja aquí.
Solo use esta entrada si realmente quiere usar var :
La ventaja de usar var es escribir código más conciso. Por ejemplo, en el caso de usar constructores, podemos evitar la necesidad de repetir el nombre de la clase y, por lo tanto, eliminar la redundancia de código.
Evita lo siguiente:
Use en su lugar:
Para la construcción a continuación, var también será una buena manera de simplificar el código sin perder información.
Evitar
Utiliza el siguiente código:
Entonces, ¿por qué nos sentimos más cómodos trabajando con var en los ejemplos presentados? Porque toda la información necesaria está contenida en los nombres de las variables. Pero si var , en combinación con un nombre de variable, reduce la claridad del código, es mejor negarse a usarlo.
Evitar
Uso:
Considere, por ejemplo, el uso de la clase java.nio.channels.Selector
. Esta clase tiene un método estático open()
que devuelve un nuevo selector y lo abre. Pero aquí puede pensar fácilmente que el método Selector.open()
puede devolver un tipo booleano , dependiendo del éxito de abrir un selector existente, o incluso devolver un vacío . El uso de var aquí conducirá a la pérdida de información y la confusión en el código.
Punto 6: el tipo var garantiza la seguridad en tiempo de compilación
Esto significa que no podemos compilar una aplicación que intente realizar asignaciones incorrectas. Por ejemplo, el siguiente código no se compila:
Pero este compila:
var items = 10; items = 20;
Y este código compila con éxito:
var items = "10"; items = "10 items";
Una vez que el compilador ha definido el valor de la variable var , no podemos asignar nada más que este tipo.
Punto 7: var no se puede usar para crear instancias de un tipo específico y asignarlo a una variable de tipo de interfaz
En Java, utilizamos el enfoque de "programación con interfaces". Por ejemplo, creamos una instancia de la clase ArrayList, asociándola con una abstracción (interfaz):
List<String> products = new ArrayList<>();
Y evitamos cosas como vincular un objeto a una variable del mismo tipo:
ArrayList<String> products = new ArrayList<>();
Esta es la práctica más común y deseable, ya que podemos reemplazar fácilmente la implementación de la interfaz con cualquier otra. Para esto, solo es necesario declarar una variable de tipo de interfaz.
No podremos seguir este concepto usando variables var, como siempre se muestra un tipo específico para ellos. Por ejemplo, en el siguiente fragmento de código, el compilador determinará el tipo de la variable como ArrayList<String>
:
var productList = new ArrayList<String>();
Hay varios argumentos de defensa que explican este comportamiento:
var se usa para variables locales, donde, en la mayoría de los casos, la programación usando interfaces se usa menos que en los casos con parámetros de método devueltos por valores o campos
El alcance de las variables locales debe ser pequeño, por lo que resolver los problemas causados por cambiar a otra implementación no debería ser muy difícil.
var trata el código de la derecha como el inicializador utilizado para determinar el tipo real. Si, en algún momento, se cambia el inicializador, entonces el tipo que se está definiendo también puede cambiar, causando problemas en el código que se basa en esta variable.
Párrafo 8: la probabilidad de la conclusión de un tipo inesperado
El uso de var en combinación con un operador de diamante (<>) en ausencia de información para identificar el tipo puede conducir a resultados inesperados.
Antes de Java 7, se usaba la inferencia de tipo explícita para las colecciones:
Comenzando con Java 7, se introdujo el operador de diamantes . En este caso, el compilador derivará independientemente el tipo necesario:
¿Qué tipo se mostrará en el siguiente código?
Debe evitar tales construcciones:
El tipo se definirá como ArrayList<Object>
. Esto se debe a que no se proporciona la información necesaria para determinar correctamente el tipo. Esto lleva al hecho de que se seleccionará el tipo más cercano, que puede ser compatible con el contexto de lo que está sucediendo. En este caso, Object
.
Por lo tanto, var solo se puede usar si proporcionamos la información necesaria para determinar el tipo esperado. El tipo se puede especificar directamente o pasar como argumento.
Especifique directamente el tipo:
Pase argumentos del tipo requerido:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
Elemento 9: asignar una matriz a una variable var no requiere corchetes []
Todos sabemos cómo declarar matrices en Java:
int[] numbers = new int[5];
¿Qué tal usar var cuando se trabaja con matrices? En este caso, no es necesario usar soportes en el lado izquierdo.
Evite lo siguiente (esto ni siquiera se compila):
Uso:
El siguiente código usando var también falla al compilar. Esto se debe a que el compilador no puede determinar el tipo desde el lado derecho:
Elemento 10: var no se puede usar al declarar múltiples variables en la misma línea
Si desea declarar variables del mismo tipo a la vez, debe saber que var no es adecuado para esto. El siguiente código no se compila:
Use en su lugar:
O es:
Punto 11: las variables locales deben esforzarse por minimizar su alcance. El tipo var refuerza esta afirmación.
Mantenga un pequeño alcance para las variables locales: estoy seguro de que escuchó esta declaración antes de la var .
La legibilidad y la corrección rápida de errores son argumentos a favor de este enfoque. Por ejemplo, definamos una pila de la siguiente manera:
Evita esto:
Tenga en cuenta que estamos llamando al método forEach()
, que se hereda de java.util.Vector
. Este método pasará por la pila como cualquier otro vector y esto es lo que necesitamos. Pero ahora decidimos usar ArrayDeque
lugar de Stack
. Cuando hacemos esto, el método forEach()
recibirá una implementación de ArrayDeque que atravesará la pila como una pila estándar (LIFO)
Esto no es lo que queremos. Es demasiado difícil rastrear el error aquí, porque el código que contiene la parte forEach()
no se encuentra al lado del código en el que se realizaron los cambios. Para aumentar la velocidad de búsqueda y corrección de errores, es mucho mejor escribir código usando la variable stack
, lo más cerca posible de la declaración de esta variable.
Esto se hace mejor de la siguiente manera:
Ahora, cuando el desarrollador cambie de Stack
a ArrayQueue
, podrá notar rápidamente el error y solucionarlo.
Cláusula 12: el tipo var simplifica el uso de varios tipos en operadores ternarios
Podemos usar diferentes tipos de operandos en el lado derecho del operador ternario.
Al especificar explícitamente los tipos, el siguiente código no se compila:
Sin embargo, podemos hacer esto:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
El siguiente código tampoco se compila:
Pero puedes usar tipos más generales:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
En todos estos casos, es mejor preferir var :
De estos ejemplos no se deduce que el tipo var define tipos de objetos en tiempo de ejecución. ¡Esto no es así!
Y, por supuesto, el tipo var funcionará correctamente con los mismos tipos de ambos operandos:
Punto 13: el tipo var se puede usar dentro de los bucles
Podemos reemplazar fácilmente la declaración explícita de tipos en bucles for con el tipo var .
Cambiar un tipo int explícito a var :
Cambiar el tipo explícito de Order
a var :
List<Order> orderList = ...;
Punto 14: var funciona bien con flujos en Java 8
Es muy simple usar var de Java 10 con secuencias que aparecieron en Java 8.
Simplemente puede reemplazar la declaración explícita de tipo Stream con var :
Ejemplo 1:
Ejemplo 2
Cláusula 15: var se puede usar al declarar variables locales destinadas a dividir grandes cadenas de expresiones en partes
Las expresiones con una gran cantidad de anidamiento se ven impresionantes y generalmente parecen algún tipo de código inteligente e importante. En el caso de que sea necesario facilitar la legibilidad del código, se recomienda dividir una expresión grande utilizando variables locales. Pero a veces escribir muchas variables locales parece un trabajo muy agotador que me gustaría evitar.
Un ejemplo de una gran expresión:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Es mejor dividir el código en sus componentes:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
La segunda versión del código parece más legible y simple, pero la primera versión también tiene derecho a existir. Es absolutamente normal que nuestra mente se adapte a la comprensión de expresiones tan grandes y las prefiera a las variables locales. Sin embargo, usar el tipo var puede ayudar a dividir estructuras grandes al reducir el esfuerzo para declarar variables locales:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Cláusula 16: var no se puede usar como un tipo de retorno o como un tipo de argumento de método
Los dos fragmentos de código que se muestran a continuación no se compilarán.
Usando var como el tipo de retorno:
Usando var como un tipo de argumento de método:
Cláusula 17: las variables locales de tipo var pueden pasarse como parámetros de un método o pueden tomar un valor de retorno
Los siguientes fragmentos de código se compilarán y funcionarán correctamente:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
Conclusión
« var », Java 10. , . , var , .
var Java!