Più controlli null in Java 8


99

Ho il codice seguente che è un po 'brutto per più controlli nulli.

String s = null;

if (str1 != null) {
    s = str1;
} else if (str2 != null) {
    s = str2;
} else if (str3 != null) {
    s = str3;
} else {
    s = str4;
}

Quindi ho provato a usare Optional.ofNullablecome di seguito, ma è ancora difficile capire se qualcuno legge il mio codice. qual è l'approccio migliore per farlo in Java 8.

String s = Optional.ofNullable(str1)
                   .orElse(Optional.ofNullable(str2)
                                   .orElse(Optional.ofNullable(str3)
                                                   .orElse(str4)));

In Java 9, possiamo usare Optional.ofNullablecon OR, Ma in Java8 c'è qualche altro approccio?


4
La orsintassi Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);non sembra buona come la Stream.ofsya.
Naman

2
@ OleV.V. Niente di sbagliato, l'OP lo sa già e sta cercando qualcosa di specifico per Java-8.
Naman

3
So che l'utente sta chiedendo una soluzione specifica per Java-8, ma in generale, vorrei andare conStringUtils.firstNonBlank()
Mohamed Anees A

3
Il problema è che Java 8 / stream non è la soluzione migliore per questo. Quel codice sa davvero che un refactor è in ordine, ma senza più contesto è davvero difficile da dire. Per cominciare, perché tre oggetti che sono probabilmente così strettamente correlati non sono già in una collezione?
Bill K

2
@MohamedAneesA avrebbe fornito la risposta migliore (come commento) ma in questo caso non ha specificato l'origine di StringUtils. In ogni caso, se DEVI avere questi come un gruppo di stringhe separate, codificarlo come un metodo vargs come "firstNonBlank" è l'ideale, la sintassi all'interno sarà un array che crea un semplice ciclo for-each con un ritorno alla ricerca di un valore non nullo banale e scontato. In questo caso, i flussi Java 8 sono un fastidio attraente. ti tentano a inline e complicare qualcosa che dovrebbe essere un semplice metodo / ciclo.
Bill K

Risposte:


172

Puoi farlo in questo modo:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);

16
Questo. Pensa a ciò di cui hai bisogno, non a ciò che hai.
Thorbjørn Ravn Andersen

21
Quanto costa la velocità in testa? La creazione di oggetti Stream, la chiamata di 4 metodi, la creazione di array temporanei ( {str1, str2, str3}) sembrano molto più lenti di un ifo locale ?:, che il runtime Java può ottimizzare. C'è qualche ottimizzazione specifica per Stream javace nel runtime Java che lo rende veloce come ?:? In caso contrario, non consiglierò questa soluzione nel codice critico per le prestazioni.
punti

16
@pts è molto probabile che sia più lento del ?:codice e sono sicuro che dovresti evitarlo nel codice critico per le prestazioni. È tuttavia molto più leggibile e dovresti consigliarlo in un codice IMO non critico per le prestazioni, che sono sicuro fa più del 99% del codice.
Aaron

7
@pts Non ci sono ottimizzazioni specifiche del flusso, né in javacné nel runtime. Ciò non preclude le ottimizzazioni generali, come l'integrazione di tutto il codice, seguita dall'eliminazione delle operazioni ridondanti. In linea di principio, il risultato finale potrebbe essere efficiente quanto le semplici espressioni condizionali, tuttavia, è piuttosto improbabile che ci arrivi, poiché il runtime spenderebbe lo sforzo necessario solo sui percorsi di codice più caldi.
Holger

22
Ottima soluzione! Per aumentare un po 'la leggibilità suggerirei di aggiungere str4ai parametri di Stream.of(...)e utilizzare orElse(null)alla fine.
danielp

73

Che ne dici dell'operatore condizionale ternario?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;

50
in realtà, sta cercando "l'approccio migliore per farlo in Java8". Questo approccio può essere utilizzato in Java8, quindi tutto dipende da ciò che l'OP intende per "il migliore" e su quali basi basa la decisione su cosa è meglio.
Stultuske

17
Di solito non mi piacciono i ternari annidati, ma questo sembra abbastanza pulito.
JollyJoker

11
Questo è un enorme problema da analizzare per chiunque non legga operatori ternari annidati ogni giorno.
cubo

2
@ Cubic: se pensi che sia difficile da analizzare, scrivi un commento come // take first non-null of str1..3. Una volta che sai cosa fa, diventa facile vedere come.
Peter Cordes,

4
@Cubic Eppure, questo è un semplice schema ripetuto. Dopo aver analizzato la riga 2, hai analizzato l'intera cosa, indipendentemente dal numero di casi inclusi. E una volta che hai finito, hai imparato a esprimere una selezione simile di dieci casi diversi in modo breve e relativamente semplice. La prossima volta che vedrai una ?:scala, saprai cosa fa. (A proposito, i programmatori funzionali lo sanno come (cond ...)clausole: è sempre una guardia seguita dal valore appropriato da usare quando la guardia è vera.)
cmaster - reintegrare monica

35

Puoi anche usare un ciclo:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}

27

Le risposte attuali sono carine ma dovresti davvero metterle in un metodo di utilità:

public static Optional<String> firstNonNull(String... strings) {
    return Arrays.stream(strings)
            .filter(Objects::nonNull)
            .findFirst();
}

Questo metodo è nella mia Utilclasse da anni, rende il codice molto più pulito:

String s = firstNonNull(str1, str2, str3).orElse(str4);

Puoi anche renderlo generico:

@SafeVarargs
public static <T> Optional<T> firstNonNull(T... objects) {
    return Arrays.stream(objects)
            .filter(Objects::nonNull)
            .findFirst();
}

// Use
Student student = firstNonNull(student1, student2, student3).orElseGet(Student::new);

10
FWIW, in SQL, questa funzione è chiamata coalesce , quindi la chiamo anche nel mio codice. Se funziona per te dipende da quanto ti piace SQL, davvero.
Tom Anderson

5
Se hai intenzione di metterlo in un metodo di utilità, potresti anche renderlo efficiente.
Todd Sewell

1
@ToddSewell, cosa intendi?
Gustavo Silva

5
@GustavoSilva Todd probabilmente significa che, essendo questo un mio metodo di utilità, non ha senso usarlo Arrays.stream()quando posso fare lo stesso con a fore a != null, che è più efficiente. E Todd avrebbe ragione. Tuttavia, quando ho codificato questo metodo stavo cercando un modo per farlo utilizzando le funzionalità di Java 8, proprio come OP, quindi c'è quello.
Galles

4
@GustavoSilva Sì, fondamentalmente è così: se hai intenzione di inserire questa è una funzione di utilità, le preoccupazioni sul codice pulito non sono più così importanti, quindi potresti anche usare una versione più veloce.
Todd Sewell,

13

Uso una funzione di supporto, qualcosa di simile

T firstNonNull<T>(T v0, T... vs) {
  if(v0 != null)
    return v0;
  for(T x : vs) {
    if (x != null) 
      return x;
  }
  return null;
}

Quindi questo tipo di codice può essere scritto come

String s = firstNonNull(str1, str2, str3, str4);

1
Perché il v0parametro extra ?
tobias_k

1
@tobias_k Il parametro extra quando si usa varargs è il modo idiomatico in Java di richiedere 1 o più argomenti invece di 0 o più. (Vedi l'articolo 53 di Effective Java, Ed. 3., che usa mincome esempio). Sono meno convinto che questo sia appropriato qui.
Nick

3
@ Nick Sì, lo immaginavo, ma la funzione funzionerebbe altrettanto bene (infatti si comporterebbe esattamente allo stesso modo) senza di essa.
tobias_k

1
Uno dei motivi principali per passare un primo argomento aggiuntivo è essere espliciti sul comportamento quando viene passato un array. In questo caso, voglio firstNonNull(arr)restituire arr se non è nullo. Se lo fosse firstNonNull(T... vs), restituirebbe invece la prima voce non nulla in arr.
Michael Anderson

4

Una soluzione che può essere applicata a tutti gli elementi che vuoi può essere:

Stream.of(str1, str2, str3, str4)
      .filter(Object::nonNull)
      .findFirst()
      .orElseThrow(IllegalArgumentException::new)

Potresti immaginare una soluzione come sotto, ma la prima garantisce non nullityper tutti gli elementi

Stream.of(str1, str2, str3).....orElse(str4)

6
Oppure, str4nonostante sia un valore nullo
Naman

1
@nullpointer Or orElseNull, che equivale allo stesso se str4è null.
tobias_k

3

Puoi anche raggruppare tutte le stringhe in un array di String, quindi eseguire un ciclo for per controllare e interrompere il ciclo una volta assegnato. Supponendo che s1, s2, s3, s4 siano tutte stringhe.

String[] arrayOfStrings = {s1, s2, s3};


s = s4;

for (String value : arrayOfStrings) {
    if (value != null) { 
        s = value;
        break;
    }
}

Modificato per lanciare la condizione per l'impostazione predefinita su s4 se nessuno è assegnato.


Hai omesso s4(o str4), che dovrebbe essere assegnato alla sfine, anche se è nullo.
displayName

3

Basato sul metodo e semplice.

String getNonNull(String def, String ...strings) {
    for(int i=0; i<strings.length; i++)
        if(strings[i] != null)
             return s[i];
    return def;
}

E usalo come:

String s = getNonNull(str4, str1, str2, str3);

È semplice da fare con gli array e sembra carino.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.