En PC Resumen, hablaremos del concepto de como funcionan los Comodines que tienen los genéricos en Java.

Antes de ver los comodines, considérese un ejemplo que nos ayude a motivar su uso. Supongamos que queremos implementar un método genérico llamado sumar que obtenga el total de números de una colección, como en un objeto ArrayList. Nos gustaría poder obtener el total de todos los números sin importar su tipo. Por esta razón, declaramos el objeto con el argumento de tipo Number, el cual es superclase de todos los números. Por lo tanto el método sumar recibirá un parámetro de tipo ArrayList<Number> y obtendrá el total de sus elementos.

import java.util.ArrayList;

public class TotalNumeros {
    
    public static double sumar (ArrayList<Number> lista) {
        double total = 0;
        for (Number elemento: lista) {
            total += element.doubleValue();
        }
        return total;
    }

    public static void main(String args[]) {
        Number[] numeros = {1, 2.4, 3, 4.1};
        ArrayList<Number> listaNumeros = new ArrayList<Number>();
        for (Number elemento: numeros) {
            listaNumeros.add (elemento);
        }
        System.out.printf("listaNumeros contiene:%s \n", listaNumeros);
        System.out.printf("Total de los elementos a listaNumeros: %.1f \n", sumar(llistaNumeros));
    }
}

El método utiliza valores double para realizar los cálculos y devuelve el resultado como un double.

Dado que el método sumar puede obtener el total de los elementos de un objeto ArrayList de objetos Number se podría espera que el método también funcionara para objetos ArrayList de objetos Integer. Así modificamos la clase para crear un ArrayList<Integer> y pasarlo a la función sumar:

import java.util.ArrayList;

public class TotalNumerosErrors {

    public static double sumar (ArrayList <Number> lista) {
        double total = 0;
        for (Number elemento: lista) {
            total += element.doubleValue();
        }
        return total;
    }

    public static void main (String[] args) {
        Integer[] enteros = {1, 2, 3, 4};
        ArrayList<Integer> listaenteros = new ArrayList<Integer>();
        for (Integer elemento: enteros) {
            listaenteros.add (elemento);
        }
        System.out.printf("listaenteros contiene: %s \n", listaenteros);
        System.out.printf("Total de los elementos listaenteros: %.1f \n", sumar(listaenteros));
    }
}

Cuando compilamos el programa nos dará el siguiente error:

sumar(java.util.ArrayList<java.lang.Number>) in TotalNumerosErrors cannot be applied to (java.util.ArrayList<java.lang.Integer>)

La razón es que aunque Number es superclase de Integer el compilador no considera que un ArrayList<Number> sea un super tipo de ArrayList<Integer>. Si lo fuera, cualquier operación que pudiéramos hacer en un ArrayList<Number> funcionaría también en un ArrayList<Integer>. Por ejemplo podemos sumar un Double en un ArrayList<Number> dado que un Double es un Number pero no podemos sumar un objeto Double a un ArrayList<Integer> ya que un Double no es un Integer. Por lo tanto no es válida la relación de los subtipos.

¿Qué hacer si queremos una versión más flexible del método para sumar los elementos de cualquier ArrayList que contenga elementos de cualquier subclase de Number?. Aquí es donde son importantes los argumentos de tipo comodín. Los comodines nos permiten especificar parámetros de métodos, valores de retorno, variables o campos, que actúan como a super tipo los tipos parametrizados. En el código siguiente, el parámetro del método suma se declara con el tipo:

ArrayList <? extends Number>

Un argumento tipo comodín se denota mediante un signo de interrogación, que por sí solo representa un tipo desconocido. En este caso, el comodín extiende a la clase Number, lo que significa que el comodín tiene como límite superior la clase Number. Por lo tanto, el argumento debe ser de tipo Number o una subclase de esta. De esta forma el método sumar puede recibir cualquier ArrayList que contenga cualquier subclase de Number.

import java.util.ArrayList;

public class PruebaComodin {
    
    public static void main(String args[]) {
        Integer[] enteros = {1, 2, 3, 4, 5};
        ArrayList<Integer> listaEnters = new ArrayList<Integer>();
        for (Integer elemento: enteros) {
            listaEnters.add(elemento);
        }
        System.out.printf("listaEnters contiene: %s \n", listaEnters);
        System.out.printf("Total de los elementos a listaEnters: %.0f \n \n",
        sumar(listaEnters));
        Double[] valoresDouble = {1.1, 3.3, 5.5};
        ArrayList<Double> listaDouble = new ArrayList<Double>();
        for (Double elemento: valoresDouble) {
            listaDouble.add (elemento);
        }
        System.out.printf("listaDouble contiene: %s \n", listaDouble);
        System.out.printf("Total de los elementos a listaDouble: %.1f \n \n", sumar(llistaDouble));
        Number[] numeros = {1, 2.4, 3, 4.1};
        ArrayList<Number> llistaNumeros = new ArrayList<Number>();
        for (Number elemento: numeros) {
            listaNumeros.add (elemento);
        }
        System.out.printf("listaNumeros contiene: %s \n", listaNumeros);
        System.out.printf("Total de los elementos a listaNumeros: %.1f \n", sumar(llistaNumeros));
    }

    public static double sumar(ArrayList <? extends Number> lista) {
        double total = 0;
        for(Number elemento: lista) {
            total += elemento.doubleValue();
        }
        return total;
    }
}

En el método sumar, aunque los tipos de los elementos no son directamente conocidos por el método, se sabe que al menos son de tipo Number. Por este motivo se acepta llamar al método doubleValue ya que todos los objetos Number tienen este método.

Aunque los comodines proporcionan flexibilidad al pasar tipo parametrizados a un método, también tienen desventajas. Como el comodín en el encabezamiento del método sumar no especifica el nombre de un parámetro de tipo, no se puede utilizar como nombre de tipo en el cuerpo del método (es decir no podemos reemplazar Number para? Dentro del for). De todas formas podríamos declarar el método sumar de la siguiente forma:

public static <T extends Number> double sumar(ArrayList <T> lista)

Después podríamos usar el parámetro de tipo T en el cuerpo del método.

Si el comodín especifica sin un límite superior, entonces sólo se pueden invocar los métodos del tipo Object en valores del tipo del comodín. Además, los métodos que utilizan comodines en los argumentos de tipo de sus parámetros no pueden utilizarse para agregar elementos a una colección en la que el parámetro se refiere.

Utilizar un comodín en la sección de parámetros de tipo de un método, o utilizar un comodín como un tipo explícito de una variable en el cuerpo del método, es un error de sintaxis.

Observaciones

  • Una clase genérica puede derivarse de una clase no genérica.
  • Una clase genérica puede derivarse de otra clase genérica.
  • Una clase no genérica puede derivarse de una clase genérica.
  • Un método genérico en una subclase puede sobrescribir un método genérico en una superclase si ambos métodos tienen las mismas firmas.
Pin It