Basics

Java Generics

Los genéricos en Java están a partir del SDK 5.0 del lenguaje, y a pesar de llevar tanto tiempo con nosotros, aún hay muchos creadores de código que no saben utilizarlos correctamente.

Un pequeño ejemplo de genéricos

Es muy común que tengamos la necesidad de almacenar objetos en listas para su uso.

Por ejemplo, tomemos un listado con nombres:

List names = new ArrayList();
names.add("Gonzalo");
names.add("Andres");

Ahora, sabemos que esta lista tiene Strings, porque nosotros la declaramos con sólo nombres, verdad? Entonces hagamos alguna iteración e imprimamos los resultados, esto debería hacerse así:

for(String name: names){
    System.out.println("Name " + name);
}

o quizás:

names.forEach((String name) -> System.out.println(name));

Verdad? Bueno, la verdad es que el compilador nos va a lanzar un error en estos casos. No hay un contrato que nos asegure que cada elemento de esa lista es un String, por lo que en nuestros for y forEach lo que tenemos no es un String, sino que un Object que podemos castear para obtener nuestro esperado String:

for(Object name: names){
    String obtainedName = (String) name;
    System.out.println("Name " + obtainedName);
}

names.forEach(name -> System.out.println((String) name));

Este cast tiene varios problemas, en primer lugar, es molesto tener que hacer casting cada vez que necesitamos una variable, y por otro lado, si por equivocación metemos un valor numérico u otro objeto a la lista y hacemos un cast, nos encontraremos con una excepción.

Este problema se soluciona fácilmente si declaramos entre <> el tipo que queremos tenga nuestra lista.

List<String> names = new ArrayList();
names.add("Gonzalo");
names.add("Andres");

for(String name: names){
    System.out.println("Name " + name);
}

names.forEach(name -> System.out.println( name));

Bienvenido a los genéricos!

Clases genéricas

Veamos una clase Java

public class GenericClassTest<T> {

    T object;


    GenericClassTest(T object){
        this.object = object;
    }

    public T getObject(){
        return this.object;
    }

    public static void main(String[] args) {
        GenericClassTest<Long> longGenericClassTest = new GenericClassTest<>(29L);
        System.out.println("Long Generic Test Class: " + longGenericClassTest.getObject());

        GenericClassTest<List<String>> listGenericClassTest = new GenericClassTest<>(Arrays.asList("Gonzalo", "Munoz"));
        listGenericClassTest.getObject().forEach(System.out::println);
    }

}

Analicemos el código, acá en la declaración de la clase estamos agregándole el uso de genéricos. Esto hará que acepte cualquier tipo de objeto.

En los atributos de clase definimos un object de tipo T, y creamos un constructor que inicializará esta variable.

Finalmente estamos haciendo un método getter para obtener este objeto.

Más abajo, en la misma clase he declarado un método main donde hacemos una instancia de nuestra clase, primero con un Long, y luego con un listado de String, e imprimiendo estos valores por consola para ver que nuestra clase genérica funciona de las mil maravillas.

También podemos agregar más genéricos a una clase:

public class GenericClassTwoParameters<T,U> {

    T obj1;
    U obj2;

    GenericClassTwoParameters(T obj1, U obj2){
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    /**
     * Simple method to retrieve the generic data
     */
    public void getParameters(){
        System.out.println("Obj1: "+obj1);
        System.out.println("Obj2: "+obj2);
    }

    public static void main(String[] args) {
        GenericClassTwoParameters<String, Integer> genericClassTwoParameters = new GenericClassTwoParameters<>("Manzanas", 20);
        
        genericClassTwoParameters.getParameters();
    }
}

Funciones Genéricas

También podemos escribir funciones genéricas que pueden ser llamadas con diferentes tipos de argumentos a partir de lo que le pasemos al método genérico.

 static <T> void genericMethod(T element){
        System.out.println(element);
    }

    public static void main(String[] args) {
        //Veamos como se comporta este método:
        genericMethod("Hello World!");

        genericMethod(2000L);

        genericMethod(2.0);
    }

Acá podemos apreciar que nuestro método genérico le estamos pasando un String, luego un Long y finalmente un Double, y la salida por consola:

Algunas características de los métodos genéricos:

  • Antes del tipo de retorno tienen un type parameter.
  • El type parameter puede ser acotado: Esto es cuando ponemos por ejemplo que los tipos aceptados deban ser numéricos:
public <T extends Number> void generic(T element){...}
// Y esto también se acepta
public <T extends Number & Comparable> void generic {...}
  • Los métodos genéricos también pueden tener distintos type parameters:
public static <T, U> List<T> fromArrayToList(T[] a) {...}

Categories: Basics, Java

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s