2014-09-30 9 views
7

Avete già visto questo molte volte da soli, di che sono sicuro:Esiste un modo elegante per ottenere il primo valore non nullo di più metodi restituiti in Java?

public SomeObject findSomeObject(Arguments args) { 
    SomeObject so = queryFirstSource(args); // the most likely source first, hopefully 
    if (so != null) return so; 

    so = querySecondSource(args); // a source less likely than the first, hopefully 
    if (so != null) return so; 

    so = queryThirdSource(args); // a source less likely than the previous, hopefully 
    if (so != null) return so; 

    // and so on 
} 

Abbiamo diverse fonti in cui un oggetto che cerchiamo potrebbe essere. Come esempio più vivido possiamo immaginare che prima controlliamo se un userid è in una lista di utenti privilegiati. In caso contrario, controlliamo se l'ID utente è nell'elenco degli utenti consentiti. Altrimenti restituiamo null. (Non è l'esempio migliore, ma spero che sia uno vivido-sufficiente.)

Guava ci offre alcuni aiutanti che potrebbero abbellire il codice sopra:

public SomeObject findSomeObject(Arguments args) { 
    // if there are only two objects 
    return com.google.common.base.Objects.firstNonNull(queryFirstSource(args), querySecondSource(args)); 

    // or else 
    return com.google.common.collect.Iterables.find(
     Arrays.asList(
      queryFirstSource(args) 
      , querySecondSource(args) 
      , queryThirdSource(args) 
      // , ... 
     ) 
     , com.google.common.base.Predicates.notNull() 
    ); 
} 

Ma, come i più esperti tra noi avremo già visto, questo può funzionare male se le ricerche (es. queryXXXXSource(args)) richiedono un certo periodo di tempo. Questo perché ora interrogiamo prima tutte le fonti e poi passiamo i risultati al metodo che trova il primo tra quei risultati che non è null.

Contrariamente al primo esempio, in cui la sorgente successiva viene valutata solo quando la prima non restituisce qualcosa, questa seconda soluzione potrebbe sembrare migliore all'inizio, ma potrebbe comportare prestazioni molto peggiori.

Ecco dove arriviamo alla mia domanda reale e al punto in cui suggerisco qualcosa di quello spero che qualcuno abbia già implementato la base di esso o che qualcuno potrebbe proporre una soluzione addirittura intelligente.

In inglese semplice: Qualcuno ha già implementato tale defferedFirstNonNull (vedere di seguito) o qualcosa di simile? Esiste una soluzione Java semplice per ottenere questo risultato con il nuovo framework Stream? Puoi proporre un'altra soluzione elegante che ottenga lo stesso risultato?

Regole: Java 8 è consentito, così come attiva tenute e ben note librerie di terze parti come Guava di Google o di Apache Commons Lang con licenza Apache o simile (No GPL!).

La soluzione proposta:

public SomeObject findSomeObject(Arguments args) { 
    return Helper.deferredFirstNonNull(
     Arrays.asList(
      args -> queryFirstSource(args) 
      , args -> querySourceSource(args) 
      , args -> queryThirdSource(args) 
     ) 
     , x -> x != null 
    ) 
} 

Quindi il metodo defferedFirstNonNull sarebbe valutare ogni espressione lambda dopo l'altro e, non appena il predicato (x -> x != null) è vero (cioè abbiamo trovato un match) il metodo sarebbe tornato il risultato immediatamente e non richiederebbe ulteriori fonti.

PS: so che le espressioni args -> queryXXXXSource(args) potrebbero essere abbreviate in queryXXXXSource. Ma ciò renderebbe più difficile leggere la soluzione proposta perché non è ovvio a prima vista cosa accadrà.

risposta

7

Dipende da alcuni fattori che non si stanno definendo. Avete un set fisso, piuttosto piccolo, di azioni query…Source come mostrato nella vostra domanda o preferite piuttosto avere un elenco di azioni più flessibile ed estensibile?

Nel primo caso si potrebbe pensare di cambiare le query…Source metodi per restituire un Optional<SomeObject> piuttosto che SomeObject o null. Se si modificano le modalità di essere come

Optional<SomeObject> queryFirstSource(Arguments args) { 
    … 
} 

È possibile concatenare loro in questo modo:

public SomeObject findSomeObject(Arguments args) { 
    return queryFirstSource(args).orElseGet(
    ()->querySecondSource(args).orElseGet(
    ()->queryThirdSource(args).orElse(null))); 
} 

Se non è possibile modificarle o preferiscono loro di tornare null è ancora possibile utilizzare la classe Optional:

public SomeObject findSomeObject(Arguments args) { 
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
     ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
     ()->queryThirdSource(args))); 
} 

Se siete alla ricerca di un modo più flessibile per una più grande numero di possibili query, è inevitabile convertirle in qualche tipo di elenco o flusso di Function s. Una possibile soluzione è:

public SomeObject findSomeObject(Arguments args) { 
    return Stream.<Function<Arguments,SomeObject>>of(
     this::queryFirstSource, this::querySecondSource, this::queryThirdSource 
    ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null); 
} 

Questo esegue l'operazione desiderata, tuttavia, sarà comporre le azioni necessarie ogni volta che si richiama il metodo. Se si vuole invocare questo metodo più spesso, si può considerare la composizione di un operazione che si può riutilizzare:

Function<Arguments, SomeObject> find = Stream.<Function<Arguments,SomeObject>>of(
    this::queryFirstSource, this::querySecondSource, this::queryThirdSource 
).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a))); 

public SomeObject findSomeObject(Arguments args) { 
    return find.apply(args); 
} 

Quindi, vedete, ci sono più di un modo. E dipende dal compito reale quale direzione andare. A volte, anche la semplice sequenza if potrebbe essere appropriata.

+0

Ho scelto questa risposta come risposta designata in quanto combina tutte le altre risposte e sembra quindi 'completa'. Non conoscevo la classe 'Optional <>' e penso che fornisca il modo più elegante per il mio problema. – cimnine

10

sì, c'è:

Arrays.asList(source1, source2, ...) 
    .stream() 
    .filter(s -> s != null) 
    .findFirst(); 

Questo è più flessibile, dal momento che restituisce un Optional non null nel caso in cui un non-null source viene trovato.

Edit: Se si desidera che la valutazione pigra si dovrebbe usare un Supplier:

Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...) 
    .stream() 
    .filter(s -> s.get() != null) 
    .findFirst(); 
+0

Se si desidera creare un 'Stream' da varargs, si può anche chiamare' Stream.of (source1, source2, ...) 'invece di' Arrays.asList (source1, source2, ...) .stream() ' –

5

vorrei scrivere in questo modo (non potrebbe essere necessario farmaci generici qui, ma perché non farlo):

public static <A, T> Optional<T> findFirst(Predicate<T> predicate, A argument, 
             Function<A, T>... functions) { 
    return Arrays.stream(functions) 
      .map(f -> f.apply(argument)) 
      .filter(predicate::test) 
      .findFirst(); 
} 

E si può chiamare con:

return findFirst(Objects::nonNull, args, this::queryFirstSource, 
             this::querySecondSource, 
             this::queryThirdSource); 

(supponendo che il queryXXX incontrati hods sono metodi di istanza)

I metodi verranno applicati in ordine fino a quando non viene restituito un valore che corrisponde al predicato (nell'esempio sopra riportato: restituisce un valore non nullo).

Problemi correlati