Esiste un'alternativa a instanceof durante il filtraggio di un flusso Java per classe?

8

Ho una situazione inaspettata in un progetto in cui tutti i tipi che estendono una classe vengono inseriti in una raccolta Java; ma solo un'estensione specifica di quella classe contiene un metodo aggiuntivo. Chiamiamolo "anche ()"; e vorrei essere chiaro che nessun'altra estensione ce l'ha. Subito prima di eseguire un'attività su ogni elemento di quella raccolta, devo chiamare anche () su ogni elemento che lo implementa.

La via più semplice è questa:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));
stuff.stream().forEach(item -> item.method());

Funziona bene, ma non mi sento a mio agio con l '"instanceof" lì dentro. Questo è generalmente un segno di cattivo odore di codice. È molto probabile che rifatterò questa classe solo per liberarmene. Prima di fare qualcosa di così drammatico, pensavo di controllare con la community e vedere se qualcuno con più esperienza con stream o collezioni aveva una soluzione più semplice.

Come esempio (certamente non esclusivo), è possibile ottenere una vista di una collezione che filtra le voci per classe?

    
posta Michael Eric Oberlin 29.11.2014 - 20:42
fonte

6 risposte

3

Suggerirei di lanciare una chiamata di .map per fare il cast per te. Quindi il tuo codice successivo può utilizzare la "cosa reale".

Prima:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));

Dopo:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .map(item -> (SpecificItem)item)
            .forEach(specificItem -> specificItem.also()));

Questo non è perfetto, ma sembra pulire un po 'le cose.

    
risposta data 15.11.2015 - 05:22
fonte
3

I have an unexpected situation in a project in which all types extending one class are packed into a Java collection; but only an extension of that class implements a method. Let's call it "also()". Right before performing one task on every item in that collection, I need to call also() on every item that implements it.

Questo è ovviamente un design difettoso. Da quello che hai scritto non è chiaro, cosa significa, che una classe non implementa il metodo. Se semplicemente nulla , non importa, quindi presumo che ci sia un effetto collaterale indesiderato.

It works fine, but I'm not comfortable with the "instanceof" in there.

Il tuo coraggio è giusto. Una buona progettazione orientata agli oggetti funzionerebbe senza ulteriore conoscenza di cosa sia esattamente un oggetto, sia che si tratti di un'istanza di un tipo speciale.

Before I do something that dramatic, though, I thought I would check with the community and see if someone with more experience with either Streams or Collections had a simpler solution.

Il refactoring non è drammatico. Migliora la qualità del codice.

Con la tua base di codice, la soluzione più semplice sarebbe, per essere sicuro, che hai due raccolte separate, una con il genitore e una con la classe figlia. Ma non è abbastanza pulito.

    
risposta data 29.11.2014 - 23:03
fonte
2

È difficile dare consigli specifici senza sapere quali sono effettivamente SpecificItem e also() , ma:

Definisci also() sulla superclasse di SpecificItem . Dagli un'implementazione predefinita che non fa nulla (dagli un corpo del metodo vuoto). Le sottoclassi individuali possono sovrascriverle se lo si desidera. A seconda di cosa sia effettivamente also , potresti dover rinominarlo in qualcosa che abbia senso per tutte le classi coinvolte.

    
risposta data 30.11.2014 - 20:48
fonte
1

un'alternativa:

  1. imposta SpecificItem implementa un'interfaccia (ad esempio "Filtrabile")
  2. crea una nuova classe che estende Stream
  3. crea un nuovo metodo 'filtro' che accetta oggetti che implementano la tua interfaccia
  4. sostituisci il metodo del flusso originale e reindirizzalo alla tua implementazione (convertendo il parametro predicato nella tua interfaccia)

in questo modo solo gli oggetti che implementano la tua interfaccia sarebbero in grado di trasmettere te stesso al tuo metodo ... non è necessario utilizzare instanceof

public class MyStream extends Stream
{
    //...

    Stream<Filterable> filter(Predicate<? implements Filterable> predicate)
    {
         return super.filter(predicate);
    }
}
    
risposta data 29.11.2014 - 22:35
fonte
1

Spero di non mancare nulla di ovvio qui (anche come suggerito da @ commento di Tristan Burnside ), ma perché non può SpecificItem.method() chiamare also() prima?

public class SpecificItem extends Item {
    ...
    public void method() {
        also();
        super.method();
    }
}

As an example (certainly nonexclusive), is it possible to get a view of a collection that filters entries by class?

Un modo stream-i che posso pensare, a scapito forse di un impatto sulle prestazioni (YMMV), è di collect() tramite Collectors.groupingBy() sulla classe come chiave, quindi scegli quello che vuoi dal% risultante codice%. I valori sono memorizzati come Map , quindi se ti aspettavi di fare un tale filtraggio su List e speri di ottenere un Set filtrato, allora avrai bisogno di un ulteriore passaggio per mettere ulteriormente valori in un Set risultante.

    
risposta data 03.04.2015 - 04:11
fonte
-1

Ecco il mio approccio:

stuff.stream().filter(item -> SpecificItem.class.isInstance(item)).map(item  -> SpecificItem.class.cast(item)).forEach(item -> item.also());
stuff.stream().forEach(item -> item.method());

No instanceof , nessun cast esplicito (in realtà è un cast esplicito, ma mascherato da una chiamata al metodo). Penso che questo sia buono come si può ottenere.

    
risposta data 03.04.2015 - 00:44
fonte

Leggi altre domande sui tag