Il seguente programma di test è derivato da un programma più complicato che fa qualcosa di utile. Compila con successo con il compilatore Eclipse.È possibile dedurre argomenti di tipo Java dai limiti dei parametri di tipo?
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> {}
}
Tuttavia, con l'Oracle JDK 1.7 javac, non riesce con questo:
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
quale compilatore è giusto?
Un aspetto sospetto dell'output precedente è CAP#1 extends Foo<?,?>
. Mi aspetto che il tipo di limiti variabili sia CAP#1 extends Foo<CAP#2,CAP#3>
. In questo caso, il limite dedotto di CAP#1
si conformerebbe ai limiti dichiarati. Tuttavia, questo potrebbe essere una falsa pista, a causa C dovrebbe effettivamente essere dedotto di essere CAP#1
, ma il messaggio di errore è per quanto riguarda A e B.
Nota che se sostituisco linea 26 con la seguente, entrambi i compilatori accettano il programma:
private static <C extends Foo<?, ?>> Foo<?, ?> make(Class<C> cls)
Tuttavia, ora non può fare riferimento ai tipi catturate dei parametri Foo
.
Aggiornamento: Analogamente accettato da entrambe compilatori (ma anche inutile) è presente:
private static <A, B, C extends Foo<? extends A, ? extends B>>
Foo<? extends A, ? extends B> make(Class<C> cls)
Essa provoca essenzialmente A
e B
essere banalmente dedurre come Object
, e quindi ovviamente non utile in qualsiasi contesto. Tuttavia, conferisce credibilità alla mia teoria al di sotto del fatto che javac
eseguirà solo l'inferenza sui limiti dei caratteri jolly, non sui limiti di acquisizione. Se nessuno ha idee migliori, questa potrebbe essere la (sfortunata) risposta. (End Modifica)
mi rendo conto tutta questa domanda è probabile TL; DR, ma io continuerò nel caso in cui qualcun altro sta colpendo questo numero ...
Sulla base di JLS 7, §15.12.2.7 Inferring Type Arguments Based on Actual Arguments , ho fatto la seguente analisi:
Dato un vincolo della forma ,
A = F
, oA >> F
:
Inizialmente, abbiamo un vincolo del modulo , che indica che il tipo A
è convertibile in tipo F
mediante la conversione del metodo di conversione (§5.3). Qui, A
è Class<CAP#1 extends Foo<CAP#2, CAP#3>>
e F
è Class<C extends Foo<A, B>>
. Notare che gli altri moduli di vincolo (A = F
e A >> F
) si presentano solo quando l'algoritmo di inferenza ricorre.
Avanti, C
deve dedurre essere CAP#1
dalle seguenti regole:
(2.) Altrimenti, se il vincolo ha la forma :
- Se
F
ha la formaG<..., Yk-1, U, Yk+1, ...>
, doveU
è un'espressione tipo che coinvolgeTj
, quindi seA
ha un supertipo della formaG<..., Xk-1, V, Xk+1, ...>
doveV
è un'espressione di tipo, , questo algoritmo viene applicato in modo ricorsivo al vincoloV = U
.
Qui, G
è Class
, U
e Tj
sono C
, e V
è CAP#1
. applicazione ricorsiva per CAP#1 = C
dovrebbe comportare il vincolo C = CAP#1
:
(3.) Altrimenti, se il vincolo ha la forma
A = F
:
- Se
F = Tj
, quindi il vincoloTj = A
è implicito.
Fino a questo punto, l'analisi sembra essere d'accordo con l'uscita javac. Forse il punto di divergenza è se continuare a tentare di dedurre A
e B
. Ad esempio, in questa regola
- Se
F
ha la formaG<..., Yk-1, ? extends U, Yk+1, ...>
, doveU
comportaTj
, quindi seA
ha un supertipo che è uno tra:
G<..., Xk-1, V, Xk+1, ...>
, doveV
è un tipo di espressione .G<..., Xk-1, ? extends V, Xk+1, ...>
.Allora questo algoritmo è applicata in modo ricorsivo al vincolo
V << U
.
Se CAP#1
è considerato un carattere jolly (che è una cattura di), allora questa regola vale, e inferenza continua ricorsivamente con U
come Foo<A, B>
e V
come Foo<CAP#2, CAP#3>
. Come sopra, ciò darebbe A = CAP#2
e B = CAP#3
.
Tuttavia, se CAP#1
è semplicemente una variabile di tipo, quindi nessuna delle regole sembra considerare i suoi limiti. Forse questa concessione alla fine della sezione nella specifica si riferisce a tali casi: algoritmo di inferenza
Il tipo deve essere visto come un euristico, progettato per funzionare bene in pratica. Se non riesce ad inferire il risultato desiderato, al suo posto possono essere usati parametri di tipo esplicito.
Ovviamente, i caratteri jolly non possono essere utilizzati come parametri di tipo esplicito. :-(
Quale versione di 'javac' stai usando? 'javac -version' – Jeffrey
@Jeffrey: più specificatamente di 1.7? 'javac 1.7.0_25' –
@BevynQ: Sì, quell'esempio" soluzione "era nella mia domanda. Tuttavia, non è utile, poiché il punto di 'make' è quello di acquisire i caratteri jolly, ad es. per rappresentare più istanze dello stesso tipo in ulteriori chiamate di metodo o creando altre istanze di oggetti con tali parametri di tipo. Il problema non ha nulla a che fare con l'assegnazione del risultato a 'Foo ,?>'; quella parte funziona in entrambi i compilatori. –