Wiele testów zerowych w Javie 8

99

Mam poniższy kod, który jest trochę brzydki dla wielu sprawdzeń zerowych.

String s = null;

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

Spróbowałem więc użyć Optional.ofNullableponiższego, ale nadal trudno jest zrozumieć, czy ktoś czyta mój kod. jakie jest najlepsze podejście do tego w Javie 8.

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

W Javie 9 możemy używać Optional.ofNullablez OR, Ale czy w Javie8 jest jakieś inne podejście?

iskierka
źródło
4
orSkładnia Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);nie wygląda tak dobrze, jak Stream.ofja bym sya.
Naman
2
@ OleV.V. Nic złego, OP już o tym wie i szuka czegoś specyficznego dla Java-8.
Naman
3
Wiem, że użytkownik prosi o rozwiązanie specyficzne dla Java-8, ale ogólnie rzecz biorąc, StringUtils.firstNonBlank()
wybrałbym
3
Problem w tym, że Java 8 / strumienie nie są najlepszym rozwiązaniem. Ten kod naprawdę pachnie jak refaktor w porządku, ale bez większego kontekstu naprawdę trudno to stwierdzić. Na początek - dlaczego trzy obiekty, które są prawdopodobnie tak blisko spokrewnione, nie znajdują się jeszcze w kolekcji?
Bill K
2
@MohamedAneesA udzieliłby najlepszej odpowiedzi (jako komentarz), ale w tym przypadku nie określił źródła StringUtils. W każdym razie, jeśli MUSISZ mieć je jako kilka oddzielnych ciągów, kodowanie ich jako metody vargs, takiej jak „firstNonBlank”, jest idealne, składnia wewnątrz będzie tablicą tworzącą prostą pętlę for-each ze zwrotem po znalezieniu wartość niezerowa trywialna i oczywista. W tym przypadku strumienie Java 8 są atrakcyjną uciążliwością. kuszą cię do wstawiania i komplikowania czegoś, co powinno być prostą metodą / pętlą.
Bill K

Odpowiedzi:

173

Możesz to zrobić tak:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Ravindra Ranwala
źródło
16
To. Myśl o tym, czego potrzebujesz, a nie o tym, co masz.
Thorbjørn Ravn Andersen
21
Ile kosztuje prędkość? Tworzenie obiektów Stream, wywoływanie 4 metod, tworzenie tymczasowych tablic ( {str1, str2, str3}) wygląda na znacznie wolniejsze niż lokalne iflub ?:, które środowisko wykonawcze Java może zoptymalizować. Czy jest jakaś optymalizacja specyficzna dla Stream javaci środowisko wykonawcze Java, które sprawia, że ​​jest tak szybki ?:? Jeśli nie, nie polecę tego rozwiązania w kodzie krytycznym dla wydajności.
pkt
16
@pts najprawdopodobniej będzie to wolniejsze niż ?:kod i jestem pewien, że należy tego unikać w kodzie krytycznym dla wydajności. Jest jednak znacznie bardziej czytelny i powinieneś go polecać w IMO z kodem niekrytycznym dla wydajności, który z pewnością stanowi ponad 99% kodu.
Aaron,
7
@pts Nie ma żadnych optymalizacji specyficznych dla Stream, ani w, javacani w środowisku wykonawczym. Nie wyklucza to ogólnych optymalizacji, takich jak wstawianie całego kodu, a następnie eliminowanie zbędnych operacji. Zasadniczo wynik końcowy może być tak samo wydajny, jak zwykłe wyrażenia warunkowe, jednak jest raczej mało prawdopodobne, aby kiedykolwiek się tam znalazł, ponieważ środowisko wykonawcze poświęciłoby niezbędny wysiłek tylko na najgorętsze ścieżki kodu.
Holger,
22
Świetne rozwiązanie! Aby nieco zwiększyć czytelność sugerowałbym dodanie str4do parametrów Stream.of(...)i wykorzystanie orElse(null)na końcu.
danielp,
73

A co z potrójnym operatorem warunkowym?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Eran
źródło
50
w rzeczywistości szuka „najlepszego podejścia do tego w Javie8”. Takie podejście można zastosować w Javie8, więc wszystko zależy tylko od tego, co OP rozumie przez „najlepszy” i na jakiej podstawie podejmuje decyzję o tym, co jest lepsze.
Stultuske
17
Zwykle nie lubię zagnieżdżonych ternariów, ale wygląda to dość czysto.
JollyJoker,
11
To ogromny problem dla każdego, kto nie czyta codziennie zagnieżdżonych operatorów trójskładnikowych.
Cubic
2
@Cubic: Jeśli uważasz, że trudno jest przeanalizować, napisz komentarz, taki jak // take first non-null of str1..3. Kiedy już wiesz, co robi, łatwo jest zobaczyć, jak to zrobić.
Peter Cordes
4
@Cubic Jednak jest to prosty powtarzający się wzór. Po przeanalizowaniu wiersza 2 przeanalizowałeś całą rzecz, bez względu na to, ile przypadków jest uwzględnionych. A kiedy skończysz, nauczyłeś się wyrażać podobny wybór dziesięciu różnych przypadków w zwięzły i stosunkowo prosty sposób. Następnym razem, gdy zobaczysz ?:drabinę, będziesz wiedział, co robi. (Btw, programiści funkcjonalne wiedzą o tym jak (cond ...)klauzul. To zawsze straż szła przez odpowiednią wartość do użycia, gdy osłona jest prawdą)
cmaster - dozbrojenie Monica
35

Możesz także użyć pętli:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}
ernest_k
źródło
27

Aktualne odpowiedzi są fajne, ale naprawdę powinieneś to umieścić w metodzie narzędziowej:

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

Ta metoda jest w mojej Utilklasie od lat, dzięki czemu kod jest znacznie czystszy:

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

Możesz nawet uczynić go ogólnym:

@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);
walen
źródło
10
FWIW, w SQL ta funkcja nazywa się coalesce , więc nazywam to również w moim kodzie. To, czy to zadziała, zależy od tego, jak bardzo lubisz SQL.
Tom Anderson,
5
Jeśli zamierzasz zastosować metodę użytkową, równie dobrze możesz uczynić ją wydajną.
Todd Sewell
1
@ToddSewell, co masz na myśli?
Gustavo Silva
5
@GustavoSilva Todd prawdopodobnie oznacza, że ​​ponieważ jest to moja metoda użytkowa, nie ma sensu używać, Arrays.stream()kiedy mogę zrobić to samo z a fori a != null, co jest bardziej wydajne. I Todd miałby rację. Jednak kiedy kodowałem tę metodę, szukałem sposobu na zrobienie tego za pomocą funkcji Java 8, tak jak OP, więc jest to.
walen
4
@GustavoSilva Tak, to w zasadzie to: jeśli zamierzasz umieścić tę funkcję, obawy dotyczące czystego kodu nie są już tak ważne, więc równie dobrze możesz użyć szybszej wersji.
Todd Sewell
13

Używam funkcji pomocniczej, coś w stylu

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

Następnie ten rodzaj kodu można zapisać jako

String s = firstNonNull(str1, str2, str3, str4);
Michael Anderson
źródło
1
Dlaczego dodatkowy v0parametr?
tobias_k
1
@tobias_k Dodatkowym parametrem podczas korzystania z varargs jest idiomatyczny sposób w Javie, który wymaga 1 lub więcej argumentów zamiast 0 lub więcej. (Patrz punkt 53 Effective Java, wyd. 3, który używa minjako przykładu). Jestem mniej przekonany, że jest to właściwe w tym miejscu.
Nick
3
@Nick Tak, zgadłem, ale ta funkcja działałaby równie dobrze (w rzeczywistości zachowywałaby się dokładnie tak samo) bez niej.
tobias_k
1
Jednym z głównych powodów przekazania dodatkowego pierwszego argumentu jest wyraźne określenie zachowania po przekazaniu tablicy. W tym przypadku chcę firstNonNull(arr)zwrócić arr, jeśli nie jest zerowy. Gdyby tak było firstNonNull(T... vs), zamiast tego zwróciłoby pierwszy niezerowy wpis w arr.
Michael Anderson
4

Rozwiązaniem, które można zastosować do dowolnej liczby elementów, może być:

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

Można sobie wyobrazić rozwiązanie jak poniżej, ale to pierwsze zapewnia non nullitywszystkie elementy

Stream.of(str1, str2, str3).....orElse(str4)
azro
źródło
6
orElse, str4mimo że jest to właściwie zero
Naman
1
@nullpointer Or orElseNull, co oznacza to samo, jeśli str4jest null.
tobias_k
3

Możesz również wrzucić wszystkie Strings do tablicy String, a następnie wykonać pętlę for, aby sprawdzić i przerwać pętlę po jej przypisaniu. Zakładając, że wszystkie s1, s2, s3, s4 są ciągami.

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


s = s4;

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

Edytowane w celu wprowadzenia warunku domyślnego na s4, jeśli żaden nie jest przypisany.

danielctw
źródło
Pominąłeś s4(lub str4), do którego ma zostać sostatecznie przypisany , nawet jeśli jest pusty.
displayName
3

Oparta na metodzie i prosta.

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

I użyj go jako:

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

Jest to proste w przypadku tablic i wygląda ładnie.

Madhusoodan P
źródło
0

Jeśli korzystasz z Apache Commons Lang 3, możesz to zapisać w ten sposób:

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

Wykorzystanie ObjectUtils.firstNonNull(T...)zostało zaczerpnięte z tej odpowiedzi . W pokrewnym pytaniu przedstawiono również różne podejścia .

Łczapski
źródło