Jak uzyskać pierwszą wartość niezerową w Javie?


153

Czy istnieje odpowiednik funkcji SQL w języku Java COALESCE? To znaczy, czy istnieje sposób na zwrócenie pierwszej niezerowej wartości kilku zmiennych?

na przykład

Double a = null;
Double b = 4.4;
Double c = null;

Chcę jakoś mieć oświadczenie, że wróci pierwszą wartość niezerową z a, bi c- w tym przypadku, to wróci b, lub 4,4. (Coś w rodzaju metody sql - powrót COALESCE(a,b,c)). Wiem, że mogę to zrobić wprost za pomocą czegoś takiego:

return a != null ? a : (b != null ? b : c)

Ale zastanawiałem się, czy jest jakaś wbudowana, akceptowana funkcja, która to umożliwia.


3
Nie powinieneś potrzebować takiej funkcji, ponieważ generalnie nie obliczysz „c”, jeśli „b” ma odpowiedź, której chcesz. tj. nie utworzyłbyś listy możliwych odpowiedzi tylko po to, by zachować jedną.
Peter Lawrey

Ostrzeżenie: Nie wszystkie zwarcia RDBMS na COALESCE. Oracle dopiero niedawno zaczął to robić.
Adam Gent

3
@ BrainSlugs83 Poważnie? Java powinna?
Dmitry Ginzburg

Odpowiedzi:


108

Nie, nie ma.

Najbliższe możesz uzyskać:

public static <T> T coalesce(T ...items) {
    for(T i : items) if(i != null) return i;
    return null;
}

Ze względów praktycznych możesz obsługiwać typowe przypadki w następujący sposób:

public static <T> T coalesce(T a, T b) {
    return a == null ? b : a;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : (b != null ? b : c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return ...
}

3
Powody wydajnościowe, o których wspomniałem powyżej, są takie, że alokacja tablicy nastąpi za każdym razem, gdy wywołasz wersję metody var arg. mogłoby to być marnotrawstwem dla pełnych rąk przedmiotów, co podejrzewam, że będzie to powszechne użycie.
les2

Chłodny. Dzięki. W takim przypadku prawdopodobnie będę trzymał się zagnieżdżonych operatorów warunkowych w tym przypadku, ponieważ jest to jedyny raz, kiedy trzeba go użyć, a metoda zdefiniowana przez użytkownika byłaby przesada ...
froadie

8
Nadal bym go wyciągnął do prywatnej metody pomocniczej, zamiast zostawić w kodzie „przerażająco wyglądający” blok warunkowy - „co to robi?” w ten sposób, jeśli kiedykolwiek będziesz musiał użyć go ponownie, możesz użyć narzędzi refaktoryzujących w swoim IDE, aby przenieść metodę do klasy narzędziowej. posiadanie nazwanej metody pomaga udokumentować intencję kodu, co jest zawsze dobrą rzeczą, IMO. (a narzut wersji innej niż var-args jest prawdopodobnie ledwo mierzalny.)
les2

10
Uważaj: Na coalesce(a, b)razie bto kompleks ekspresja i anie jest null, bjest jeszcze oceniany. Tak nie jest w przypadku operatora warunkowego?:. Zobacz tę odpowiedź .
Pang

wymaga to wstępnego obliczenia każdego argumentu przed wezwaniem do połączenia, bezcelowego ze względu na wydajność
Ivan G.


59

Jeśli są tylko dwie zmienne do sprawdzenia i używasz Guava, możesz użyć MoreObjects.firstNonNull (T pierwsza, T druga) .


49
Objects.firstNonNull przyjmuje tylko dwa argumenty; nie ma odpowiednika varargs w guawie. Ponadto zgłasza wyjątek NullPointerException, jeśli oba argumenty mają wartość null - może to być pożądane lub nie.

2
Dobry komentarz, Jake. Ten wyjątek NullPointerException często ogranicza użycie Objects.firstNonNull. Jednak podejście Guava polega na całkowitym unikaniu zer.
Anton Shchastnyi

4
Metoda ta jest obecnie przestarzała, a zalecaną alternatywą jest MoreObjects.firstNonNull
davidwebster48

1
Jeśli NPE jest niepożądany, zobacz tę odpowiedź
OrangeDog

51

Jeśli do przetestowania są tylko dwa odwołania i używasz języka Java 8, możesz użyć

Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

Jeśli importujesz static Optional, wyrażenie nie jest takie złe.

Niestety Twój przypadek z „kilkoma zmiennymi” nie jest możliwy przy użyciu metody opcjonalnej. Zamiast tego możesz użyć:

Object o = null;
Object p = null;
Object q = "p";

Optional<Object> r = Stream.of( o, p, q ).filter( Objects::nonNull ).findFirst();
System.out.println( r.orElse(null) );   // p

23

Kontynuując odpowiedź LES2, możesz wyeliminować pewne powtórzenia w wydajnej wersji, wywołując przeciążoną funkcję:

public static <T> T coalesce(T a, T b) {
    return a != null ? a : b;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : coalesce(b,c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return a != null ? a : coalesce(b,c,d);
}
public static <T> T coalesce(T a, T b, T c, T d, T e) {
    return a != null ? a : coalesce(b,c,d,e);
}

5
+1 za ładne. Nie jestem pewien co do korzyści w zakresie wydajności w porównaniu z prostą pętlą, ale jeśli zamierzasz w ten sposób zwiększyć wydajność, równie dobrze może być ładnie.
Carl Manaster

3
w ten sposób pisanie przeciążonych wariantów jest znacznie mniej bolesne i mniej podatne na błędy!
les2

2
Celem wydajnej wersji było to, aby nie marnować pamięci przydzielając tablicę przy użyciu varargs. Tutaj marnujesz pamięć, tworząc ramkę stosu dla każdego zagnieżdżonego coalesce()wywołania. Wywołanie coalesce(a, b, c, d, e)tworzy do 3 ramek stosu do obliczenia.
Łukasz

10

Ta sytuacja wymaga pewnego preprocesora. Ponieważ jeśli napiszesz funkcję (metodę statyczną), która wybiera pierwszą niezerową wartość, ocenia ona wszystkie elementy. Jest to problem, jeśli niektóre elementy są wywołaniami metod (mogą być czasochłonne wywołania metod). Te metody są wywoływane, nawet jeśli jakikolwiek element przed nimi nie jest pusty.

Niektóre działają w ten sposób

public static <T> T coalesce(T ...items) 

powinno być używane, ale przed skompilowaniem do kodu bajtowego powinien istnieć preprocesor, który znajdzie zastosowania tej „funkcji koalescencji” i zastąpi ją konstrukcją taką jak

a != null ? a : (b != null ? b : c)

Aktualizacja 2014-09-02:

Dzięki Java 8 i Lambdas istnieje możliwość prawdziwej koalescencji w Javie! W tym kluczowa cecha: poszczególne wyrażenia są oceniane tylko wtedy, gdy są potrzebne - jeśli poprzednie nie jest puste, to następne nie są oceniane (nie są wywoływane metody, nie są wykonywane obliczenia ani operacje na dysku / sieci).

Napisałem o tym artykuł Java 8: coalesce - hledáme neNULLové hodnoty - (napisany po czesku, ale mam nadzieję, że przykłady kodu są zrozumiałe dla każdego).


1
Fajny artykuł - ale fajnie byłoby mieć go po angielsku.
Quantum

1
Na tej stronie bloga jest coś, co nie działa z Tłumaczem Google. :-(
HairOfTheDog

5

Z guawą możesz:

Optional.fromNullable(a).or(b);

który nie zgłasza NPE, jeśli oba ai bnull.

EDYCJA: Myliłem się, wyrzuca NPE. Prawidłowy sposób, jak skomentował Michał Čizmazia, to:

Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull();

1
Hej, tak:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michał Čizmazia

1
To Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
załatwia sprawę

4

Dla zupełności, przypadek „kilku zmiennych” jest rzeczywiście możliwy, chociaż wcale nie jest elegancki. Na przykład, dla zmiennych o, poraz q:

Optional.ofNullable( o ).orElseGet(()-> Optional.ofNullable( p ).orElseGet(()-> q ) )

Proszę zwrócić uwagę na zastosowanie obsługi orElseGet()sprawy, że o, pi qnie są zmiennymi, ale wyrażeniami albo drogimi, albo z niepożądanymi skutkami ubocznymi.

W najbardziej ogólnym przypadku coalesce(e[1],e[2],e[3],...,e[N])

coalesce-expression(i) ==  e[i]  when i = N
coalesce-expression(i) ==  Optional.ofNullable( e[i] ).orElseGet(()-> coalesce-expression(i+1) )  when i < N

Może to generować zbyt długie wyrażenia. Jeśli jednak próbujemy przenieść się do świata bez null, v[i]to najprawdopodobniej już jesteśmy typu Optional<String>, a nie po prostu String. W tym przypadku,

result= o.orElse(p.orElse(q.get())) ;

lub w przypadku wyrażeń:

result= o.orElseGet(()-> p.orElseGet(()-> q.get() ) ) ;

Ponadto, jeśli są również przeprowadzki do stylu funkcjonalnym-deklaratywny, o, p, i qpowinny być typu Supplier<String>jak w:

Supplier<String> q= ()-> q-expr ;
Supplier<String> p= ()-> Optional.ofNullable(p-expr).orElseGet( q ) ;
Supplier<String> o= ()-> Optional.ofNullable(o-expr).orElseGet( p ) ;

A potem całość coalescesprowadza się po prostu do o.get().

Bardziej konkretny przykład:

Supplier<Integer> hardcodedDefaultAge= ()-> 99 ;
Supplier<Integer> defaultAge= ()-> defaultAgeFromDatabase().orElseGet( hardcodedDefaultAge ) ;
Supplier<Integer> ageInStore= ()-> ageFromDatabase(memberId).orElseGet( defaultAge ) ;
Supplier<Integer> effectiveAge= ()-> ageFromInput().orElseGet( ageInStore ) ;

defaultAgeFromDatabase(), ageFromDatabase()i naturalnie ageFromInput()już wróci Optional<Integer>.

A potem coalescestaje się effectiveAge.get()lub po prostu, effectiveAgejeśli jesteśmy zadowoleni z Supplier<Integer>.

IMHO, w Javie 8 zobaczymy coraz więcej kodu o takiej strukturze, ponieważ jest on niezwykle samo-wyjaśniający i jednocześnie wydajny, szczególnie w bardziej złożonych przypadkach.

Tęsknię za klasą, Lazy<T>która wywołuje Supplier<T>tylko raz, ale leniwie, a także spójność w definicji Optional<T>(tj. Optional<T>- Optional<T>operatorów, a nawet Supplier<Optional<T>>).


4

Możesz spróbować tego:

public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

Na podstawie tej odpowiedzi


3

Co powiesz na korzystanie z dostawców, jeśli chcesz uniknąć oceny jakiejś drogiej metody?

Lubię to:

public static <T> T coalesce(Supplier<T>... items) {
for (Supplier<T> item : items) {
    T value = item.get();
    if (value != null) {
        return value;
    }
    return null;
}

A potem używając tego w ten sposób:

Double amount = coalesce(order::firstAmount, order::secondAmount, order::thirdAmount)

Możesz również użyć przeciążonych metod dla wywołań z dwoma, trzema lub czterema argumentami.

Ponadto możesz również użyć strumieni z czymś takim:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}

Po co zawijać pierwszy argument w, Supplierskoro i tak będzie on sprawdzany? Ze względu na jednolitość?
Inego

0

Co powiesz na:

firstNonNull = FluentIterable.from(
    Lists.newArrayList( a, b, c, ... ) )
        .firstMatch( Predicates.notNull() )
            .or( someKnownNonNullDefault );

Java ArrayList dogodnie zezwala na wpisy o wartości null, a to wyrażenie jest spójne niezależnie od liczby obiektów do rozważenia. (W tej formie wszystkie rozważane obiekty muszą być tego samego typu).


-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}

2
Boże, nienawidzę leków generycznych. Od razu zrozumiałem, co masz na myśli. Musiałem dwukrotnie spojrzeć na @ LES2, aby dowiedzieć się, że robi to samo (i prawdopodobnie „lepiej”)! +1 dla jasności
Bill K

Tak, leki generyczne są do zrobienia. Ale nie jestem zbyt zaznajomiony z zawiłościami.
Eric

10
Czas nauczyć się generyków :-). Istnieje niewielka różnica między przykładem @ LES2 a tym, inna niż T zamiast Object. -1 do budowania funkcji, która wymusi rzutowanie wartości zwracanej z powrotem na Double. Również do nazywania metody Java wielkimi literami, co może być dobre w SQL, ale nie jest dobrym stylem w Javie.
Avi

1
Zdaję sobie sprawę, że czapki z daszkiem to zły zwyczaj. Właśnie pokazywałem OP, jak napisać funkcję pod żądaną nazwą. Zgoda, rzut z powrotem do Doublejest daleki od ideału. Po prostu nie byłem świadomy, że statycznym funkcjom można nadać parametry typu. Myślałem, że to tylko zajęcia.
Eric
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.