Gaya fungsional Java 8's Opsional. Jika Ada dan Jika-Tidak-Ada?

274

Di Java 8, saya ingin melakukan sesuatu ke Optionalobjek jika ada, dan melakukan hal lain jika tidak ada.

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

Ini bukan 'gaya fungsional'.

Optionalmemiliki ifPresent()metode, tetapi saya tidak dapat mengaitkan orElse()metode.

Jadi, saya tidak bisa menulis:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

Sebagai balasan untuk @assylias, saya pikir ini tidak Optional.map()berfungsi untuk kasus berikut:

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

Dalam hal ini, ketika optada, saya memperbarui propertinya dan menyimpan ke database. Ketika tidak tersedia, saya membuat yang baru objdan menyimpan ke database.

Catatan dalam dua lambda saya harus kembali null.

Tetapi ketika optada, kedua lambda akan dieksekusi. objakan diperbarui, dan objek baru akan disimpan ke database. Ini karena return nulldalam lambda pertama. Dan orElseGet()akan terus dieksekusi.

smallufo
sumber
53
Gunakan sampel pertama Anda. Itu indah .
Sotirios Delimanolis
3
Saya sarankan Anda berhenti memaksakan perilaku tertentu saat menggunakan API yang tidak dirancang untuk perilaku itu. Anda contoh pertama terlihat baik-baik saja bagi saya terlepas dari beberapa pernyataan gaya kecil, tetapi itu pendapat.
skiwi
4
@smallufo: ganti return null;dengan return o;(keduanya). Namun, saya memiliki perasaan kuat bahwa Anda bekerja di tempat yang salah. Anda harus bekerja di situs yang menghasilkan itu Optional. Di tempat itu harus ada cara melakukan operasi yang diinginkan tanpa perantara Optional.
Holger
10
Java 9 mengimplementasikan solusi untuk masalah Anda: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Saya pikir alasan ini tidak dapat dilakukan dengan mudah adalah sengaja. Opsional seharusnya tidak melakukan kontrol aliran, melainkan transformasi nilai. Saya tahu yang ifPresentbertentangan ini. Semua metode lain merujuk pada nilai dan bukan tindakan.
AlikElzin-kilaka

Jawaban:

109

Bagi saya jawaban dari @Dane White adalah OK, pertama saya tidak suka menggunakan Runnable tapi saya tidak bisa menemukan alternatif, di sini implementasi lain saya lebih suka

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

Kemudian :

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s ->System.out.println("isPresent "+s))
            .ifNotPresent(() -> System.out.println("! isPresent"));

Pembaruan 1:

solusi di atas untuk cara pengembangan tradisional ketika Anda memiliki nilai dan ingin memprosesnya, tetapi bagaimana jika saya ingin mendefinisikan fungsionalitas dan pelaksanaannya nanti, periksa peningkatan di bawah ini;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

Kemudian bisa digunakan sebagai:

    Consumer<Optional<Integer>> c=OptionalConsumer.of(System.out::println, ()->{System.out.println("Not fit");});
    IntStream.range(0, 100).boxed().map(i->Optional.of(i).filter(j->j%2==0)).forEach(c);

Dalam kode baru ini Anda memiliki 3 hal:

  1. dapat mendefinisikan fungsionalitas sebelum ada objek yang mudah.
  2. tidak membuat objek refrence untuk setiap Opsional, hanya satu, Anda memiliki lebih sedikit memori daripada kurang GC.
  3. itu menerapkan konsumen untuk penggunaan yang lebih baik dengan komponen lain.

ngomong-ngomong sekarang namanya lebih deskriptif itu sebenarnya Consumer>

Bassem Reda Zohdy
sumber
3
Harus menggunakan Optional.ofNullable (o) dan bukan Optional.of (o)
traeper
2
Anda perlu menggunakan ofNullable jika Anda tidak yakin apakah nilai yang akan Anda gunakan memiliki nol atau tidak dan tidak perlu menghadapi NPE, dan jika Anda yakin itu bukan nol atau Anda tidak peduli jika mendapatkan NPE.
Bassem Reda Zohdy
1
Saya pikir kelas OptionalConsumer terlihat lebih baik daripada if / else dalam kode. Terima kasih! :)
witek1902
207

Jika Anda menggunakan Java 9+, Anda dapat menggunakan ifPresentOrElse()metode:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
sumber
3
Bagus karena hampir sebersih pencocokan pola di Scala
sscarduzio
Dua lambda seperti itu sangat jelek. Saya pikir jika / lain jauh lebih bersih untuk kasus ini.
john16384
1
@ john16384 OK, jika Anda merasa jelek, maka saya akan menghapus jawaban saya (tidak).
ZhekaKozlov
Ini sangat bagus tetapi pertanyaan itu dimaksudkan khusus untuk JDK8 karena ifPresentOrElse tidak tersedia.
Hreinn
81

Java 9 memperkenalkan

ifPresentOrElse jika ada nilai, lakukan tindakan yang diberikan dengan nilai tersebut, jika tidak lakukan tindakan berbasis kosong yang diberikan.

Lihat Opsional yang sangat baik di lembar contekan Java 8 .

Ini memberikan semua jawaban untuk sebagian besar kasus penggunaan.

Ringkasan singkat di bawah ini

ifPresent () - lakukan sesuatu ketika Opsional diatur

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter () - tolak (filter out) nilai Opsional tertentu.

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map () - mentransformasikan nilai jika ada

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse () / orElseGet () - mengubah kosong Opsional ke standar T

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow () - malas melempar pengecualian pada Opsional kosong

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);
Bartosz Bilicki
sumber
66
Ini sebenarnya tidak menjawab pertanyaan OP. Ini menjawab banyak kegunaan umum tetapi tidak apa yang diminta OP.
Kapten Man
1
@ KaptenMan sebenarnya itu; ekspresi opt.map ("found"). orElse ("not found") memenuhi tagihan.
Matt
4
@ Mat tidak, OP secara khusus meminta tindakan yang dia lakukan ketika opsional ada / tidak ada, tidak mengembalikan nilai ketika itu atau tidak. OP bahkan menyebutkan sesuatu yang mirip dalam pertanyaan menggunakan orElseGet menjelaskan mengapa itu tidak berhasil.
Kapten Man
2
@ Kapten Saya melihat poin Anda. Saya pikir dia bisa membuatnya bekerja jika dia tidak mengembalikan nol dari map, tetapi agak aneh untuk meminta solusi fungsional sehingga Anda dapat memanggil DAO. Menurut saya akan lebih masuk akal untuk mengembalikan objek yang diperbarui / baru dari map.orElseblok ini dan kemudian melakukan apa yang perlu Anda lakukan dengan objek yang dikembalikan.
Matt
1
Saya pikir mapfokus pada aliran itu sendiri dan tidak dimaksudkan untuk "melakukan sesuatu ke objek lain tergantung pada status elemen ini dalam aliran". Baik untuk mengetahui yang ifPresentOrElseditambahkan di Jawa 9.
WesternGun
53

Alternatifnya adalah:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

Saya tidak berpikir itu meningkatkan keterbacaan.

Atau seperti yang disarankan Marko, gunakan operator ternary:

System.out.println(opt.isPresent() ? "Found" : "Not found");
assylias
sumber
2
Terima kasih @assylias, tapi saya rasa Optional.map () tidak berfungsi untuk kasus ini (lihat pembaruan konteks saya).
smallufo
2
@smallufo Anda harus kembali new Object();dengan lambda pertama Anda, tetapi jujur ​​saja itu menjadi sangat jelek. Saya akan berpegang pada if / else untuk contoh Anda yang diperbarui.
assylias
Setuju, menggunakan mapuntuk hanya kembali Optionaluntuk merantai membuat kode lebih sulit untuk dipahami sementara mapdiasumsikan secara harfiah memetakan sesuatu.
Tiina
40

Solusi lain adalah dengan menggunakan fungsi tingkat tinggi sebagai berikut

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
pengguna5057016
sumber
8
Di mata saya solusi terbaik sejauh ini tanpa JDK 9.
Semaphor
5
Penjelasan akan sangat bagus. Saya bertanya pada diri sendiri, mengapa Anda perlu menggunakan peta runnable (?) Dan apa arti value -> () -> sysobagian itu.
froehli
Terima kasih atas solusi ini! Saya pikir alasan menggunakan Runnable adalah bahwa out map tidak mengembalikan nilai apa pun dan dengan Runnable itu mengembalikan lambda dan seperti hasil peta lambda kita jalankan setelah itu. Jadi, jika Anda memiliki nilai pengembalian, Anda dapat menggunakan yang berikut ini:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik
21

Tidak ada cara yang bagus untuk melakukannya di luar kotak. Jika Anda ingin menggunakan sintaks pembersih Anda secara teratur, maka Anda dapat membuat kelas utilitas untuk membantu:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

Kemudian Anda dapat menggunakan impor statis di tempat lain untuk mendapatkan sintaks yang mendekati apa yang Anda cari:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));
Dane White
sumber
Terima kasih. Solusi ini indah. Saya tahu mungkin tidak ada solusi bawaan (kecuali JDK menggabungkan metode seperti itu). Anda OpsionalEx sangat membantu. Bagaimanapun, terima kasih.
smallufo
Ya, saya suka hasilnya, dan gaya yang didukungnya. JADI, mengapa tidak di API standar?
guthrie
Jawaban yang bagus. Kami melakukan hal yang sama. Saya setuju itu harus dalam API (atau bahasa!), Tetapi telah ditolak: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith
Bagus. Ini harus menjadi bagian dari JDK 8.1 untuk dipertimbangkan.
peter_pilgrim
35
Optional.ifPresentOrElse()telah ditambahkan ke JDK 9.
Stuart Marks
9

Jika Anda hanya dapat menggunakan Java 8 atau lebih rendah:

1) jika Anda tidak memiliki spring-datacara terbaik sejauh ini adalah:

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

Sekarang saya tahu itu tidak mudah dibaca dan bahkan sulit dimengerti untuk seseorang, tetapi terlihat baik bagi saya secara pribadi dan saya tidak melihat cara lain yang lancar untuk kasus ini.

2) jika Anda cukup beruntung dan Anda dapat menggunakan spring-datacara terbaik adalah Opsional # ifPresentOrElse :

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

Jika Anda dapat menggunakan Java 9, Anda harus menggunakan:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));
Tyulpan Tyulpan
sumber
2

Perilaku yang dijelaskan dapat dicapai dengan menggunakan VAVR (sebelumnya dikenal sebagai Javaslang), perpustakaan fungsional-objek untuk Java 8+, yang mengimplementasikan sebagian besar konstruksi Scala (menjadi Scala bahasa yang lebih ekspresif dengan cara tipe sistem yang lebih kaya yang dibangun pada JVM). Ini adalah pustaka yang sangat bagus untuk ditambahkan ke proyek Java Anda untuk menulis kode fungsional murni.

Vavr menyediakan Optionmonad yang menyediakan fungsi untuk bekerja dengan jenis Opsi seperti:

  • fold: untuk memetakan nilai opsi pada kedua kasus (ditentukan / kosong)
  • onEmpty: memungkinkan untuk mengeksekusi Runnableopsi ketika kosong
  • peek: memungkinkan untuk mengkonsumsi nilai opsi (saat ditentukan).
  • dan itu juga Serializablesebaliknya Optionalyang berarti Anda dapat menggunakannya dengan aman sebagai argumen metode dan anggota contoh.

Opsi mengikuti hukum monad yang berbeda dengan "pseudo-monad" opsional Java dan menyediakan API yang lebih kaya. Dan tentu saja Anda dapat membuatnya dari Java's Optional (dan sebaliknya):Option.ofOptional(javaOptional) -Vavr berfokus pada interoperabilitas.

Pergi ke contoh:

// AWESOME Vavr functional collections (immutable for the gread good :)
// fully convertible to Java's counterparts.
final Map<String, String> map = Map("key1", "value1", "key2", "value2");

final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!

final String result = opt.fold(
        () -> "Not found!!!",                // Option is None
        val -> "Found the value: " + val     // Option is Some(val)
);

Bacaan lebih lanjut

Referensi kosong, kesalahan miliar dolar

NB Ini hanya sedikit contoh dari apa yang ditawarkan Vavr (pencocokan pola, stream alias daftar malas yang dievaluasi, tipe monadik, koleksi tidak berubah, ...).

Gerard Bosch
sumber
1

Solusi lain dapat mengikuti:

Ini adalah bagaimana Anda menggunakannya:

    final Opt<String> opt = Opt.of("I'm a cool text");
    opt.ifPresent()
        .apply(s -> System.out.printf("Text is: %s\n", s))
        .elseApply(() -> System.out.println("no text available"));

Atau jika Anda dalam kasus penggunaan sebaliknya adalah benar:

    final Opt<String> opt = Opt.of("This is the text");
    opt.ifNotPresent()
        .apply(() -> System.out.println("Not present"))
        .elseApply(t -> /*do something here*/);

Ini bahannya:

  1. Antarmuka Fungsi yang sedikit dimodifikasi, hanya untuk metode "elseApply"
  2. Peningkatan opsional
  3. Sedikit mengalir :-)

Antarmuka Fungsi yang ditingkatkan "secara kosmetik".

@FunctionalInterface
public interface Fkt<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }

}

Dan kelas bungkus opsional untuk peningkatan:

public class Opt<T> {

    private final Optional<T> optional;

    private Opt(final Optional<T> theOptional) {
        this.optional = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

    private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
        if (this.optional.isPresent()) {
            present.accept(this.optional.get());
        } else {
            notPresent.run();
        }
        return null;
    };

   private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.optional.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.optional.get());
        }
        return null;
    };

    public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

    private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
        return (final X x) -> (final Y y) -> function.apply(x, y);
    }
}

Ini harus melakukan trik dan dapat berfungsi sebagai templat dasar bagaimana menangani persyaratan tersebut.

Ide dasar di sini adalah sebagai berikut. Dalam dunia pemrograman gaya non fungsional Anda mungkin akan menerapkan metode mengambil dua parameter di mana yang pertama adalah jenis kode runnable yang harus dieksekusi jika nilai tersedia dan parameter lainnya adalah kode runnable yang harus dijalankan jika nilai tidak tersedia. Demi keterbacaan yang lebih baik, Anda dapat menggunakan arus untuk membagi fungsi dua parameter menjadi dua fungsi masing-masing satu parameter. Inilah yang pada dasarnya saya lakukan di sini.

Petunjuk: Keikutsertaan juga menyediakan use case lain di mana Anda ingin mengeksekusi sepotong kode kalau-kalau nilainya tidak tersedia. Ini dapat dilakukan juga melalui Optional.filter.stuff tetapi saya menemukan ini jauh lebih mudah dibaca.

Semoga itu bisa membantu!

Pemrograman yang bagus :-)

Alessandro Giusa
sumber
Bisakah Anda memberi tahu apa yang tidak berfungsi? Saya mengujinya lagi dan bagi saya itu berhasil?
Alessandro Giusa
Maaf, tapi di mana ckass Fkt didefinisikan? Terakhir, namun tidak kalah pentingnya, saya memiliki masalah kompilasi: Kesalahan: (35, 17) java: variabel opsional mungkin belum diinisialisasi
Enrico Giurin
Fkt didefinisikan di atas sebagai antarmuka. Baca saja seluruh artikel :-)
Alessandro Giusa
Iya. Saya mengubah antarmuka EFunction menjadi Fkt sebagai nama. Ada kesalahan ketik. Terima kasih untuk ulasannya :-) maaf untuk itu.
Alessandro Giusa
Saya tidak berpikir itu ide bagus untuk menulis kode seperti ini ... Anda harus menggunakan utilitas jdk ketika Anda bisa.
Tyulpan Tyulpan
0

Jika Anda ingin menyimpan nilai:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));
Jhutan Debnath
sumber
0

Misalkan Anda memiliki daftar dan menghindari isPresent()masalah (terkait dengan opsional) yang dapat Anda gunakan .iterator().hasNext()untuk memeriksa jika tidak ada.

Leandro Maro
sumber