2016-01-26 16 views
5

Odpowiedź sprowadza się do Java does not support lower bounds on parameterized methods, because such a feature is "not useful enough", odnoszą się do similar questionDlaczego kompilator Java nie może prawidłowo dziedziczyć dziedziczenia?

Biorąc pod uwagę następujący fragment:

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(); 
    } 
} 

bez wyraźnej obsady do R w TypicalJavaFooBar#foobar kompilator nie powiedzie się z powodu następującego błędu

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

My pytanie brzmi: dlaczego? Wydaje mi się, że kompilator powinien mieć wystarczającą ilość informacji, ponieważ TypicalJavaFooBar jest jednoznacznie zdefiniowany do implementacji zarówno Foo, jak i Bar; dlaczego nie wystarczy, aby spełnić warunek Foo & Bar?

UPDATE

Głównym celem tego ćwiczenia jest określenie następującą umowę: wywołanie metody foobar na instancji FooBar gwarantuje powrót coś który implementuje zarówno Foo i Bar.

+0

@horatius To nie ma nic wspólnego z tym pytaniem. – chrylis

+0

W 'TypicalJavaFooBar' możesz zmienić typ zwracania metody' foobar' na 'TypicalJavaFooBar', co również eliminuje obsadę. Chociaż to nie odpowiada na twoje pytanie, to właśnie bym prawdopodobnie zrobił w tej sytuacji.Chociaż generalnie preferuję kompozycję powyżej dziedziczenia i dlatego raczej nie napotkam tej sytuacji. :-) – Waldheinz

+0

@Andrey, niestety twój "główny cel" nie jest faktycznie możliwy w systemie typu Java. Możesz zwrócić coś, co gwarantuje implementację określonego interfejsu, ale nie możesz wyrazić "czegoś, co implementuje oba te interfejsy". –

Odpowiedz

6

Parametr typ R jest związany z metodą kodem wywołującego i może być teoretycznie Baz implements Foo, Bar; patrz na przykład Collections.emptySet(), którego parametr typu jest określany przez dzwoniącego i może na niego wpływać świadek typu.

Aby zrobić to, co najwyraźniej próbujesz, musisz przenieść parametr typu do interfejsu FooBar i mieć TypicalJavaFooBar implements Foo, Bar, FooBar<TypicalJavaFooBar>.

1

Twój R może być dowolnego typu, który jest Foo & Bar więc można napisać

class MyFooBar implements Foo, Bar { ... 


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

Nie, kompilator nie może wnioskowania typ R a obsada jest źle. Aby zrozumieć, dlaczego, po prostu rozszerzyć swoją klasę tak:

static class C extends TypicalJavaFooBar { 

    void eat() {} 

    @Override 
    public void bar() {} 

    @Override 
    public void foo() {} 
} 

Następnie Twój obsada pozwala na to:

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

co skutkuje RuntimeException:

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

Dlatego - - na pewno projektanci kompilatora nie pozwolą na to. W kodzie jest coś nie tak, stąd wymagana obsada.

+0

Uaktualniłem odpowiedź, po prostu starając się utrzymać krótką wersję demo ... szczerze mówiąc, uważam, że niekończące się, publiczne zajęcia lub ostatnie klasy z publicznymi konstruktorami były przerażającą praktyką od czasu wydania Java 8. także, brakuje ci punktu. chodzi o to, by zawrzeć następującą umowę wyraźnie: metoda wywołania "foobar" na instancji "FooBar" gwarantuje zwrot * czegoś *, który implementuje zarówno "Foo", jak i "Bar". – Andrey

+1

Jeśli uważasz, że zajęcia nierealne, publiczne są przerażające, nie spodoba ci się inne funkcje tego języka, takie jak konieczność rzutowania, aby pokazać ryzyko wyjątku ClassCastException w czasie wykonywania. – smarquis

+0

statyczne metody fabryczne w interfejsach = nie dotyczy zajęć publicznych. także, co * sprawi *, że to zadziała definiuje Foo & Bar jako * niższy * granica, tj .: "", nie widzę żadnego uzasadnionego powodu, dla którego określenie dolnych granic nie jest obsługiwane ... – Andrey

0

Jeśli można to wykorzystać w Javie

if (obj instanceof (Foo & Bar)) 

wtedy nie byłby zmuszony do obsady.

+0

konieczność obsady jest tutaj, aby pokazać ryzyko wystąpienia wyjątku java.lang.ClassCastException w środowisku wykonawczym. – smarquis

+1

Może to być..IDK ... ale dla mnie nie ma logicznej różnicy między 'if (obj instanceof (Foo & Bar)) i' if ((obj instanceof Foo) & (obj instanceof Bar)), ale pierwszy nie kompiluje się jednak –

Powiązane problemy