Ho una situazione interessante e mi chiedo se c'è un modo migliore per farlo. La situazione è questa, ho una struttura ad albero (un albero sintattico astratto, in particolare) e alcuni nodi possono contenere nodi figlio di vario tipo ma tutti estesi da una data classe base.Tipo sicurezza, generics Java e query
Desidero eseguire frequentemente query su questo albero e vorrei recuperare i sottotipi specifici a cui sono interessato. Ho quindi creato una classe di predicati che è quindi possibile passare in un metodo di query generico. In un primo momento ho avuto un metodo di query che si presentava così:
public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c);
dove l'argomento Class
è stato utilizzato solo per indicare il tipo di ritorno. Ciò che mi ha infastidito su questo approccio è che tutti i miei predicati erano già per tipi specifici, quindi qui sono disponibili informazioni ridondanti. Una chiamata tipica potrebbe essere simile:
List<Declaration> decls =
scope.findAll(new DeclarationPredicate(), Declaration.class);
Così ho riscritta così:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred);
Dove l'interfaccia IElementPredicate
si presenta così:
public interface IElementPredicate<T extends Element> {
public boolean match(T e);
public String getDescription();
public Class<T> getGenericClass();
}
Il punto qui è che il predicato l'interfaccia viene espansa per fornire invece l'oggetto Class
. Rende la scrittura del metodo effettivo findAll
un po 'più di lavoro e aggiunge un po' più di lavoro nella scrittura del predicato, ma sono entrambe cose "di una volta" essenzialmente minuscole e rende la query chiamata molto più bella perché non aggiungere l'argomento extra (potenzialmente ridondante), ad es
List<Declaration> decls = scope.findAll(new DeclarationPredicate());
Non ho notato questo modello prima. È questo un modo tipico di affrontare la semantica dei generici di Java? Solo curioso di sapere se mi manca qualche schema migliore.
Commmenti?
UPDATE:
Una domanda era che cosa avete bisogno della Classe per? Ecco l'implementazione di findAll:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred) {
List<T> ret = new LinkedList<T>();
Class<T> c = pred.getGenericClass();
for(Element e: elements) {
if (!c.isInstance(e)) continue;
T obj = c.cast(e);
if (pred.match(obj)) {
ret.add(c.cast(e));
}
}
return ret;
}
Se è vero che incontro vuole solo un T, ho bisogno di assicurarsi che l'oggetto è una T prima che io possa chiamare. Per questo, ho bisogno dei metodi "isInstance" e "cast" di Class (per quanto posso dire).
Uso spesso i visitatori (in questo stesso progetto, di fatto) ma in questo caso particolare, il modello di visitatore aggiungerebbe molto più codice e complessità di quanto ritenga necessario. –
Sono d'accordo con te, è una specie di complicatore che cambia nel modo in cui ha grandi effetti collaterali. È difficile invertire il generalismo come vuoi tu. Credo che la tua soluzione sia la migliore, perché senza un visitatore credo che avresti bisogno di un binding tardivo o di un controllo esplicito del tipo (nel modo in cui equals() funziona). La tua soluzione è migliore perché centralizza il controllo dei tipi invece di attribuire quel peso agli implementatori. Vedi questo problema anche nelle generiche implementazioni Comparabili. –
E con l'associazione tardiva intendevo che Java doveva implementare una risoluzione del metodo sovraccaricata basata sul tipo di oggetto, non sul tipo di riferimento. Questo è fondamentalmente ciò che hai implementato nel tuo codice. –