Comodines o Wildcards en Java

Avanzado

El signo de interrogación (?) se conoce como el comodín (wildcard) en la programación genérica. Representa un tipo desconocido. El comodín se puede usar en una variedad de situaciones, como el tipo de parámetro, campo o variable local; a veces como un tipo de devolución. A diferencia de las matrices, las diferentes instancias de un tipo genérico no son compatibles entre sí, ni siquiera explícitamente. Esta incompatibilidad puede suavizarse con el comodín si ? se usa como un parámetro de tipo actual.

Lectura Recomendada
Genéricos en Java

1. Introducción a Wildcard

Por muy útil que sea el tipo de seguridad, a veces puede obstaculizar construcciones perfectamente aceptables. Por ejemplo, dada la clase OperaMate que se muestra al final de la sección anterior (Genéricos), suponga que desea agregar un método llamado igualAbs() que devuelve verdadero si dos objetos OperaMate contienen números cuyos valores absolutos son los mismos. Además, desea que este método pueda funcionar correctamente independientemente del tipo de número que tenga cada objeto.

Por ejemplo, si un objeto contiene el valor double 1.25 y el otro objeto contiene el valor float -1.25, entonces igualAbs() devolverá verdadero. Una forma de implementar igualAbs() es pasarle un argumento OperaMate, y luego comparar el valor absoluto de ese argumento con el valor absoluto del objeto invocado, y devolver verdadero solo si los valores son los mismos. Por ejemplo, desea poder llamar a igualAbs(), como se muestra aquí:

OperaMate<Double> dOb=  new OperaMate<Double>(1.25);
OperaMate<Float> fOb= new OperaMate<Float>((float) -1.25);
  if (dOb.igualAbs(fOb))
      System.out.println("Los valores absolutos son los mismos.");
  else
      System.out.println("Los valores absolutos son diferentes.");

Al principio, crear igualAbs() parece una tarea fácil. Lamentablemente, los problemas comienzan tan pronto como intenta declarar un parámetro de tipo OperaMate. ¿Qué tipo especificas para el parámetro de tipo OperaMate? Al principio, puede pensar en una solución como esta, en la que se usa T como parámetro de tipo:

// Esto no funcionará !. 
// Determine si los valores absolutos de dos objetos son iguales.
 boolean igualAbs(OperaMate<T>ob){
      if(Math.abs(num.doubleValue())==Math.abs(ob.num.doubleValue()))
           return true;
      return false;
 }

Aquí, el método estándar Math.abs() se usa para obtener el valor absoluto de cada número, y luego se comparan los valores. El problema con este intento es que solo funcionará con otros objetos OperaMate cuyo tipo sea el mismo que el objeto invocado. Por ejemplo, si el objeto invocado es de tipo OperaMate <Integer>, entonces el parámetro ob también debe ser de tipo OperaMate <Integer>. Por ejemplo, no se puede usar para comparar un objeto de tipo OperaMate <Double>. Así, este enfoque no produce una solución general (es decir, genérica).

2. Uso de un wildcard o comodín

Para crear un método genérico igualAbs(), debe usar otra característica de los genéricos de Java: el argumento comodín (wildcard argument). El argumento comodín está especificado por ? Y representa un tipo desconocido. Usando un comodín, aquí hay una forma de escribir el método igualAbs():

boolean igualAbs(OperaMate<?>ob){
    if(Math.abs(num.doubleValue())==Math.abs(ob.num.doubleValue()))
        return true;
    return false;
}

Aquí, OperaMate <?> coincide con cualquier tipo de objeto OperaMate, lo que permite que dos objetos OperaMate comparen sus valores absolutos. El siguiente programa demuestra esto:

// Uso de wildcard
class OperaMate <T extends Number>{

    T num;

    // Pase al constructor una referencia a un objeto numérico.
    OperaMate( T n){
        num=n;
    }

    //Devuelve el recíproco
    double reciproco(){
        return 1/num.doubleValue();
    }

    //Devuelve parte fraccionaria
    double fraccion(){
        return num.doubleValue()-num.intValue();
    }

    boolean igualAbs(OperaMate<?>ob){
        if(Math.abs(num.doubleValue())==Math.abs(ob.num.doubleValue()))
            return true;
        return false;
    }
}


//Demostrar un comodín
class DemoWildcard{
    public static void main(String[] args) {
        OperaMate<Integer> iOb=
                new OperaMate<Integer>(5);

        OperaMate<Double> dOb =
                new OperaMate<Double>(-5.0);

        OperaMate<Long> lOb= new OperaMate<Long>(4L);

        System.out.println("Comparando iOb y dOb");
        if (iOb.igualAbs(dOb))
            System.out.println("Los valores absolutos son iguales.");
        else
            System.out.println("Los valores absolutos son diferentes.");

        System.out.println();

        System.out.println("Comparando iOb y lOb");
        if (iOb.igualAbs(lOb))
            System.out.println("Los valores absolutos son los mismos.");
        else
            System.out.println("Los valores absolutos son diferentes.");


    }
}

Salida:

Comparando iOb y dOb
Los valores absolutos son iguales.

Comparando iOb y lOb
Los valores absolutos son diferentes.

En el programa, observe estas dos llamadas a igualAbs():

if (iOb.igualAbs(dOb))
if (iOb.igualAbs(lOb))

En la primera llamada, iOb es un objeto de tipo OperaMate<Integer> y dOb es un objeto de tipo OperaMate<Double>. Sin embargo, mediante el uso de un comodín, es posible que iOb pase dOb en la llamada a igualAbs(). Lo mismo se aplica a la segunda llamada, en la que se pasa un objeto de tipo OperaMate<Long>.

Un último punto: es importante entender que el comodín no afecta qué tipo de objetos OperaMate se pueden crear. Esto se rige por la cláusula extends en la declaración OperaMate. El comodín simplemente coincide con cualquier objeto OperaMate válido.

3. Wildcards Limitados

Los argumentos de comodines pueden delimitarse de forma muy similar a como se puede delimitar un parámetro de tipo. Un comodín delimitado es especialmente importante cuando está creando un método que está diseñado para operar solo en objetos que son subclases de una superclase específica. Para entender por qué, trabajemos a través de un simple ejemplo. Considere el siguiente conjunto de clases:

class A{
    //...
}
class B extends A{
    //...
}
class C extends A{
    //...
}
//Note que D no extiende de A
class D{
    //...
}

Aquí, la clase A se extiende por las clases B y C, pero no por D. Luego, considere la siguiente clase genérica muy simple:

//Una simple clase genérica
class Gen<T>{
    T ob;

    Gen(T o){
        ob=o;
    }
}

Gen toma un parámetro de tipo, que especifica el tipo de objeto almacenado en ob. Como T no tiene límites, el tipo de T no está restringido. Es decir, T puede ser de cualquier tipo de clase.

Supongamos ahora que desea crear un método que tome como argumento cualquier tipo de objeto Gen siempre que su parámetro de tipo sea A o una subclase de A. En otras palabras, desea crear un método que opere solo en objetos de Gen<tipo>, donde el tipo es A o una subclase de A. Para lograr esto, debe usar un comodín delimitado. Por ejemplo, aquí hay un método llamado prueba() que acepta como argumento solo objetos Gen cuyo tipo de parámetro es A o una subclase de A:

//Aquí ? coincidirá con A o cualquier tipo de clase que se extienda A.
static void prueba (Gen<? extends A>o){
//...
}

3.1. Ejemplo de comodín delimitado

La siguiente clase muestra los tipos de objetos Gen que se pueden pasar a prueba().

class A{
    //...
}
class B extends A{
    //...
}
class C extends A{
    //...
}
class D{
    //...
}
//Una simple clase genérica
class Gen<T>{
    T ob;
    Gen(T o){
        ob=o;
    }
}

class WildcardsLimitados {
    //Aquí ? coincidirá con A o cualquier tipo de clase que se extienda A.
    static void prueba (Gen<? extends A> o){
        //...
    }
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        C c = new C();
        D d = new D();

        Gen<A> w=new Gen<A>(a);
        Gen<B> w2=new Gen<B>(b);
        Gen<C> w3=new Gen<C>(c);
        Gen<D> w4=new Gen<D>(d);

        //Estas llamadas a prueba() están bien.
        prueba(w);
        prueba(w2);
        prueba(w3);

        // No se puede llamar a prueba() con w4
        // porque no es un objeto de una clase que hereda A.
        // prueba(w4) //ERROR
        // Esto es ilegal porque w4 no es una subclase de A.
    }
}

En main(), se crean objetos de tipo A, B, C y D. Estos se usan para crear cuatro objetos Gen, uno para cada tipo. Finalmente, se realizan cuatro llamadas a prueba(), con la última llamada comentada. Las primeras tres llamadas son válidas porque w, w2 y w3 son objetos Gen cuyo tipo es A o una subclase de A. Sin embargo, la última llamada a prueba() es ilegal porque w4 es un objeto de tipo D, que no es derivado de A.

Por lo tanto, el comodín delimitado en prueba() no aceptará w4 como argumento. En general, para establecer un límite superior para un comodín, use el siguiente tipo de expresión de comodín:

<? extends superclase>

donde superclase es el nombre de la clase que sirve como límite superior.

Recuerde, esta es una cláusula inclusiva porque la clase que forma el límite superior (especificado por la superclase) también está dentro de los límites. También puede especificar un límite inferior para un comodín agregando una cláusula super a una declaración de comodín. Aquí está su forma general:

<? super subclase>

En este caso, solo las clases que son superclases de subclase son argumentos aceptables. Esta es una cláusula inclusiva.

Genéricos en Java
  • Wildcards en Java

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.