Bagaimana cara mendapatkan nilai non-null pertama di Java?


154

Apakah ada Java yang setara dengan COALESCEfungsi SQL ? Yaitu, adakah cara untuk mengembalikan nilai non-null pertama dari beberapa variabel?

misalnya

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

Saya ingin entah bagaimana memiliki pernyataan yang akan mengembalikan nilai non-null pertama a, bdan c- dalam hal ini, itu akan kembali b, atau 4,4. (Sesuatu seperti metode sql - return COALESCE(a,b,c)). Saya tahu bahwa saya dapat melakukannya secara eksplisit dengan sesuatu seperti:

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

Tetapi saya bertanya-tanya apakah ada fungsi bawaan yang diterima untuk melakukan ini.


3
Anda seharusnya tidak memerlukan fungsi seperti ini karena Anda secara genetis tidak akan menghitung 'c' jika 'b' memiliki jawaban yang Anda inginkan. yaitu Anda tidak akan membuat daftar jawaban yang mungkin hanya untuk menyimpannya.
Peter Lawrey

Peringatan: Tidak semua hubungan pendek RDBMS di COALESCE. Oracle baru-baru ini mulai melakukannya.
Adam Gent

3
@ BrainSlugs83 Serius? Jawa harus?
Dmitry Ginzburg

Jawaban:


108

Tidak, tidak ada.

Yang paling dekat yang bisa Anda dapatkan adalah:

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

Untuk alasan yang efisien, Anda dapat menangani kasus umum sebagai berikut:

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
alasan efisiensi yang saya sebutkan di atas adalah bahwa alokasi array akan terjadi setiap kali Anda menjalankan versi metode ini. ini bisa sia-sia untuk barang-barang yang penuh dengan tangan, yang saya curigai akan umum digunakan.
les2

Keren. Terima kasih. Dalam hal ini saya mungkin akan tetap berpegang pada operator kondisional bersarang dalam kasus ini karena ini adalah satu-satunya waktu yang harus digunakan dan metode yang ditentukan pengguna akan berlebihan ...
froadie

8
Saya masih akan menariknya ke dalam metode pembantu pribadi daripada meninggalkan blok bersyarat "menakutkan" dalam kode - "apa fungsinya?" dengan cara itu, jika Anda memang perlu menggunakannya lagi, Anda dapat menggunakan alat refactoring di IDE Anda untuk memindahkan metode ke kelas utilitas. memiliki metode bernama membantu untuk mendokumentasikan maksud kode, yang selalu merupakan hal yang baik, IMO. (dan overhead dari versi non var-args mungkin hampir tidak dapat diukur.)
les2

10
Awas: Dalam coalesce(a, b), jika bmerupakan ekspresi yang rumit dan atidak null, bmasih dievaluasi. Ini bukan kasus untuk?: Operator kondisional. Lihat jawaban ini .
Pang

ini mengharuskan setiap argumen untuk dihitung sebelum panggilan untuk menyatu, tidak ada gunanya karena alasan kinerja
Ivan G.


59

Jika hanya ada dua variabel untuk diperiksa dan Anda menggunakan Guava, Anda dapat menggunakan MoreObjects.firstNonNull (T pertama, T kedua) .


49
Objects.firstNonNull hanya membutuhkan dua argumen; tidak ada varargs yang setara di Jambu. Selain itu, ia melempar NullPointerException jika kedua argumennya nol - ini mungkin atau mungkin tidak diinginkan.

2
Komentar yang bagus, Jake. NullPointerException ini sering membatasi penggunaan Objects.firstNonNull. Namun, itu pendekatan Guava untuk menghindari nol sama sekali.
Anton Shchastnyi

4
Metode itu sekarang sudah tidak digunakan lagi, dan alternatif yang disarankan adalah MoreObjects.firstNonNull
davidwebster48

1
Jika NPE tidak diinginkan, lihat jawaban ini
OrangeDog

51

Jika hanya ada dua referensi untuk diuji dan Anda menggunakan Java 8, Anda bisa menggunakannya

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

Jika Anda mengimpor statis Opsional, ekspresi tidak terlalu buruk.

Sayangnya kasus Anda dengan "beberapa variabel" tidak dimungkinkan dengan metode-Opsional. Sebagai gantinya Anda dapat menggunakan:

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

Mengikuti dari jawaban LES2, Anda dapat menghilangkan beberapa pengulangan dalam versi efisien, dengan memanggil fungsi kelebihan beban:

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 untuk cantik. Tidak yakin tentang manfaat efisiensi daripada loop sederhana, tetapi jika Anda akan menambah efisiensi kecil dengan cara ini, mungkin juga cukup.
Carl Manaster

3
cara ini membuatnya jauh lebih menyakitkan dan lebih sedikit kesalahan untuk menulis varian yang kelebihan beban!
les2

2
Inti dari versi yang efisien adalah untuk tidak membuang memori yang mengalokasikan array dengan menggunakan varargs. Di sini, Anda membuang-buang memori dengan membuat bingkai tumpukan untuk setiap coalesce()panggilan bersarang . Memanggil coalesce(a, b, c, d, e)menciptakan hingga 3 frame stack untuk menghitung.
Luke

10

Situasi ini memerlukan beberapa preprosesor. Karena jika Anda menulis fungsi (metode statis) yang memilih yang pertama bukan nilai nol, itu mengevaluasi semua item. Masalah jika beberapa item adalah panggilan metode (mungkin panggilan metode waktu yang mahal). Dan metode ini dipanggil bahkan jika setiap item sebelum mereka bukan nol.

Beberapa fungsinya seperti ini

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

harus digunakan tetapi sebelum mengkompilasi ke dalam kode byte harus ada preprocessor yang menemukan penggunaan "fungsi penggabungan" ini dan menggantinya dengan konstruksi seperti

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

Pembaruan 2014-09-02:

Berkat Java 8 dan Lambdas ada kemungkinan untuk memiliki perpaduan sejati di Jawa! Termasuk fitur penting: ekspresi tertentu dievaluasi hanya bila diperlukan - jika sebelumnya tidak nol, maka yang berikut tidak dievaluasi (metode tidak dipanggil, operasi komputasi atau disk / jaringan tidak dilakukan).

Saya menulis artikel tentang hal itu Java 8: coalesce - hledáme neNULLové hodnoty - (ditulis dalam bahasa Ceko, tapi saya harap contoh kode dapat dimengerti oleh semua orang).


1
Artikel yang bagus - akan menyenangkan jika berbahasa Inggris.
quantum

1
Ada sesuatu tentang halaman blog yang tidak berfungsi dengan Google Translate. :-(
HairOfTheDog

5

Dengan Jambu Biji, Anda bisa melakukannya:

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

yang tidak melempar NPE jika keduanya adan bsedang null.

EDIT: Saya salah, itu melempar NPE. Cara yang benar seperti yang dikomentari oleh Michal Čizmazia adalah:

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

1
Hei, memang:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michal Čizmazia

1
Ini Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
caranya

4

Hanya untuk kelengkapan, kasus "beberapa variabel" memang mungkin, meskipun tidak elegan sama sekali. Misalnya, untuk variabel o, pdan q:

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

Harap dicatat penggunaan orElseGet()menghadiri untuk kasus yang o, pdan qtidak variabel tapi ekspresi baik mahal atau dengan yang tidak diinginkan efek samping.

Dalam kasus yang paling umum 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

Ini bisa menghasilkan ekspresi terlalu lama. Namun, jika kita mencoba untuk pindah ke dunia tanpa null, maka v[i]kemungkinan besar sudah bertipe Optional<String>, bukan hanya String. Pada kasus ini,

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

atau dalam hal ekspresi:

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

Selain itu, jika Anda juga pindah ke gaya fungsional-deklaratif, o, p, dan qharus dari jenis Supplier<String>seperti di:

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

Dan kemudian keseluruhan coalescedikurangi menjadi o.get().

Untuk contoh yang lebih konkret:

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()dan ageFromInput()sudah akan kembali Optional<Integer>, secara alami.

Dan kemudian coalescemenjadi effectiveAge.get()atau hanya effectiveAgejika kita senang dengan Supplier<Integer>.

IMHO, dengan Java 8 kita akan melihat semakin banyak kode terstruktur seperti ini, karena sangat jelas dan efisien pada saat yang sama, terutama dalam kasus yang lebih kompleks.

Saya melewatkan kelas Lazy<T>yang memanggil Supplier<T>hanya satu kali, tetapi malas, serta konsistensi dalam definisi Optional<T>(yaitu Optional<T>- Optional<T>operator, atau bahkan Supplier<Optional<T>>).


4

Anda dapat mencoba ini:

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

Berdasarkan tanggapan ini


3

Bagaimana dengan menggunakan pemasok ketika Anda ingin menghindari mengevaluasi beberapa metode mahal?

Seperti ini:

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

Dan kemudian menggunakannya seperti ini:

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

Anda juga dapat menggunakan metode kelebihan beban untuk panggilan dengan dua, tiga atau empat argumen.

Selain itu, Anda juga bisa menggunakan stream dengan sesuatu seperti ini:

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

Mengapa membungkus argumen pertama dalam Supplierjika itu tetap akan diperiksa? Demi keseragaman?
Inego

0

Bagaimana tentang:

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

Java ArrayList dengan mudah memungkinkan entri nol dan ekspresi ini konsisten terlepas dari jumlah objek yang dipertimbangkan. (Dalam bentuk ini, semua objek yang dipertimbangkan harus memiliki tipe yang sama.)


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

2
Ya Tuhan, aku benci obat generik. Saya melihat apa yang Anda maksudkan langsung dari kelelawar. Saya harus melihat @ LES2 dua kali untuk mengetahui bahwa dia melakukan hal yang sama (dan mungkin "lebih baik")! +1 untuk kejelasan
Bill K

Ya, obat generik adalah jalan yang harus ditempuh. Tapi aku tidak terlalu akrab dengan seluk-beluknya.
Eric

10
Saatnya mempelajari obat generik :-). Ada sedikit perbedaan antara contoh @ LES2 dan ini, selain T daripada Object. -1 untuk membangun fungsi yang akan memaksa casting nilai kembali kembali ke Gandakan. Juga untuk memberi nama metode Java di semua-topi, yang mungkin baik-baik saja dalam SQL, tetapi tidak gaya yang baik di Jawa.
Avi

1
Saya menyadari bahwa all-caps adalah praktik buruk. Saya baru saja menunjukkan OP bagaimana menulis fungsi dengan nama yang mereka minta. Setuju, para pemeran kembali ke Doublejauh dari ideal. Saya hanya tidak sadar bahwa fungsi statis dapat diberikan parameter tipe. Saya pikir itu hanya kelas.
Eric
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.