2011-06-07 23 views
11

es el compilador de Java capaz de inferir el tipo de una función estática genérica de su contexto como argumento a otra función estática genérica?inferir tipos genéricos de funciones genéricas anidadas estáticas

Por ejemplo, tengo una sencilla clase Par:

public class Pair<F, S> { 

    private final F mFirst; 

    private final S mSecond; 

    public Pair(F first, S second) { 
     mFirst = checkNotNull(first); 
     mSecond = checkNotNull(second); 
    } 

    public static <F, S, F1 extends F, S1 extends S> Pair<F, S> of(F1 first, S1 second) { 
     return new Pair<F, S>(first, second); 
    } 

    public F first() { 
     return mFirst; 
    } 

    public S second() { 
     return mSecond; 
    } 

    // ... 
} 

y tengo la siguiente función estática genérica:

public static <F, P extends Pair<F, ?>> Function<P, F> deferredFirst() { 
    return (Function<P, F>)DEFERRED_FIRST; 
} 

private static final Function<Pair<Object, ?>, Object> DEFERRED_FIRST = 
     new Function<Pair<Object,?>, Object>() { 

    @Override 
    public Object apply(Pair<Object, ?> input) { 
     return input.first(); 
    } 
}; 

que deseo utilizar de la siguiente manera (Collections2.transform is from Google Guava):

List<Pair<Integer, Double>> values = ... 
Collection<Integer> firsts = Collections2.transform(values, 
     Pair.deferredFirst()); 

A los que el compilador se queja:

The method transform(Collection<F>, Function<? super F,T>) in the type 
Collections2 is not applicable for the arguments 
(List<Pair<Integer,Double>>, Function<Pair<Object,?>,Object>) 

Parece que el compilador no puede propagar los tipos inferidos para transform() a deferredFirst() como cree que son objetos.

Forzar el compilador para entender los tipos de cualquiera de estas maneras funciona:

Function<Pair<Integer, ?>, Integer> func = Pair.deferredFirst(); 
Collection<Integer> firsts = Collections2.transform(values, func); 


Collection<Integer> firsts = Collections2.transform(values, 
     Pair.<Integer, Pair<Integer, ?>>deferredFirst()); 

¿Es posible cambiar la firma de cualquiera de las funciones para permitir que el compilador para inferir/propagar los tipos?

Editar: Para Bohemia, aquí hay un método posible el ejemplo anterior podría ser utilizado en:

public static int sumSomeInts(List<Pair<Integer, Double>> values) { 
    Collection<Integer> ints = Collections2.transform(values, 
      Pair.deferredFirst()); 
    int sum = 0; 
    for(int i : ints) 
     sum += i; 
    return sum; 
} 
+2

Tenga cuidado cuando tenga 'clase A {public void method() {}}'. Supongo que las 'F, S' en el método anulan las de tu clase (es decir, equivalente a' clase A {public void method() {}} ' – toto2

Respuesta

4

La inferencia de tipos es desagradable y complicado. Tienen que parar en alguna parte. Considere

static <T> T foo(); 

String s = foo(); 

print(foo()) 

En el contexto de asignación, la intención del programador es clara, debe haber TString

En la siguiente línea, no tanto.

print El método no es un ejemplo muy justa, que está muy sobrecargado. Supongamos print no está sobrecargado, su tipo de parámetro se fija, por lo T pueden ser claramente inferidos. ¿No debería el compilador ser lo suficientemente inteligente como para resolverlo?

Eso suena razonable, hasta que uno se aventura a leer el texto de especificaciones relacionadas, 15.12 Method Invocation Expressions Buena suerte cambiar nada en ese lío!

Es tan complicado, ni siquiera los autores del compilador lo entienden. Hay toneladas de errores en javac y otros compiladores que se originaron a partir de esta sección de la especificación.

+0

Gracias, es bueno tener alguna información general. – Hal

1

Prueba este genéricos Kung Fu:

public static int sumSomeInts(List<Pair<Integer, Double>> values) { 
    Collection<Integer> ints = Collections2.transform(values, 
     Pair.<Integer, Double>deferredFirst()); 
    int sum = 0; 
    for(int i : ints) 
     sum += i; 
    return sum; 
} 

puede escribir la llamada a un método y pasar los genéricos a la próxima llamada.

no estoy seguro acerca de los parámetros genéricos exactas para usar aquí, porque no se ha incluido el código suficiente. Si pega el método completo donde está el problema, editaré esta respuesta para hacerla compilar. EDITADO: con nueva información de la pregunta

Háganme saber si compila. Si no es esa solución, estará cerca. La clave es escribir el método estático usando la sintaxis Class.<Type>staticMethod().

+0

He editado con un método de ejemplo simple. – Hal

+0

Gracias por su ayuda. Debido a que parece que no hay solución sin agregar tipos explícitamente a la llamada anidada, acepté la respuesta irrebatible ya que tiene más información general sobre el problema. – Hal

1

Lo que me ocurrió hace un tiempo es:

@SuppressWarnings("rawtypes") 
private static final Function ExtractFirst = new Function() { 
    @Override 
    public Object apply(final Object from) { 
     Preconditions.checkNotNull(from); 
     return ((Pair)from).first; 
    } 
}; 

@SuppressWarnings("unchecked") 
public static <A> Function<Pair<A,?>,A> extractFirst() { 
    return ExtractFirst; 
} 

No deje que los "SuppressWarnings" te desanime, que funciona bien.

Ejemplo:

List<Pair<String,String>> pairs = ImmutableList.of(Pair.of("a", "b"), 
    Pair.of("c", "d"),Pair.of("e", "f")); 
Iterable<String> firsts = Iterables.transform(pairs, 
    Pair.<String>extractFirst()); 

Por desgracia, sí, usted tiene que suministrar el argumento genérico para extractFirst(). Creo que esto es lo mejor que obtendrás sin embargo.

+0

Y 'primeros' sería [" a "," c "," e "], ¿verdad? – Ray

Cuestiones relacionadas