2012-09-23 26 views
7

¿Por qué Bar.go es con el argumento f2 pero no con el argumento f1?comodín de Java en el tipo genérico de varios niveles

public class HelloWorld { 
    public static void main(String[] args) { 
     Foo<Foo<?>> f1 = new Foo<Foo<?>>(); 
     Foo<Foo<String>> f2 = new Foo<Foo<String>>(); 
     Bar.go(f1);  // not OK 
     Bar.go(f2);  // OK 
    } 

    public static void p(Object o) { 
     System.out.println(o); 
    } 
} 

class Foo<E> { 
} 

class Bar { 
    public static <T> void go(Foo<Foo<T>> f) { 
    } 
} 

¿No debería el compilador inferir automáticamente el tipo T como capture of ? en ambos casos?

Respuesta

2

¡Buena pregunta!

(En los siguientes comentarios, WRT una clase genérica en E como Foo<E>, defina "método covariante" como un método que devuelve una E sin tener ningún parámetro usando E, y un "método contravariant" como lo contrario: una que toma un parámetro formal del tipo E pero no devuelve un tipo que implique E. [La definición real de estos términos es más complicada, pero no importa que por ahora.])

Parece que el compilador intenta vincular T a Object en el caso de f1, porque si lo hace

class Bar0 { 
    public static <T> void go(Foo< Foo< ? extends T > > f) { 
     // can pass a Foo<T> to a contravariant method of f; 
     // can use any result r of any covariant method of f, 
     // but can't pass T to any contravariant method of r 
    } 
} 

luego los go(f1) obras, pero ahora go(f2) no lo hace, porque a pesar de Foo<String> <: Foo< ? extends String >, que no implica que Foo< Foo<String> > <: Foo< Foo< ? extends String > >.

Estas son algunas modificaciones que compilan tanto para f1 y f2:

class Bar1 { 
    public static <T> void go(Foo< ? super Foo<T> > f) { 
     // can't properly type the results of any covariant method of f, 
     // but we can pass a Foo<T> to any contravariant method of f 
    } 
} 

class Bar2 { 
    public static <T> void go(Foo< ? extends Foo< ? extends T > > f) { 
     // can't pass a Foo<T> to a contravariant method of f; 
     // can use result r of any covariant method of f; 
     // can't pass a T to a contravariant method of r; 
     // can use result of covariant method of r 
    } 
} 
+0

Es genial ver ejemplos de trabajo. – Cyker

+0

¿Qué tal el caso de 'Foo >'? Si necesito trabajar en términos de 'Foo ', puedo escribir 'Foo > 'pero siento que traerá más varianza que en realidad me gustaría si pudiera usar' Foo > '. ¿Cómo se acepta la aceptación de 'Foo ' como 'Foo ' en términos de varianza? – ony

+1

Si entiendo tu pregunta @ony, no la acepto en absoluto. 'Foo >' no es un 'Foo < Foo< ? >>'.En general, 'G < S >' no es un 'G < T >' incluso si 'S' es una' T', pero 'G < S >' es una 'G 'y' G < T > 'es un' G < ? super S > '. 'Foo < ? >' es la abreviatura de 'Foo ', por lo que' Foo < String > 'es un' Foo < ? > '. Pero deje que 'Foo < String >' sea 'S' y' Foo < ? > 'be' T'. La falta de relación de arriba significa 'Foo < S >' no es un 'Foo < T >'. –

2
Foo<Foo<?>> f1 = new Foo<Foo<?>>(); 

Esto implica que el tipo es desconocido y objetos de cualquier tipo puede ser añadido a Foo<Foo<?>> que son heterogéneos y el compilador no puede garantizar que todos los objetos en Foo<Foo<?>> son del mismo tipo. Por lo tanto, no se puede pasar a Bar.go que toma un tipo limitado como parámetro.

En cambio, puede declarar que como Foo<Foo<Object>> f1 = new Foo<Foo<Object>>(); para pasarlo a Bar.go donde menciona explícitamente que todo es del tipo Object.

+0

realidad, suponiendo 'foo' es como una colección, es el tipo sugerido que es heterogéneo. 'Foo >' significa un Foo que contiene Foos que contiene algún tipo de elemento específico y homogéneo que no está especificado. –

+0

La idea era impulsar el punto de que 'Foo >' funciona en tipo ilimitado y, por lo tanto, el compilador no puede hacerla coincidir con una contraparte limitada. Y sí, estoy de acuerdo en que la terminología era más suponer que 'Foo' es un contenedor que no tiene que ser, pero el punto sigue siendo válido. – Vikdor

+0

@JudgeMental: No, 'Foo >' es heterogéneo y puede contener 'Foo ' y 'Foo ' al mismo tiempo. – newacct

0

Una Niza Lee What do multi-level wildcards mean?

Ejemplo:

Collection< Pair<String,Long> >  c1 = new ArrayList<Pair<String,Long>>(); 

Collection< Pair<String,Long> >  c2 = c1; // fine 
Collection< Pair<String,?> >   c3 = c1; // error 
Collection< ? extends Pair<String,?> > c4 = c1; // fine 

Por supuesto, podemos asignar un Collection<Pair<String,Long>> a un Collection<Pair<String,Long>>. No hay nada sorprendente aquí.

Pero no podemos asignar un Collection<Pair<String,Long>> a un Collection<Pair<String,?>>. El tipo parametrizado Collection<Pair<String,Long>> es una colección homogénea de pares de un String y un Long; el tipo parametrizado Collection<Pair<String,?>> es una colección heterogénea de pares de String y algo de tipo desconocido. El heterogéneo Collection<Pair<String,?>> podría contener, por ejemplo, un Pair<String,Date> y que claramente no pertenece a un Collection<Pair<String,Long>>. Por esta razón, la asignación no está permitida.

0

Voy a demostrar que si el compilador permite Bar.go(f1);, el sistema de tipos (de seguridad) se rompería:

Java gramática le permite usar T como un tipo para declarar variables en go(). Algo como: T t = <something>.

Ahora, vamos a usar ArrayList en lugar de Foo,

entonces tenemos:

class HW { 
public static void main(String[] args) { 
     ArrayList<ArrayList<?>> f1 = new ArrayList<ArrayList<?>>(); 
     go(f1);  // not OK 
    } 

    public static <T> void go(ArrayList<ArrayList<T>> f) { 
    } 
} 

ArrayList<?> es supertipo de ArrayList<String>, también es supertipo de ArrayList<Integer>, lo que significa que puede hacer lo siguiente en main :

ArrayList<?> s = new ArrayList<String>(); 
f1.add(s); 

ArrayList<?> i = new ArrayList<Integer>(); 
f1.add(i); 

Ahora, supongamos que el compilador le permite llamar al go() con f1 como argumento. La opción para inferir T son:

  1. T = Object, pero no es ArrayList<ArrayList<Object>>ArrayList<ArrayList<?>> porque ArrayList<Object> no es del mismo tipo que ArrayList<?> opción de modo que no está permitido.

  2. T = ?, entonces podríamos ser capaces de hacer:

    public static <T> void go(ArrayList<ArrayList<T>> f) { 
        ArrayList<T> alt1 = f.get(0); // ArrayList<String> 
        T str = alt1.get(0); 
        ArrayList<T> alt2 = f.get(1); // ArrayList<Integer> 
        alt2.add(str); // We have added String to List<Integer> 
        // ... type system broken 
    } 
    

go() para trabajar en ambos casos lo que tiene que hacer:

public static void go(ArrayList<? extends ArrayList<?>> f) { 
} 
+0

Su prueba parece estar rota. ¿Por qué debería 'alt.add (nuevo Integer (1)) '(supongo que quiere decir' agregar')? La subtipificación entre 'T' y' Integer' es desconocida, sería rechazada. Una prueba de trabajo sería: 'f.get (1) .add (f.get (0) .get (0))', efectivamente agregando un 'String' a una lista de' Integer's. –

+0

@Ben Schulz Gracias por descubrir mi error. Fijo. –

Cuestiones relacionadas