2014-10-09 15 views
8

I farmaci generici sono difficili. E sembra che siano trattati in modo diverso nelle diverse versioni di Java.Perché compila in Java7 e non in Java8?

Questo codice viene compilato con successo in Java 7 e non riesce a compilare con Java 8.

import java.util.EnumSet; 

public class Main { 
    public static void main(String[] args) { 
    Enum foo = null; 
    tryCompile(EnumSet.of(foo)); 
    } 

    static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) {} 

    static interface Another {} 
} 

Ecco un messaggio di errore da Java 8. Ho usato questo per compilarlo: http://www.compilejava.net/

/tmp/java_A7GNRg/Main.java:6: error: method tryCompile in class Main cannot be applied to given types; 
    tryCompile(EnumSet.of(foo)); 
    ^
    required: Iterable<C> 
    found: EnumSet 
    reason: inferred type does not conform to upper bound(s) 
    inferred: Enum 
    upper bound(s): Enum<Enum>,Another 
    where C is a type-variable: 
    C extends Enum<C>,Another declared in method <C>tryCompile(Iterable<C>) 
/tmp/java_A7GNRg/Main.java:6: warning: [unchecked] unchecked method invocation: method of in class EnumSet is applied to given types 
    tryCompile(EnumSet.of(foo)); 
         ^
    required: E 
    found: Enum 
    where E is a type-variable: 
    E extends Enum<E> declared in method <E>of(E) 
1 error 
1 warning 

La domanda riguarda la differenza tra le versioni del compilatore Java.

+10

Di cosa si lamenta Java 8? – MadProgrammer

+1

In effetti, l'errore di compilazione in Java 8 * dovrebbe * spiegare perché non si sta compilando. E molto probabilmente ... –

+0

Cosa ti aspetti da questo codice? Inoltre, il tuo 'Enum' è un tipo grezzo. –

risposta

12

La principale differenza tra Java 7 e Java 8 è l'inferenza del tipo di destinazione. Mentre Java 7 considera solo i parametri di un richiamo del metodo per determinare gli argomenti del tipo, Java 8 utilizzerà il tipo di destinazione di un'espressione, cioè il tipo di parametro nel caso di un richiamo del metodo nidificato, il tipo della variabile inizializzata o assegnata a, o il tipo di ritorno del metodo in caso di una dichiarazione return.

E.g. durante la scrittura, List<Number> list=Arrays.asList(1, 2, 3, 4);, Java 7 dedurrà il tipo List<Integer> per il lato destro osservando gli argomenti del metodo e genererà un errore mentre Java 8 utilizzerà il tipo di destinazione List<Number> per dedurre il vincolo che gli argomenti del metodo devono essere istanze di Number che è il caso. Pertanto, è legale in Java 8.

Se siete interessati ai dettagli formali, si può studiare la “Java Language Specification, Chapter 18. Type Inference”, soprattutto §18.5.2. Invocation Type Inference, tuttavia, che non è facile lettura ...

Che cosa succede quando si dice Enum foo = null; tryCompile(EnumSet.of(foo));?

In Java 7 del tipo dell'espressione EnumSet.of(foo) verrà dedotto osservando il tipo dell'argomento, foo che è il tipo grezzo Enum, quindi un'operazione deselezionata verrà eseguita e il tipo di risultato è il tipo grezzo EnumSet. Questo tipo implementa il tipo raw Iterable e quindi può essere passato a tryCompile formando un'altra operazione non controllata.

In Java 8 il tipo di destinazione di EnumSet.of(foo) è il tipo del primo parametro di tryCompile che è Iterable<C extends Enum<C> & Another>, quindi senza entrare troppo nei dettagli, in Java 7 EnumSet.of saranno trattati come tipo invocazione grezzo perchè ha un tipo grezzo argomento, in Java 8 verrà trattato come invocazione generica perché ha un tipo di target generico. Trattandolo come una chiamata generica, il compilatore concluderà che il tipo trovato (Enum) non è compatibile con il tipo richiesto C extends Enum<C> & Another. Mentre si può ottenere assegnando il tipo grezzo Enum a C extends Enum<C> con un avviso non controllato, sarà considerato incompatibile con Another (senza un cast di tipo).

si può infatti inserire un cast:

Enum foo = null; 
tryCompile(EnumSet.of((Enum&Another)foo)); 

Questo compila, ovviamente non senza un avvertimento incontrollato a causa della cessione di Enum a C extends Enum<C>.

È possibile inoltre allo scioglimento del rapporto tipo di destinazione in modo che vengano eseguiti gli stessi passi in Java 7:

Enum foo = null; 
EnumSet set = EnumSet.of(foo); 
tryCompile(set); 

Qui, i tipi grezzi sono utilizzati in tutto le tre linee in modo tale compila con avvisi incontrollati e lo stesso ignoranza sul vincolo implements Another come in Java 7.

+0

Questa è una spiegazione fantastica. Qualche possibilità che tu conosca una fonte che posso leggere per essere in grado di ottenere cose del genere in cima alla mia testa? E sì, non una JLS, qualcosa di più ... leggibile –

+1

C'è una breve introduzione alla digitazione di destinazione [qui] (http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html#target_types) e [qui] (http : //docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#target-typing). Per un generale "cosa c'è di nuovo in Java 8" puoi guardare [qui] (http://docs.oracle .com/javase/8/docs/technotes/guides/language/enhancements.html). Potresti trovare molti altri siti in internet che raccontano simili usando altre parole e altri esempi, tuttavia, non conosco un sito andando sostanzialmente più in profondità senza essere troppo formale. – Holger

0

Sembra un errore corretto per me:

reason: inferred type does not conform to upper bound(s) 
    inferred: Enum 
    upper bound(s): Enum<Enum>,Another 

EnumSet.of(foo) avranno tipo EnumSet<Enum>, che non è compatibile con C extends Enum<C> & Another, per la stessa ragione che Set<Enum> non è compatibile con Set<? extends Enum>, dal momento che Java i generici sono invarianti.

+0

Quindi non sarebbe compilato in Java 7. Lo fa. –

+0

Buon punto. Sono curioso di che tipo Java 7 dedica qui. –

+0

Enum in Java ha un tipo 'Enum >', quindi corrisponde a 'C estende Enum ' per quanto posso dire. –

2

Il motore di inferenza del tipo in Java 8 è stato migliorato e (presumo) è ora in grado di determinare che il tipo C non si estende Another.

In Java 7 il sistema di inferenza del tipo non è stato in grado o non si è preoccupato di determinare che il tipo Another mancava e ha dato al programmatore il vantaggio del dubbio (in fase di compilazione).

si continua a pagare per la trasgressione in fase di esecuzione se si chiama metodi sull'interfaccia Another in fase di esecuzione in Java 7.

Ad esempio, questo codice:

import java.util.EnumSet; 

public class Main { 

    static enum Foo { 
    BAR 
    } 

    public static void main(String[] args) { 
    Enum foo = Foo.BAR; 
    tryCompile(EnumSet.of(foo)); 
    } 

    static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) { 
    i.iterator().next().doSomething(); 
    } 

    static interface Another { 
    void doSomething(); 
    } 
} 

produrrà questo errore in fase di esecuzione :

Exception in thread "main" java.lang.ClassCastException: Main$Foo cannot be cast to Main$Another 
    at Main.tryCompile(Main.java:16) 
    at Main.main(Main.java:12) 

Anche se il compilatore Java 7 sarà compilare il codice, dà ancora avvertimenti circa i tipi prime e invocazioni incontrollati whic Dovresti avvisarti di qualcosa che non va.


Ecco un esempio molto semplice che non utilizzano un enum, ma modellato sulla definizione di Enum che presenta lo stesso problema. Compila con avvisi in Java 7, ma non in Java 8:

import java.util.Collections; 
import java.util.List; 

public class Main { 

    static class Foo<T extends Foo<T>> { 
    } 

    static class FooA extends Foo<FooA> { 
    } 

    public static <T extends Foo<T>> List<T> fooList(T e) { 
    return Collections.singletonList(e); 
    } 


    public static void main(String[] args) { 
    Foo foo = new FooA(); 
    tryCompile(fooList(foo)); 
    } 

    static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) { 
    i.iterator().next().doSomething(); 
    } 

    static interface Another { 
    void doSomething(); 
    } 
} 

Quindi non è un problema specifico Enum, ma può essere a causa dei tipi ricorsivi coinvolti.

+0

Nah, sono previste eccezioni di runtime. Sono d'accordo. Ma se provi a sostituire Enum con il tuo Foo, la compilazione si romperebbe dicendo qualcosa su un altro. Quindi questo è un problema specifico di Enum, da quello che vedo. –

+1

Sì, ma il compilatore può capire che 'Foo' non implementa l'interfaccia' Altro'. Non penso che sia enum specifico, ma probabilmente è specifico per le classi con parametri di tipo. Fammi controllare. – msandiford

-1

compila per me in Eclipse Versione standard/SDK: Luna Release (4.4.0) ID build: 20140612-0600 con Eclipse JDT (Java Development Tools) Patch con supporto Java 8 (per Kepler SR2) 1.0. 0.v20140317-1956 org.eclipse.jdt.java8patch.feature.group Eclipse.org installato.

ottengo alcuni avvisi (tipo non elaborato su foo e invocazione non selezionata su tryCompile.

+0

Se stai ricevendo degli avvisi, verrà compilato come java 7 .. Devi compilare come java 8 completo – NightSkyCode

+2

oops, sembra che abbia mentito. hai ragione, mi stavo compilando alla versione 1.7 :( –