2016-05-06 15 views
6

muszę zaprojektować interfejs do hierarchicznej jednostki:interfejs Projektowanie dla hierarchicznej jednostki

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    Stream<T> getAncestors(); 
} 

Jest to dość łatwe do wdrożenia domyślnygetAncestors() metody pod względem getParent() w taki sposób, aby były wróci Stream z wszyscy przodkowie.

przykładem realizacji:

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    return parentsBuilder.build(); 
} 

Ale muszę także this do strumienia, i tu pojawia się problem. Poniższy wiersz nie jest prawidłowe, ponieważ this jest typu HierarchicalEntity nie T:

parentsBuilder.add(this); // type mismatch! 

Jak mogę przeprojektowanie interfejsu w celu uczynienia getAncestors() obejmują this na wynik?

+5

Niestety to nigdy nie będzie w pełni typu bezpieczne. Java nie ma składni typu self-referencyjnego. Mogę utworzyć 'class Fake implementuje HierarchicalEntity ' i twoje 'getAncestors' prawdopodobnie przestanie z' ClassCastException' ostatecznie. –

+0

Masz rację. Być może można przeprojektować cały interfejs. – Aliaxander

+0

Po prostu wyślij 'to' do' T' i dodaj do konstruktora strumienia –

Odpowiedz

1

Jest to powracający problem podczas tworzenia typów samoodnoszącej się. W typie podstawowym (lub interfejsie) nie można wymusić, aby this było zgodne z T.

Oczywiście można wykonać niezaznaczoną obsadę od this do T, jeśli masz pewność, że wszystkie podtypy spełnią to ograniczenie. Ale musisz wykonać tę niezaznaczoną obsadę, gdy potrzebujesz referencji this jako this jako .

Lepszym rozwiązaniem jest dodanie abstrakcyjną metodę jak

/** 
    All subtypes should implement this as: 

    public T myself() { 
     return this; 
    } 
*/ 
public abstract T myself(); 

Następnie można użyć myself() zamiast this gdy trzeba samoodnoszenie jako T.

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    for(T node = myself(); node != null; node = node.getParent()) { 
     parentsBuilder.add(parent); 
    } 
    return parentsBuilder.build(); 
} 

Oczywiście, nie można wymusić, że podklasy poprawnie wdrożyć myself() jako return this;, ale przynajmniej można łatwo sprawdzić, czy robią w czasie wykonywania:

assert this == myself(); 

Porównanie odniesienia jest bardzo tania operacja i, jeśli myself() jest poprawnie zaimplementowany jako niezmiennie wracający this, HotSpot może z góry udowodnić, że to porównanie zawsze będzie true i całkowicie wymazać czek.

Wadą jest to, że każda specjalizacja będzie musiała mieć tę nadmiarową implementację myself() { return this; }, ale z drugiej strony jest całkowicie wolna od odznaczonych rzutów typu. Alternatywą jest posiadanie deklaracji z myself() w klasie bazowej jako @SuppressWarnings("unchecked") T myself() { return (T)this; } w celu ograniczenia niezaznaczonej operacji do pojedynczego miejsca dla hierarchii typów. Ale nie można sprawdzić, czy naprawdę jest to typ this naprawdę jest typu T ...

1

Nie powiodło się dodanie this, ponieważ HierarchicalEntity<T> niekoniecznie jest T; może to być nieznany podtyp. Jednak T jest zawsze HierarchicalEntity<T>, jak to zadeklarowałeś w ten sposób.

Zmień typ zwracanej getAncestors i na Stream.Builder z T do HierarchicalEntity<T>, który pozwoli Ci dodać this.

default Stream<HierarchicalEntity<T>> getAncestors() { 
    Stream.Builder<HierarchicalEntity<T>> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    parentsBuilder.add(this); 
    return parentsBuilder.build(); 
} 

Można zadeklarować getParent zwrócić HierarchicalEntity<T> również dla spójności.

+0

Ale OP chce strumień 'T'. – shmosel

+0

@shmosel To jest obecny projekt interfejsu, ale nie ma takiego wyraźnego oświadczenia OP. – rgettman

+0

@rgettman rzeczywisty typ dla 'T' ma mieć pewne metody, które nie są zdefiniowane w' HierarchicalEntity', więc kod jak 'getAncestors(). Map (ActualType :: foo) staje się nieważny. – Aliaxander

2

Jak powiedział @SotiriosDelimanolis, nie ma możliwości pełnego wymuszenia tego. Ale jeśli jesteś gotów przyjąć interfejs jest używany zgodnie z przeznaczeniem, można założyć, że this jest instancją T i po prostu rzucić go:

parentsBuilder.add((T)this); 

Jeśli chcesz uniknąć odlewania, można dodać metodę być przesłonięte w podklasie:

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    T getThis(); 
    default Stream<T> getAncestors() { 
     // ... 
     parentsBuilder.add(getThis()); 
     // ... 
    } 
} 

class Foo extends HierarchicalEntity<Foo> { 
    // ... 
    @Override 
    public Foo getThis() { 
     return this; 
    } 
} 

teraz możemy dostać this w sposób typesafe, ale nie ma gwarancji, że getThis() został wdrożony prawidłowo. Możliwe, że zwróci on dowolne wystąpienie Foo. Tak więc, wybieram twoją truciznę.

+0

Sposób z 'getThis()' wydaje się odpowiedni, ale nadal niezręczny ... – Aliaxander

+0

@Aliaxander Jeśli jest to opcja użycia wspólnej klasy abstrakcyjnej, możesz utworzyć konstruktor 'AbstractEntity (T thisObj)' i wywołać podklasy 'super (this) ', więc nie musisz implementować' getThis() 'na każdej podklasie. Nadal nie jest elegancko, ale nie wiem, czy istnieje eleganckie rozwiązanie. – shmosel

+1

Nie można użyć 'this' w jawnym wywołaniu konstruktora, zobacz http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.7.1 – Alex

Powiązane problemy