Opsional orElse Opsional di Java

138

Saya telah bekerja dengan tipe Opsional baru di Java 8 , dan saya menemukan apa yang tampak seperti operasi umum yang tidak didukung secara fungsional: sebuah "orElseOptional"

Perhatikan pola berikut:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

Ada banyak bentuk dari pola ini, tetapi intinya adalah menginginkan "orElse" pada opsional yang mengambil fungsi yang menghasilkan opsional baru, yang dipanggil hanya jika yang sekarang tidak ada.

Implementasinya akan terlihat seperti ini:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

Saya ingin tahu apakah ada alasan mengapa metode seperti itu tidak ada, jika saya hanya menggunakan Opsional dengan cara yang tidak disengaja, dan cara lain apa yang dilakukan orang untuk menangani kasus ini.

Saya harus mengatakan bahwa menurut saya solusi yang melibatkan kelas / metode utilitas khusus tidak elegan karena orang yang bekerja dengan kode saya belum tentu tahu bahwa mereka ada.

Juga, jika ada yang tahu, apakah metode seperti itu akan dimasukkan dalam JDK 9, dan di mana saya bisa mengusulkan metode seperti itu? Bagi saya, ini sepertinya kelalaian yang cukup mencolok ke API.

Yona Appletree
sumber
13
Lihat masalah ini . Untuk memperjelas: ini sudah akan ada di Java 9 - jika tidak dalam pembaruan Java 8.
Obicere
Ini! Terima kasih, tidak menemukan itu dalam pencarian saya.
Yona Appletree
2
@Obicere Masalah itu tidak berlaku di sini karena ini tentang perilaku kosong Opsional, bukan tentang hasil alternatif . Opsional sudah memiliki orElseGet()apa yang dibutuhkan OP, hanya saja itu tidak menghasilkan sintaks kaskade yang bagus.
Marko Topolnik
1
Tutorial hebat tentang Java Opsional: codeflex.co/java-optional-no-more-nullpointerexception
John Detroit

Jawaban:

88

Ini adalah bagian dari JDK 9 dalam bentuk or, yang mengambil Supplier<Optional<T>>. Contoh Anda kemudian akan menjadi:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

Untuk detailnya lihat Javadoc atau posting ini yang saya tulis.

Nicolai Parlog
sumber
Bagus. Penambahan itu harus berusia satu tahun dan saya tidak menyadarinya. Mengenai pertanyaan di blog Anda, mengubah jenis kembalian akan merusak kompatibilitas biner, karena instruksi pemanggilan bytecode merujuk ke tanda tangan lengkap, termasuk jenis kembalian, jadi tidak ada peluang untuk mengubah jenis kembalian ifPresent. Tapi bagaimanapun, saya pikir nama ifPresentitu tidak bagus. Untuk semua metode lain tidak bantalan “yang lain” dalam nama (seperti map, filter, flatMap), itu tersirat bahwa mereka melakukan apa-apa jika tidak ada nilai hadir, jadi mengapa harus ifPresent...
Holger
Jadi menambahkan Optional<T> perform(Consumer<T> c)metode untuk memungkinkan rangkaian perform(x).orElseDo(y)( orElseDosebagai alternatif dari yang Anda usulkan ifEmpty, agar konsisten dalam elsenama semua metode yang mungkin melakukan sesuatu untuk nilai yang tidak ada). Anda dapat meniru itu performdi Java 9 melalui stream().peek(x).findFirst()meskipun itu adalah penyalahgunaan API dan masih tidak ada cara untuk mengeksekusi Runnabletanpa menentukan a Consumerpada saat yang sama…
Holger
65

Pendekatan "coba layanan" terbersih yang diberikan API saat ini adalah:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

Aspek pentingnya bukanlah rangkaian operasi (konstan) yang harus Anda tulis satu kali tetapi betapa mudahnya menambahkan layanan lain (atau mengubah daftar layanan secara umum). Di sini, menambahkan atau menghapus satu saja ()->serviceX(args)sudah cukup.

Karena evaluasi streaming yang lambat, tidak ada layanan yang akan dipanggil jika layanan sebelumnya menampilkan non-kosong Optional.

Holger
sumber
13
hanya menggunakannya dalam sebuah proyek, terima kasih Tuhan kami tidak melakukan tinjauan kode.
Ilya Smagin
3
Ini jauh lebih bersih, daripada rangkaian "orElseGet", tetapi juga jauh lebih sulit untuk dibaca.
slartidan
4
Ini memang benar ... tapi jujur, saya butuh waktu sedetik untuk menguraikannya dan saya tidak yakin bahwa itu benar. Ingatlah, saya menyadari bahwa contoh ini adalah, tetapi saya dapat membayangkan perubahan kecil yang akan membuatnya tidak malas dievaluasi atau memiliki kesalahan lain yang tidak dapat dibedakan dalam sekejap. Bagi saya, ini termasuk dalam kategori fungsi yang akan-menjadi-layak-sebagai-utilitas.
Yona Appletree
3
Saya ingin tahu apakah beralih .map(Optional::get)dengan .findFirst()akan membuatnya lebih mudah untuk "membaca", misalnya .filter(Optional::isPresent).findFirst().map(Optional::get)dapat "membaca" seperti "menemukan elemen pertama dalam aliran yang Opsional :: isPresent benar dan kemudian meratakannya dengan menerapkan Opsional :: get"?
schatten
3
Lucu, saya diposting yang sangat mirip solusi untuk pertanyaan serupa beberapa bulan yang lalu. Ini adalah pertama kalinya saya menemukan ini.
shmosel
34

Ini tidak cantik, tapi ini akan berhasil:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup)adalah pola yang cukup berguna untuk digunakan dengan Optional. Artinya "Jika ini Optionalmengandung nilai v, beri aku func(v), jika tidak beri aku sup.get()".

Dalam hal ini, kami menelepon serviceA(args)dan mendapatkan Optional<Result>. Jika itu Optionalmengandung nilai v, kami ingin mendapatkannya Optional.of(v), tetapi jika kosong, kami ingin mendapatkannya serviceB(args). Bilas-ulangi dengan lebih banyak alternatif.

Kegunaan lain dari pola ini adalah

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)
Misha
sumber
1
huh, ketika saya menggunakan strategi ini, eclipse mengatakan: "Metode orElseGet (Supplier <? extends String>) di tipe Opsional <String> tidak berlaku untuk argumen (() -> {})" Sepertinya tidak untuk mempertimbangkan mengembalikan Opsional strategi yang valid, di String ??
chrismarx
@chrismarx () -> {}tidak mengembalikan Optional. Apa yang ingin Anda capai?
Misha
1
Saya hanya mencoba mengikuti contoh. Merangkai panggilan peta tidak berfungsi. Layanan saya mengembalikan string, dan tentu saja tidak ada opsi .map () yang tersedia saat itu
chrismarx
1
Alternatif terbaca yang sangat baik untuk or(Supplier<Optional<T>>)Java 9 yang akan datang
David M.
1
@Shehe kamu salah. .map()di kosong Optionalakan menghasilkan yang kosong Optional.
Misha
27

Mungkin inilah yang Anda cari: Dapatkan nilai dari satu Opsional atau lainnya

Jika tidak, Anda mungkin ingin melihatnya Optional.orElseGet. Berikut adalah contoh dari apa yang menurut saya Anda cari:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));
aioobe
sumber
2
Inilah yang biasanya dilakukan para genius. Evaluasi opsional sebagai nullable dan membungkusnya dengan ofNullableadalah hal paling keren yang pernah saya lihat.
Jin Kwon
5

Dengan asumsi Anda masih menggunakan JDK8, ada beberapa opsi.

Opsi # 1: Buat metode pembantu Anda sendiri

Misalnya:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

Sehingga Anda dapat melakukan:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

Opsi # 2: Gunakan perpustakaan

Misalnya Opsional jambu google mendukung or()operasi yang benar (seperti JDK9), misalnya:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(Di mana setiap layanan kembali com.google.common.base.Optional, bukan java.util.Optional).

Sheepy
sumber
Saya tidak menemukan Optional<T>.or(Supplier<Optional<T>>)di dokumen Guava. Apakah Anda punya link untuk itu?
Tamas Hegedus
.orElseGet mengembalikan T tetapi Opsional :: mengembalikan kosong Opsional <T>. Opsional.ofNullable (........ orElse (null)) memiliki efek yang diinginkan seperti yang dijelaskan oleh @aioobe.
Miguel Pereira
@TamasHegedus Maksud Anda dalam opsi # 1? Ini adalah implementasi khusus yang ada di potongan di atasnya. ps: maaf atas jawaban yang terlambat
Sheepy
@MiguelPereira .orElseGet dalam hal ini mengembalikan Opsional <T> karena beroperasi pada Opsional <Opsional <T>>
Sheepy
2

Ini adalah tampak seperti cocok untuk pencocokan pola dan antarmuka Option yang lebih tradisional dengan beberapa dan ada implementasi (seperti di Javaslang , FunctionalJava ) atau malas Mungkin implementasi di cyclops-bereaksi .Saya penulis perpustakaan ini.

Dengan cyclops-react, Anda juga dapat menggunakan pencocokan pola struktural pada tipe JDK. Untuk Opsional, Anda dapat mencocokkan kasus saat ini dan yang tidak ada melalui pola pengunjung . itu akan terlihat seperti ini -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
John McClean
sumber