2016-01-26 12 views
5

risposta si riduce a Java does not support lower bounds on parameterized methods, because such a feature is "not useful enough", fare riferimento a un similar questionPerché il compilatore Java non può inferire correttamente l'ereditarietà?

Dato il seguente frammento:

package demo; 

public class Demo { 
    interface Foo { void foo(); } 
    interface Bar { void bar(); } 
    interface FooBar { 
     <R extends Foo & Bar> R foobar(); 

     static FooBar create() { return new TypicalJavaFooBar(); } 
    } 

    private static final class TypicalJavaFooBar implements Foo, Bar, FooBar { 
     public void bar() { System.out.println("foo"); } 
     public void foo() { System.out.println("bar"); } 

     public <R extends Foo & Bar> R foobar() { 
      return (R) this; 
     } 
    } 

    public static void main(String[] args) { 
     FooBar x = FooBar.create(); 
     Foo foo = x.foobar(); 
     Bar bar = x.foobar(); 
     x.foobar().foo(); 
     x.foobar().bar(); 
    } 
} 

Senza il cast esplicito a R in TypicalJavaFooBar#foobar compilatore non riesce con il seguente errore

Error:(13, 20) java: incompatible types: demo.Demo.TypicalJavaFooBar cannot be converted to R

mio la domanda è perché? Per me, sembra che il compilatore debba avere abbastanza informazioni dal TypicalJavaFooBar è chiaramente definito per implementare sia Foo e Bar; perché non è sufficiente per soddisfare il vincolo Foo & Bar?

UPDATE

L'obiettivo principale di questo esercizio è quello di definire il seguente contratto: metodo foobar invitando un'istanza di un FooBar è garantito per tornare qualcosa che implementa sia Foo e Bar.

+0

@horatius Questo non ha nulla a che fare con questa domanda. – chrylis

+0

In 'TypicalJavaFooBar' è possibile modificare il tipo di ritorno del metodo' foobar' in "TypicalJavaFooBar', che elimina anche il cast. Mentre questo non risponde alla tua domanda, questo è quello che probabilmente farei in questa situazione.Anche se generalmente preferisco la composizione al di sopra dell'eredità e quindi sono improbabile incontrare questa situazione. :-) – Waldheinz

+0

@Andrey, sfortunatamente il tuo "obiettivo principale" non è effettivamente possibile nel sistema di tipi di Java. È possibile restituire qualcosa che è garantito per implementare un'interfaccia specifica, ma non è possibile esprimere "qualcosa che implementa entrambe le interfacce". –

risposta

6

Il tipo di parametro R è legato al metodo dal codice chiamante e potrebbe teoricamente essere Baz implements Foo, Bar; vedere, ad esempio, Collections.emptySet(), il cui parametro di tipo è determinato dal chiamante e può essere influenzato da un tipo di testimone.

Per fare ciò che si sta tentando apparentemente, è necessario spostare il parametro di tipo sull'interfaccia FooBar e disporre di TypicalJavaFooBar implements Foo, Bar, FooBar<TypicalJavaFooBar>.

1

tuo R può essere di qualsiasi tipo, che è un Foo & Bar in modo da poter scrivere

class MyFooBar implements Foo, Bar { ... 


FooBar x = new TypicalJavaFooBar(); 
MyFooBar mfb = x.foobar(); // this compiles now with the cast. 
0

No, il compilatore non può inferenza del tipo di R ed il cast è sbagliato. capire perché, basta estendere la classe come questa:

static class C extends TypicalJavaFooBar { 

    void eat() {} 

    @Override 
    public void bar() {} 

    @Override 
    public void foo() {} 
} 

Poi, il cast permette questo:

FooBar x = new TypicalJavaFooBar(); 
C c = x.foobar(); 
c.eat(); 

che si traduce in un RuntimeException:

Exception in thread "main" java.lang.ClassCastException: demo.Demo$TypicalJavaFooBar cannot be cast to demo.Demo$C 

Questo è il motivo - - sicuramente i progettisti del compilatore non lo permetterebbero. C'è qualcosa di sbagliato nel codice, da cui la necessità del cast.

+0

Ho aggiornato la risposta, ero solo cercando di mantenere la demo breve ... onestamente, trovo le classi non final, public class o final con i costruttori pubblici come pratica orrenda da quando è uscito Java 8. Inoltre, ti manca il punto. il punto qui è di rendere esplicito il seguente contratto: il metodo di chiamata "foobar" su un'istanza di "FooBar" è garantito per restituire * qualcosa * che implementa sia "Foo" sia "Bar". – Andrey

+1

Se non trovi le classi pubbliche non definitive orrende, non ti piaceranno le altre caratteristiche della lingua, come la necessità del cast di mostrare il rischio di ClassCastException in fase di esecuzione. – smarquis

+0

metodi factory statici in interfacce = non si tratta di classi pubbliche. inoltre, ciò che * dovrebbe * far funzionare è definire Foo & Bar come * lower * bound, cioè: "", non vedo ragioni valide per cui specificare limiti inferiori non è supportato ... – Andrey

0

Se si potrebbe usare questo in Java

if (obj instanceof (Foo & Bar)) 

allora non sarebbe stato costretto a fare il cast.

+0

la necessità del cast è qui per mostrare il rischio di java.lang.ClassCastException in fase di esecuzione. – smarquis

+1

Potrebbe essere..IDK ... ma per me non c'è alcuna differenza logica tra 'if (obj instanceof (Foo & Bar))' e 'if ((obj instanceof Foo) & (obj instanceof Bar))', ma il primo non si compila comunque –

Problemi correlati