2016-05-06 51 views
6

devo progettare un'interfaccia per l'entità gerarchica:interfaccia Progettare per un'entità gerarchica

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

E 'abbastanza facile da implementare predefinita metodogetAncestors() in termini di getParent() in modo tale che il primo sarebbe tornato Stream di tutti gli antenati

esempio di implementazione:

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

Ma ho bisogno di includere anche this nel flusso, e qui appare un problema. La seguente riga non è corretto perché this è di tipo HierarchicalEntity, non T:

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

Come posso ridisegnare l'interfaccia in modo da rendere getAncestors() includono this nel risultato?

+5

Sfortunatamente questo non sarà mai completamente sicuro. Java non ha una sintassi di tipo autoreferenziale. Posso creare una 'classe Fake implementa HierarchicalEntity ' e il tuo 'getAncestors' probabilmente fallirà con un' ClassCastException' alla fine. –

+0

Ottenuto il punto. Forse qualche riprogettazione potrebbe essere applicata all'intera interfaccia. – Aliaxander

+0

Basta lanciare 'this' a' T' e aggiungerlo al generatore di stream –

risposta

1

È un problema ricorrente durante la creazione di tipi autoreferenziali. Nel tipo di base (o nell'interfaccia), non è possibile imporre che lo this sia compatibile con lo T.

Ovviamente, è possibile eseguire un cast non controllato di this a T se si è sicuri che tutti i sottotipi soddisfino tale vincolo. Ma devi eseguire questo cast non selezionato ogni volta che hai bisogno di un riferimento this come T.

La soluzione migliore è quella di aggiungere un metodo astratto come

/** 
    All subtypes should implement this as: 

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

Quindi, è possibile utilizzare al posto di myself()this ogni volta che avete bisogno di un auto-riferimento 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(); 
} 

Naturalmente, non si può far valere che sottoclassi implementano correttamente myself() come return this;, ma almeno, si può facilmente verificare se lo fanno in fase di esecuzione:

assert this == myself(); 

Questo confronto di riferimento è molto operazione economica e, se myself() è correttamente implementato come invariabilmente restituendo this, HotSpot può dimostrare in anticipo che questo confronto sarà sempre true ed elidere completamente il controllo.

Lo svantaggio è che ciascuna specializzazione dovrà avere questa implementazione ridondante di myself() { return this; }, ma d'altra parte, è completamente priva di cast di tipi non controllati. L'alternativa è di avere una dichiarazione non abstract di myself() nella classe base come @SuppressWarnings("unchecked") T myself() { return (T)this; } per limitare l'operazione deselezionata a un singolo posto per la gerarchia di tipi. Ma poi, non è possibile verificare se this sia realmente di tipo T ...

1

L'aggiunta di this non riesce perché un HierarchicalEntity<T> non è necessariamente un T; potrebbe essere un sottotipo sconosciuto. Tuttavia, uno T è sempre un HierarchicalEntity<T> come lo hai dichiarato in questo modo.

cambiare il tipo di ritorno di getAncestors e del Stream.BuilderT-HierarchicalEntity<T>, che vi permetterà di aggiungere 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(); 
} 

Si potrebbe desiderare di dichiarare getParent per restituire un HierarchicalEntity<T> anche per coerenza.

+0

Ma OP vuole un flusso di 'T'. – shmosel

+0

@shmosel Questo è l'attuale design dell'interfaccia, ma l'OP non ha una dichiarazione così esplicita. – rgettman

+0

@rgettman tipo effettivo per 'T' si suppone che abbia alcuni metodi che non sono definiti in' HierarchicalEntity', quindi il codice come 'getAncestors(). Map (ActualType :: foo)' non è più valido. – Aliaxander

2

Come ha detto @SotiriosDelimanolis, non c'è modo di applicarlo completamente. Ma se siete disposti ad assumere l'interfaccia viene utilizzato come progettato, si può supporre che this è un'istanza di T e semplicemente lanci:

parentsBuilder.add((T)this); 

Se si vuole evitare la fusione, si può aggiungere un metodo per essere sottoposto a override in sottoclasse:

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

Ora possiamo ottenere this in maniera typesafe, ma non c'è alcuna garanzia che getThis() è stato implementato correttamente. È possibile che restituisca qualsiasi istanza di Foo. Quindi, scegli il tuo veleno, immagino.

+0

Il modo con 'getThis()' sembra adeguato ma ancora scomodo ... – Aliaxander

+0

@Aliaxander Se è un'opzione per usare una classe astratta comune, puoi creare un costruttore 'AbstractEntity (T thisObj)' e far chiamare le sottoclassi 'super (questo) 'quindi non devi implementare' getThis() 'in ogni sottoclasse. Ancora non elegante, ma non so se c'è una soluzione elegante. – shmosel

+1

Non è possibile utilizzare 'this' in una chiamata al costruttore esplicita, consultare http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.7.1 – Alex