2015-09-02 7 views
5

To nie jest pytanie JavaFX, ale próbuję napisać interfejs w JavaFX, który deklaruje, że klasa jest widoczna. Widoczne klasy mają mieć metodę view(), która zwraca obiekt Node reprezentujący to, co można zobaczyć. Prosty do tej pory, ale tutaj jest, gdzie się komplikuje. Zwrócony węzeł powinien mieć zagwarantowaną metodę getViewable() zwracającą obiekt, który można obejrzeć. Jak to zrobić? Moim pierwszym odruchem było spróbować czegoś takiego:Egzekwowanie wielu ogólnych ograniczeń w języku Java Zwróć typ

interface Viewable<V extends Viewable<V>>{ 
    <N extends Node&View<V>>N view(); 
} 
interface View<V extends Viewable<V>>{ 
    V getViewable(); 
} 

które na pierwszy pojawia się dźwięk i pozwala klasy jak następuje:

class ViewableObject implements Viewable<ViewableObject>{ 
    @Override public ObjectView view(){ 
     return new ObjectView(); 
    } 
    class ObjectView extends Pane implements View<ViewableObject>{ 
     @Override public ViewableObject getViewable(){ 
      return ViewableObject.this; 
     } 
    } 
} 

Jednak z jakiegoś powodu, ta klasa zestawia również:

class ViewableObject implements Viewable<ViewableObject>{ 
    @Override public Pane view(){ 
     return new Pane(); 
    } 
} 

Okienko jest węzłem, ale nie implementuje widoku, więc dlaczego ta klasa się kompiluje? Myślę, że to narusza kontrakt metody view(). Nawet nieznajomy, ta sama klasa nie kompiluje się, gdy Pane jest zastąpiony Obiektem:

class ViewableObject implements Viewable<ViewableObject>{ 
    @Override public Object view(){//complains this is not an @Override 
     return new Object(); 
    } 
} 

Co tu się dzieje? Czy moja wiedza na temat generyków jest wadliwa? Jak mogę sprawić, aby działało zgodnie z zamierzeniami?

+0

wszystko w imię zgodności wstecznej z kodem generycznym. ale moja lektura [spec] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.5) wydaje się nie zgadzać z kompilatorem. w każdym razie może to być bezpieczne, pod warunkiem, że programista wypełnia umowy pozapasmowe, które nie są wyrażone w systemie typu. w tym przypadku zwracanie podtypu okienka implementującego widok – ZhongYu

Odpowiedz

2

Nie chcesz używać ogólnej metody w tym przypadku, ponieważ Twoim celem jest naprawienie typu zwracanej wartości view(). Ogólna metoda pozwala określić typy betonu. Tak naprawdę robisz dokładnie odwrotny od wymuszania.

Myślę, że chcesz zbudować parametr typu typu węzła w definicjach interfejsu, co wymusi, że view() zwraca poprawny typ. Może coś takiego:

interface Viewable<V extends Viewable<V, N>, N extends Node & View<V, N>> { 
    N view(); 
} 

interface View<V extends Viewable<V, TNode>, TNode extends Node & View<V, TNode>> { 
    V getViewable(); 
} 

class ViewableObject implements Viewable<ViewableObject, ViewableObject.ObjectView> { 
    @Override 
    public ObjectView view() { 
     return new ObjectView(); 
    } 

    class ObjectView extends Pane implements View<ViewableObject, ObjectView> { 
     @Override 
     public ViewableObject getViewable() { 
      return ViewableObject.this; 
     } 
    } 
} 

Jeśli przyjrzeć kodu bajtowego dla deklaracji Viewable.view(), zobaczysz, że kompilator wybiera pierwszy typ związany określić jako rzeczywisty typ zwrotu dla metody. Oto odpowiednie linie na wyjściu z kodu bajtowego widza IntelliJ:

// declaration: N view<N extends org.cumberlw.viewtest.Node, org.cumberlw.viewtest.View<V>>() 
public abstract view()Lorg/cumberlw/viewtest/Node; 

Więc kiedy nadrzędny można określić dowolny typ, który jest kowariantna tylko pierwszego rodzaju i kompilator będzie akceptować. Jeśli przełączyć kolejność granicach typu, to co zobaczysz w przeglądarce kodu bajtowego:

// declaration: N view<N extends org.cumberlw.viewtest.View<V>, org.cumberlw.viewtest.Node>() 
public abstract view()Lorg/cumberlw/viewtest/View; 

Zauważ, że kod bajtowy mówi zwracana jest wartość Zobacz teraz. Teraz twój drugi przykład nie będzie się kompilował, ponieważ Pane nie jest podklasą widoku. Żadna kolejność parametrów nie pozwoli na kompilację trzeciego przykładu, ponieważ obiekt nie jest podklasą węzła lub widoku.


Zastąpienie metody generycznym typem powrotu z wieloma granicami może łatwo spowodować błędy w czasie wykonywania. Kompilator wymusza tylko, że typy zwracane są kowariantne z pierwszym typem związanym, więc możesz zwrócić typ, który nie jest zgodny z drugim typem powiązania. Na przykład kompiluje się dobrze, ale ulega awarii w czasie wykonywania:

interface DogLike { 
    void bark(); 
} 

interface CatLike { 
    void meow(); 
} 

class Dog implements DogLike { 
    @Override 
    public void bark() { 
     System.out.println("Woof"); 
    } 
} 

interface MoreauMachine { 
    <H extends DogLike & CatLike > H createHybrid(); 
} 

class MalfunctioningDogCatFactory implements MoreauMachine { 

    @Override 
    public DogLike createHybrid() { 
     //Compile with -Xlint:unchecked to see a warning here: 
     //Warning:(84, 20) java: createHybrid() in org.cumberlw.viewtest.MalfunctioningDogCatFactory implements <H>createHybrid() in org.cumberlw.viewtest.MoreauMachine 
     //return type requires unchecked conversion from org.cumberlw.viewtest.DogLike to H 
     return new Dog(); 
    } 

    public static void main(String[] args) { 
     MoreauMachine factory = new MalfunctioningDogCatFactory(); 

     //crashes! 
     //Exception in thread "main" java.lang.ClassCastException: org.cumberlw.viewtest.Dog cannot be cast to org.cumberlw.viewtest.CatLike 
     factory.createHybrid().meow(); 
    } 
} 
Powiązane problemy