reciben - pasar parametros por linea de comandos java
¿Puede Java inferir argumentos de tipo de los límites de parámetros de tipo? (2)
El siguiente programa de prueba se deriva de un programa más complicado que hace algo útil. Se compila con éxito con el compilador de Eclipse.
import java.util.ArrayList;
import java.util.List;
public class InferenceTest
{
public static void main(String[] args)
{
final List<Class<? extends Foo<?, ?>>> classes =
new ArrayList<Class<? extends Foo<?, ?>>>();
classes.add(Bar.class);
System.out.println(makeOne(classes));
}
private static Foo<?, ?> makeOne(Iterable<Class<? extends Foo<?, ?>>> classes)
{
for (final Class<? extends Foo<?, ?>> cls : classes)
{
final Foo<?, ?> foo = make(cls); // javac error here
if (foo != null)
return foo;
}
return null;
}
// helper used to capture wildcards as type variables
private static <A, B, C extends Foo<A, B>> Foo<A, B> make(Class<C> cls)
{
// assume that a real program actually references A and B
try
{
return cls.getConstructor().newInstance();
}
catch (final Exception e)
{
return null;
}
}
public static interface Foo<A, B> {}
public static class Bar implements Foo<Integer, Long> {}
}
Sin embargo, con Oracle JDK 1.7 javac, falla con esto:
InferenceTest.java:18: error: invalid inferred types for A,B; inferred type does not
conform to declared bound(s)
final Foo<?, ?> foo = make(cls);
^
inferred: CAP#1
bound(s): Foo<CAP#2,CAP#3>
where A,B,C are type-variables:
A extends Object declared in method <A,B,C>make(Class<C>)
B extends Object declared in method <A,B,C>make(Class<C>)
C extends Foo<A,B> declared in method <A,B,C>make(Class<C>)
where CAP#1,CAP#2,CAP#3 are fresh type-variables:
CAP#1 extends Foo<?,?> from capture of ? extends Foo<?,?>
CAP#2 extends Object from capture of ?
CAP#3 extends Object from capture of ?
1 error
¿Qué compilador tiene razón?
Un aspecto sospechoso de la salida anterior es que CAP#1 extends Foo<?,?>
. Espero que los límites de las variables de tipo sean CAP#1 extends Foo<CAP#2,CAP#3>
. Si este fuera el caso, entonces el límite inferido de CAP#1
se ajustaría a los límites declarados. Sin embargo, esto podría ser una pista falsa, ya que se debería inferir que C es CAP#1
, pero el mensaje de error es sobre A y B.
Tenga en cuenta que si reemplazo la línea 26 con lo siguiente, ambos compiladores aceptan el programa:
private static <C extends Foo<?, ?>> Foo<?, ?> make(Class<C> cls)
Sin embargo, ahora no puedo hacer referencia a los tipos capturados de los parámetros de Foo
.
Actualización: el mismo modo aceptado por ambos compiladores (pero también inútil) es este:
private static <A, B, C extends Foo<? extends A, ? extends B>>
Foo<? extends A, ? extends B> make(Class<C> cls)
Esencialmente hace que A
y B
se inferan de forma trivial como Object
, y por lo tanto, obviamente no son útiles en ningún contexto. Sin embargo, da credibilidad a mi teoría a continuación que javac
solo realizará inferencia en los límites de comodín, no en los límites de captura. Si nadie tiene mejores ideas, esta podría ser la respuesta (desafortunada). (Fin de la actualización)
Me doy cuenta de que es probable que toda esta pregunta sea TL; DR, pero continuaré en caso de que alguien más esté tratando este problema ...
Basado en JLS 7, §15.12.2.7 . Inferir argumentos de tipo basados en argumentos reales , he realizado el siguiente análisis:
Dada una restricción de la forma
A << F
,A = F
oA >> F
:
Inicialmente, tenemos una restricción de la forma A << F
, que indica que el tipo A
es convertible al tipo F
por conversión de invocación de método ( §5.3 ). Aquí, A
es Class<CAP#1 extends Foo<CAP#2, CAP#3>>
y F
es Class<C extends Foo<A, B>>
. Tenga en cuenta que las otras formas de restricción ( A = F
y A >> F
) solo surgen a medida que el algoritmo de inferencia recurre.
A continuación, se debe inferir que C
es CAP#1
mediante las siguientes reglas:
(2.) De lo contrario, si la restricción tiene la forma
A << F
:
- Si
F
tiene la formaG<..., Yk-1, U, Yk+1, ...>
, dondeU
es una expresión de tipo que involucraTj
, entonces siA
tiene un supertipo de la formaG<..., Xk-1, V, Xk+1, ...>
dondeV
es una expresión de tipo, este algoritmo se aplica recursivamente a la restricciónV = U
Aquí, G
es Class
, U
y Tj
son C
, y V
es CAP#1
. La aplicación recursiva a CAP#1 = C
debería resultar en la restricción C = CAP#1
:
(3.) De lo contrario, si la restricción tiene la forma
A = F
:
- Si
F = Tj
, entonces la restricciónTj = A
está implícita.
Hasta este punto, el análisis parece estar de acuerdo con la salida de javac. Quizás el punto de la divergencia es si continuar intentando inferir A
y B
Por ejemplo, dada esta regla.
- Si
F
tiene la formaG<..., Yk-1, ? extends U, Yk+1, ...>
G<..., Yk-1, ? extends U, Yk+1, ...>
, dondeU
implicaTj
, entonces siA
tiene un supertipo que es uno de:
G<..., Xk-1, V, Xk+1, ...>
, dondeV
es una expresión de tipo.G<..., Xk-1, ? extends V, Xk+1, ...>
G<..., Xk-1, ? extends V, Xk+1, ...>
.Entonces este algoritmo se aplica recursivamente a la restricción
V << U
Si se considera que el CAP#1
es un comodín (del cual es una captura), entonces esta regla se aplica y la inferencia continúa recursivamente con U
como Foo<A, B>
y V
como Foo<CAP#2, CAP#3>
. Como arriba, esto daría A = CAP#2
y B = CAP#3
.
Sin embargo, si CAP#1
es simplemente una variable de tipo, entonces ninguna de las reglas parece considerar sus límites. Quizás esta concesión al final de la sección en la especificación se refiera a tales casos:
El algoritmo de inferencia de tipos debe verse como un heurístico, diseñado para funcionar bien en la práctica. Si no puede inferir el resultado deseado, se pueden usar parámetros de tipo explícito en su lugar.
Obviamente, los comodines no se pueden utilizar como parámetros de tipo explícito. :-(
Dos cosas que he notado:
CAP#1
no es un comodín, es una variable de tipo debido a la conversión de captura .En el primer paso, el JLS menciona que
U
es la expresión de tipo , mientras queTj
es el parámetro de tipo. El JLS no define explícitamente qué es una expresión de tipo, pero mi intuición es que incluye los límites del parámetro de tipo. Si ese es el caso,U
seríaC extends Foo<A,B>
yV
seríaCAP#1 extends Foo<CAP#2, CAP#3>
. Siguiendo el algoritmo de inferencia de tipo:
V = U
-> C = CAP#1
Y Foo<CAP#2, CAP#3> = Foo<A, B>
Puede continuar aplicando el algoritmo de inferencia de tipo a lo anterior, y terminará con A= CAP#2
y B=CAP#3
.
Creo que has visto un error con el compilador de Oracle
El problema es que empiezas desde la siguiente restricción de inferencia:
class<#1>, #1 <: Foo<?, ?>
Lo que te da una solución para C, es decir, C = # 1.
Luego necesitas verificar si C cumple con los límites declarados: el límite de C es Foo, por lo que terminas con esta comprobación:
#1 <: Foo<A,B>
el cual puede ser reescrito como
Bound(#1) <: Foo<A, B>
por lo tanto:
Foo<?, ?> <: Foo<A, B>
Ahora, aquí el compilador realiza una conversión de captura de la LHS (aquí es donde se generan # 2 y # 3):
Foo<#2, #3> <: Foo<A, B>
Lo que significa
A = #2
B = #3
Entonces, tenemos que nuestra solución es {A = # 2, B = # 3, C = # 1}.
¿Es esa una solución válida? Para responder a esa pregunta, debemos verificar si los tipos inferidos son compatibles con los límites de la variable de inferencia, después de la sustitución de tipo, por lo que:
[A:=#2]A <: Object
#2 <: Object - ok
[B:=#3]B <: Object
#3 <: Object - ok
[C:=#1]C <: [A:=#2, B:=#3]Foo<A, B>
#1 <: Foo<#2, #3>
Foo<?, ?> <: Foo<#2, #3>
Foo<#4, #5> <: Foo<#2, #3> - not ok
De ahí el error.
La especificación está subespecificada cuando se trata de la interacción entre la inferencia y los tipos capturados, por lo que es bastante normal (¡pero no bueno!) Tener diferentes comportamientos al cambiar entre compiladores diferentes. Sin embargo, se está trabajando en algunos de estos problemas, tanto desde la perspectiva del compilador como desde la perspectiva de JLS, por lo que los problemas de este tipo deberían solucionarse a medio plazo.