Java

Tutorial Java: Inferencia de tipos con var

Java 10 y la historia de 'var': Comprende la evolución de la sintaxis con la inferencia de tipos y cómo mejora el desarrollo Java.

Aprende Java y certifícate

Historia y origen de var desde Java 10

La palabra clave var se introdujo en Java 10 como parte del JEP 286 (Java Enhancement Proposal) titulado "Local-Variable Type Inference", permitiendo que el tipo de las variables locales sea inferido por el compilador en lugar de tener que declararlo explícitamente.

Antes de Java 10, se debía especificar el tipo de cada variable de forma explícita:

ArrayList<String> nombres = new ArrayList<String>();
HashMap<Integer, Usuario> mapaUsuarios = new HashMap<Integer, Usuario>();

Con la introducción de var, se puede simplificar esta sintaxis:

var nombres = new ArrayList<String>();
var mapaUsuarios = new HashMap<Integer, Usuario>();

Motivación detrás de var

La incorporación de var respondió a varias necesidades:

  • Reducción de la verbosidad del código, especialmente con tipos genéricos complejos
  • Mejora de la legibilidad en casos donde el tipo es obvio o demasiado largo
  • Alineación con otros lenguajes modernos como Kotlin, Scala, C# y JavaScript que ya utilizaban inferencia de tipos
  • Preparación para futuras evoluciones del lenguaje Java

var no convierte a Java en un lenguaje de tipado dinámico. El sistema de tipos sigue siendo estático y fuertemente tipado, solo que ahora el compilador puede determinar el tipo en tiempo de compilación basándose en el inicializador de la variable.

Evolución desde Java 10

Cuando se introdujo en Java 10 (marzo de 2018), var tenía algunas limitaciones:

  • Solo podía utilizarse con variables locales con inicializadores
  • No se permitía en parámetros de métodos, campos de clase, o variables sin inicializar

En versiones posteriores, se ha ampliado su uso:

  • En Java 11 se permitió usar var en parámetros de expresiones lambda:
// Java 10: No permitido
Consumer<String> printer = (var s) -> System.out.println(s); // Error

// Java 11: Permitido
Consumer<String> printer = (var s) -> System.out.println(s); // Correcto

Implementación técnica

Desde el punto de vista técnico, var no es una palabra clave tradicional en Java, sino un identificador reservado con contexto específico. Esto significa que se puede seguir utilizando var como nombre de variable, método o clase (aunque no se recomienda por razones de claridad):

// Esto es válido pero confuso
String var = "Esto es una variable llamada var";
var var = "Esto usa la inferencia de tipos";

El compilador de Java realiza la inferencia de tipos analizando el lado derecho de la asignación y asignando ese tipo a la variable. Este proceso ocurre completamente en tiempo de compilación, por lo que no hay impacto en el rendimiento en tiempo de ejecución.

Casos de uso recomendados para var

Variables con inicializadores obvios

Cuando el tipo de la variable es evidente a partir del inicializador, var mejora la legibilidad al eliminar la redundancia:

// Sin var - redundante
String mensaje = "Hola mundo";
ArrayList<String> nombres = new ArrayList<>();

// Con var - conciso y claro
var mensaje = "Hola mundo";
var nombres = new ArrayList<String>();

En estos casos, cualquier programador puede identificar que mensaje es un String y nombres es un ArrayList<String> sin necesidad de la declaración explícita.

Reducción de la verbosidad con tipos genéricos complejos

Los tipos genéricos anidados pueden crear declaraciones muy largas que dificultan la lectura del código:

// Sin var - excesivamente verboso
Map<Integer, List<Map<String, Set<Customer>>>> datosComplejos = new HashMap<>();

// Con var - enfoque en la operación, no en el tipo
var datosComplejos = new HashMap<Integer, List<Map<String, Set<Customer>>>>();

El uso de var en este contexto permite centrarse en lo que hace el código en lugar de perderse en la declaración de tipos.

Variables de ámbito limitado

Cuando una variable tiene un ámbito muy reducido, como en bucles o bloques pequeños, var puede hacer que el código sea más compacto sin sacrificar la comprensión:

// Bucle for tradicional
for (var i = 0; i < 10; i++) {
    var elemento = lista.get(i);
    // Operaciones con elemento...
}

// Try-with-resources
try (var conexion = obtenerConexion();
     var statement = conexion.createStatement()) {
    // Uso de statement...
}

Mejora de la legibilidad en expresiones lambda

En Java 11 y posteriores, var se puede utilizar en parámetros de expresiones lambda para añadir anotaciones o hacer el código más explícito:

// Sin var
lista.stream().map(s -> s.toUpperCase()).forEach(System.out::println);

// Con var - permite anotaciones
lista.stream()
    .map((@NotNull var s) -> s.toUpperCase())
    .forEach(System.out::println);

Captura de tipos de implementación sin exponerlos

Cuando se trabaja con tipos de implementación que no deberían exponerse en la API, var permite capturar el tipo real sin mencionarlo explícitamente:

// Sin var - expone el tipo de implementación
LinkedHashMap<String, Integer> cache = obtenerCache();

// Con var - captura el tipo sin exponerlo
var cache = obtenerCache();

Esto es especialmente útil cuando se sigue el principio de "programar hacia interfaces" pero se necesita acceder a métodos específicos de la implementación en un ámbito local.

Nombres de variables descriptivos

Cuando se utilizan nombres de variables muy descriptivos, var puede evitar la redundancia y mejorar la legibilidad:

// Sin var - el tipo y el nombre contienen información similar
Customer clientePreferente = buscarClientePreferente();

// Con var - el nombre ya indica el tipo
var clientePreferente = buscarClientePreferente();

Operaciones con tipos inferidos

En operaciones donde el tipo exacto no es relevante para el algoritmo, var puede hacer que el código sea más genérico:

// El tipo específico no importa, solo que tenga los métodos necesarios
var resultado = operacion1();
resultado = resultado.combinarCon(operacion2());
return resultado.procesar();

Refactorización y mantenimiento

El uso de var puede facilitar ciertas refactorizaciones, ya que al cambiar el tipo devuelto por un método no es necesario actualizar todas las declaraciones de variables:

// Si cambiamos obtenerDatos() para que devuelva List<T> en lugar de ArrayList<T>,
// no necesitamos cambiar esta línea
var datos = obtenerDatos();

Mejora de la visibilidad del código importante

Al reducir el "ruido visual" de las declaraciones de tipo, var puede ayudar a que el código importante destaque más:

// El foco está en la lógica, no en las declaraciones
var cliente = obtenerCliente(id);
var pedidos = cliente.obtenerPedidosRecientes();
var total = calcularTotal(pedidos);

if (total > LIMITE_DESCUENTO) {
    aplicarDescuento(cliente, total);
}

Uso con patrones de diseño

En patrones como Builder o Factory, var puede hacer que el código sea más limpio:

// Builder pattern
var usuario = Usuario.builder()
    .nombre("Ana")
    .email("ana@ejemplo.com")
    .rol("admin")
    .build();

// Factory pattern
var documento = DocumentoFactory.crear(tipo, contenido);

Limitaciones y restricciones, cuando no usar var

Restricciones técnicas

Existen varias limitaciones técnicas que restringen dónde y cómo se puede utilizar var:

  • Variables sin inicializador: var requiere inicialización en la misma línea de declaración:
// No permitido
var nombre;
nombre = "Juan";

// Correcto
var nombre = "Juan";
  • Variables de clase (campos): var solo funciona con variables locales, no con campos de clase:
public class Ejemplo {
    // No permitido
    private var contador = 0;
    
    // Correcto
    private int contador = 0;
}
  • Parámetros de métodos: No se puede usar en la declaración de parámetros de métodos (excepto en lambdas desde Java 11):
// No permitido
public void procesar(var datos) {
    // código
}

// Correcto
public void procesar(List<String> datos) {
    // código
}
  • Valor de retorno de métodos: No se puede usar para declarar tipos de retorno:
// No permitido
public var obtenerDatos() {
    return new ArrayList<String>();
}

// Correcto
public List<String> obtenerDatos() {
    return new ArrayList<>();
}
  • Inicialización con null: No se puede inferir el tipo de null:
// No permitido - ¿qué tipo debería inferir?
var referencia = null;

// Correcto
String referencia = null;
  • Arrays con inicializadores abreviados: La sintaxis abreviada para arrays no funciona con var:
// No permitido
var numeros = {1, 2, 3, 4};

// Correcto
var numeros = new int[] {1, 2, 3, 4};
  • Lambdas sin contexto: No se puede usar con lambdas sin un tipo objetivo claro:
// No permitido - tipo ambiguo
var comparador = (a, b) -> a.compareTo(b);

// Correcto
Comparator<String> comparador = (a, b) -> a.compareTo(b);

Casos donde no es recomendable usar var

Además de las restricciones técnicas, hay situaciones donde el uso de var puede reducir la claridad del código:

  • Tipos no obvios: Cuando el tipo no es evidente a partir del contexto o el inicializador:
// Problemático - ¿qué tipo devuelve obtenerValor()?
var resultado = obtenerValor();

// Mejor - explícito sobre el tipo
TipoEspecifico resultado = obtenerValor();
  • Tipos primitivos: Con tipos primitivos, especialmente booleanos, la declaración explícita puede ser más clara:
// Menos claro
var activo = false;

// Más claro
boolean activo = false;
  • Métodos que devuelven tipos genéricos: Cuando un método devuelve un tipo genérico sin parámetros de tipo explícitos:
// Problemático - se pierde información de tipo
var lista = Collections.emptyList();  // ¿Lista de qué?

// Mejor
List<Cliente> lista = Collections.emptyList();
  • Cadenas de métodos con tipos intermedios importantes: Cuando los tipos intermedios en una cadena de métodos son relevantes para entender el código:
// Problemático - se oculta información importante
var resultado = datos.stream()
    .filter(d -> d.isValid())
    .map(DataProcessor::process)
    .collect(Collectors.toList());

// Mejor - explícito sobre los tipos
Stream<RawData> stream = datos.stream();
Stream<RawData> filtered = stream.filter(d -> d.isValid());
Stream<ProcessedData> mapped = filtered.map(DataProcessor::process);
List<ProcessedData> resultado = mapped.collect(Collectors.toList());
  • Inferencia que cambia con versiones de API: Cuando el tipo inferido podría cambiar si cambia la implementación de una API:
// Peligroso - si el método cambia su tipo de retorno, el código podría romperse
var configuracion = ConfiguracionFactory.obtenerConfiguracion();
  • Código con muchas variables de tipos similares: Cuando hay múltiples variables con tipos similares, la declaración explícita ayuda a distinguirlas:
// Confuso
var id1 = obtenerPrimerIdentificador();
var id2 = obtenerSegundoIdentificador();

// Más claro
ClienteId id1 = obtenerPrimerIdentificador();
ProductoId id2 = obtenerSegundoIdentificador();

Impacto en la legibilidad del código

El uso excesivo o inapropiado de var puede tener consecuencias negativas:

  • Dificulta la comprensión rápida del código: Sin tipos explícitos, puede ser necesario analizar más código para entender qué hace.
  • Complica el mantenimiento: Los desarrolladores que mantienen el código pueden tener que investigar más para entender los tipos.
  • Reduce la documentación implícita: Los tipos explícitos sirven como documentación integrada en el código.

Consideraciones de estilo y convenciones de equipo

Muchos equipos de desarrollo han establecido directrices específicas para el uso de var:

  • Limitar su uso a variables con ámbito reducido
  • Evitarlo en interfaces públicas o APIs
  • Utilizarlo solo cuando mejora genuinamente la legibilidad
  • Combinarlo con nombres de variables muy descriptivos

Algunas herramientas de análisis estático como SonarQube o CheckStyle pueden configurarse para aplicar estas reglas automáticamente.

Equilibrio entre concisión y claridad

El principio fundamental para decidir cuándo usar var debe ser el equilibrio entre concisión y claridad. Si bien el código más corto suele ser preferible, nunca debe ser a expensas de la comprensibilidad.

Se recomienda evaluar cada caso individualmente, considerando factores como:

  • La complejidad del tipo
  • La obviedad del tipo a partir del contexto
  • La importancia del tipo para entender el algoritmo
  • El público objetivo del código (API pública vs. implementación interna)

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende Java online

Ejercicios de esta lección Inferencia de tipos con var

Evalúa tus conocimientos de esta lección Inferencia de tipos con var con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Listas

Código

Métodos de la clase String

Código

Streams: reduce()

Test

Polimorfismo

Código

Pattern Matching

Código

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Inferencia de tipos con var

Código

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Arrays y matrices

Código

Clases y objetos

Código

Interfaz funcional Consumer

Test

Interfaces

Código

Enumeraciones Enums

Código

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Sobrecarga de métodos

Código

Clases sealed

Código

Creación de Streams

Test

Records

Código

Encapsulación

Código

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Funciones

Código

Polimorfismo de tiempo de compilación

Test

Reto sintaxis Java

Proyecto

Conjuntos

Código

Estructuras de control

Código

Recursión

Código

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

Operadores

Código

Variables

Código

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Tipos de datos

Código

Clases abstractas

Código

Instalación

Test

Funciones

Código

Excepciones

Código

Estructuras de control

Código

Herencia de clases

Código

La clase Scanner

Código

Generics

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Recursión

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Records

Programación Orientada A Objetos

Pattern Matching

Programación Orientada A Objetos

Inferencia De Tipos Con Var

Programación Orientada A Objetos

Enumeraciones Enums

Programación Orientada A Objetos

Generics

Programación Orientada A Objetos

Clases Sealed

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Ecosistema Jakarta Ee De Java

Frameworks Para Java

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la introducción y evolución de la palabra clave 'var' desde Java 10.\n- Identificar los beneficios de 'var', como la reducción de verbosidad y la mejora de legibilidad.\n- Aprender las limitaciones y restricciones técnicas del uso de 'var' en Java.\n- Reconocer cuándo utilizar 'var' para optimizar el código y cuándo es recomendable evitarlo.\n- Explorar los impactos de 'var' en la comunidad y el ecosistema Java a través de ejemplos prácticos.\n- Discutir las mejores prácticas al implementar 'var' en proyectos Java reales.