CodeGym /Cursos /JAVA 25 SELF /Genéricos: para qué sirven y sintaxis básica

Genéricos: para qué sirven y sintaxis básica

JAVA 25 SELF
Nivel 26 , Lección 4
Disponible

1. El problema de las colecciones «crudas» (raw types)

Un poco de historia. Antes de Java 5 todas las colecciones eran «omnívoras». Almacenaban objetos de tipo Object, y el compilador no controlaba qué metías en ellas. ¿Quieres meter una cadena? Adelante. ¿Un número? ¿Por qué no? ¿Un gato? También se podía.

// Ejemplo de colecciones "crudas" (raw types), Java anterior a la versión 5
List list = new ArrayList();
list.add("Hola");
list.add(42);
list.add(new Object());

El problema aparecía al «sacar» y usar el valor:

String s = (String) list.get(0); // OK, es una cadena
String s2 = (String) list.get(1); // ¡BOOM! ClassCastException

El compilador guarda silencio, y en tiempo de ejecución obtienes ClassCastException. Es como una caja con la etiqueta «manzanas» en la que hay una taza, un plátano y un erizo.

¿Por qué es malo?

  • Los errores se manifiestan solo en tiempo de ejecución.
  • Se mezclan los tipos: hay que convertir (cast) los objetos manualmente al tipo necesario.
  • El código es menos legible y más peligroso.

La solución: genéricos

Los genéricos (generics) son un mecanismo que permite crear clases, interfaces y métodos con parámetros de tipo. Es decir, le dices a la colección: «Guarda solo cadenas», y el compilador lo hace cumplir estrictamente.

List<String> words = new ArrayList<>();
words.add("Hola");
words.add("Mundo");
// words.add(42); // Error de compilación: no se puede añadir un int a List<String>

Ahora el compilador no permitirá meter en la lista nada salvo cadenas. El error se detecta antes de ejecutar el programa.

La idea principal de los genéricos:
Proporcionar seguridad de tipos para colecciones (y no solo) para que los errores se capturen en la compilación, no en tiempo de ejecución.

2. Sintaxis de genéricos: cómo se ve en el código

Especificar el tipo entre los signos de menor y mayor

Cuando crees una colección, especifica el tipo de los elementos entre <>:

List<String> names = new ArrayList<>();
names.add("Alicia");
names.add("Bob");
// names.add(123); // Error: no se puede añadir un número a una lista de cadenas

String first = names.get(0); // ¡No hace falta casting!

Clásicos:

  • List<String> — lista de cadenas
  • List<Integer> — lista de números enteros
  • Set<Double> — conjunto de números de punto flotante
  • Map<String, Integer> — clave String, valor Integer

¿Por qué no escribir simplemente List?

Se puede, pero pierdes todas las ventajas de los genéricos, y el compilador te avisará:

List list = new ArrayList(); // raw type - no recomendado
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // ¡Hola, ClassCastException!

El código moderno en Java siempre usa genéricos.

Operador diamante <>

Desde Java 7 se puede omitir el tipo a la derecha si se entiende por el contexto:

List<String> list = new ArrayList<>(); // El compilador infiere que es <String>

3. Genéricos para distintas colecciones

Ejemplos para List, Set, Map

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alicia");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alicia", 23);
ages.put("Bob", 31);

Ejemplo con una clase propia

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. Matices útiles

Ventajas de los genéricos

Seguridad de tipos. El compilador se asegura de que a la colección solo entren elementos del tipo adecuado.

No hay necesidad de conversiones de tipo. Antes: String s = (String) list.get(0);. Ahora: String s = list.get(0);.

Código más legible y fiable. Menos sorpresas en tiempo de ejecución.

Limitaciones de los genéricos

No se pueden usar tipos primitivos. Los genéricos funcionan solo con objetos, no con primitivos (int, double, boolean). Usa clases envoltorio: Integer, Double, Boolean.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int se convierte automáticamente en Integer (autoboxing)

Breve sobre el borrado de tipos (type erasure)

En Java los genéricos se implementan mediante borrado de tipos: tras la compilación, la información sobre los parámetros de tipo se borra y, en tiempo de ejecución, la JVM no sabe que era un List<String> y no simplemente un List. Se hizo por compatibilidad hacia atrás.

Consecuencia: no se puede comprobar el parámetro de tipo con instanceof usando un argumento de tipo concreto.

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Error de compilación

Intentar agregar un elemento de otro tipo: error de compilación

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Error de compilación: incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Gato", 5);
// map.put(3, "Elefante"); // Error: la clave debe ser String y el valor, Integer

Y eso es fantástico: los errores se detectan en la compilación.

No solo colecciones

También puedes usar genéricos en clases y métodos propios. Por ejemplo, una «Caja» genérica:

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Hola");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

En colecciones los genéricos son el estándar, pero también los verás en otros lugares, por ejemplo, en Stream API y Optional.

5. Errores típicos al trabajar con genéricos

Error n.º 1: Uso de colecciones «crudas». La forma List list = new ArrayList(); elimina la seguridad de tipos. Indica siempre los parámetros de tipo, por ejemplo List<String>.

Error n.º 2: Intentar usar primitivos. No puedes escribir List<int>; usa List<Integer>.

Error n.º 3: Hacer cast manual al leer de la colección. Si usas genéricos, el cast (String) list.get(i) no es necesario. Si hay que hacerlo, en algún punto se han roto los tipos.

Error n.º 4: Esperar que los parámetros de tipo estén disponibles en tiempo de ejecución. Debido al borrado de tipos, no se pueden comprobar con instanceof al estilo de List<String>.

Error n.º 5: Mezclar tipos distintos en una misma colección. Si se ha declarado List<String>, no intentes añadir Integer: el compilador no lo permitirá, y eso es bueno.

1
Tarea
JAVA 25 SELF, nivel 26, lección 4
Bloqueada
Catálogo de mi biblioteca digital 📚✍️
Catálogo de mi biblioteca digital 📚✍️
1
Tarea
JAVA 25 SELF, nivel 26, lección 4
Bloqueada
Cofres mágicos universales 📦✨
Cofres mágicos universales 📦✨
1
Cuestionario/control
Colecciones y generics, nivel 26, lección 4
No disponible
Colecciones y generics
Colecciones y generics
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION