El concepto de wildcard capture en Java

Escrito por el .
java planeta-codigo
Comentarios

Java

Con la introducción de los generics en el lenguaje Java en la versión de Java 5 se añadió validación de tipos a por ejemplo las colecciones, y entre ellos los elementos wildcard definidos con un ?. Una lista definida como List<?> se considera una lista de elementos de un tipo desconocido, todas las colecciones pre-java5 se consideran a partir de Java 5 de forma efectiva como List<?> o List<? extends Object> a partir de Java 5.

El siguiente código produce un error de compilación con el mensaje capture of ya que el compilador no puede validar que el tipo que se inserta en la lista, Object, como primer elemento si es compatible en tiempo de ejecución con el tipo de elementos que tiene la lista:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.List;

public class WildcardError {

    public void foo(List<?> list) {
        Object object = list.get(0);
        list.set(0, object);
    }

    // @SuppressWarnings("unchecked")
    public void bar(List list) {
        Object object = list.get(0);
        list.set(0, object);
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        WildcardError wildcard = new WildcardError();
        wildcard.foo(list);
        wildcard.bar(list);
    }
}
1
2
3
4
5
6
7
8
9
WildcardError.java:8: error: incompatible types: Object cannot be converted to CAP#1
        list.set(0, object);
                    ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
Note: WildcardError.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

El método bar() define como parámetro una lista raw y es capaz de extraer un Object ya que todo objeto hereda de él e insetar un Object ya que es una lista raw, el compilador realiza el type erasure y la considera como List<Object> pero el compilador advierte del posible error en tiempo de ejecución con el mensaje Note: WildcardError.java uses unchecked or unsafe operations, en este caso la advertencia es innecesaria ya que se inserta un elemento extraído de la propia lista, se puede suprimir anotando el método con @SuppressWarnings(“unchecked”).

Para establecer una relación entre dos tipos se deben usar type parameters, en este caso para el tipo que se extrae de la lista y el tipo insertado en la lista. Para que el código anterior compile hay que escribir un método que capture el tipo del wildcard, estos métodos por convención se nombran añadiendo al final la palabra Helper. En este caso otra alternativa es definir el método como en bar() aunque un List<?> y un List<T> no son lo mismo el primero admite más tipos de listas parametrizadas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.ArrayList;
import java.util.List;

public class Wildcard {

    public void foo(List<?> list) {
        fooHelper(list);
    }

    private <T> void fooHelper(List<T> list) {
        T object = list.get(0);
        list.set(0, object);
    }

    public <T> void bar(List<T> list) {
        T object = list.get(0);
        list.set(0, object);
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Wildcard wildcard = new Wildcard();
        wildcard.foo(list);
        wildcard.bar(list);
    }
}

Este concepto de wildcard capture genera bastantes dudas y en internet hay múltiples artículos tratando de explicarlo.