Rangkaian metode null-safe dengan Opsional

Pola Optional Guava sangat bagus, karena membantu menghilangkan ambiguitas dengan null. Metode transform sangat membantu untuk membuat rantai metode null-safe ketika bagian pertama dari rantai tersebut mungkin tidak ada, namun tidak berguna ketika bagian lain dari rantai tersebut tidak ada.

Pertanyaan ini terkait dengan Jenis Opsional Jambu Biji, ketika transformasi mengembalikan Opsional lain, yang pada dasarnya menanyakan pertanyaan yang sama tetapi untuk kasus penggunaan berbeda yang menurut saya mungkin bukan tujuan penggunaan Optional (penanganan kesalahan).

Pertimbangkan metode Optional<Book> findBook(String id). findBook(id).transform(Book.getName) berfungsi seperti yang diharapkan. Jika tidak ditemukan buku kita mendapat Absent<String>, jika ada buku ditemukan kita mendapat Present<String>.

Dalam kasus umum di mana metode perantara dapat mengembalikan null/absent(), tampaknya tidak ada cara yang elegan untuk menyambungkan panggilan. Misalnya, asumsikan Book memiliki metode Optional<Publisher> getPublisher(), dan kita ingin semua buku diterbitkan oleh penerbit sebuah buku. Sintaks aslinya adalah findBook(id).transform(Book.getPublisher).transform(Publisher.getPublishedBooks), namun ini akan gagal karena panggilan transform(Publisher.getPublishedBooks) sebenarnya akan mengembalikan Optional<Optional<Publisher>>.

Tampaknya cukup masuk akal untuk memiliki metode seperti transform() di Optional yang akan menerima fungsi yang mengembalikan Optional. Ini akan bertindak persis seperti implementasi saat ini kecuali bahwa itu tidak akan membungkus hasil fungsi dalam Opsional. Implementasinya (untuk Present) mungkin berbunyi:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    return function.apply(reference);
}

Implementasi untuk Absent tidak berubah dari transform:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    checkNotNull(function);
    return Optional.absent();
}

Akan lebih baik juga jika ada cara untuk menangani metode yang mengembalikan null dibandingkan dengan Optional untuk bekerja dengan objek lama. Metode seperti itu akan seperti transform tetapi cukup panggil Optional.fromNullable pada hasil fungsinya.

Saya ingin tahu apakah ada orang lain yang mengalami gangguan ini dan menemukan solusi yang bagus (yang tidak melibatkan penulisan kelas Optional Anda sendiri). Saya juga ingin mendengar pendapat tim Guava atau diarahkan ke diskusi terkait masalah tersebut (saya tidak menemukannya dalam pencarian saya).


person Yona Appletree    schedule 31.05.2013    source sumber
comment
Sudahkah Anda mencoba grup diskusi? Pertimbangkan untuk memposting di sana, meskipun hanya untuk menunjuk pada pertanyaan (yang ditulis dengan baik) ini.   -  person Paul Bellora    schedule 03.07.2013


Jawaban (2)


Anda mencari beberapa Monad, tetapi Opsional Guava (sebagai kebalikan dari misalnya Opsi Scala) hanyalah sebuah Functor.

Apa itu Functor?!

Functor dan Monad adalah sejenis kotak, sebuah konteks yang membungkus beberapa nilai. Functor yang berisi beberapa nilai tipe A mengetahui cara menerapkan fungsi A => B dan mengembalikan hasilnya ke Functor. Misalnya: keluarkan sesuatu dari Opsional, ubah, dan gabungkan kembali menjadi Opsional. Dalam bahasa pemrograman fungsional metode seperti ini sering disebut 'peta'.

Mona.. apa?

Monad hampir sama dengan Functor, hanya saja ia menggunakan fungsi yang mengembalikan nilai yang dibungkus dalam Monad (A => Monad, misalnya Int => Opsional). Metode ajaib Monad ini sering disebut 'flatMap'.

Di sini Anda dapat menemukan penjelasan yang sangat mengagumkan untuk istilah-istilah dasar KB: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Functor & Monad akan hadir!

Opsional dari Java 8 dapat diklasifikasikan sebagai Functor (http://docs.Oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Fungsi-) dan Monad (http://docs.Oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-).

Mon(ad)olog yang baik, Marcin, tapi bagaimana cara mengatasi masalah khusus saya?

Saat ini saya sedang mengerjakan proyek yang menggunakan Java 6 dan kemarin saya menulis beberapa kelas pembantu, yang disebut 'Opsional', yang menghemat banyak waktu.

Ini menyediakan beberapa metode pembantu, yang memungkinkan saya mengubah Opsional menjadi Monads (flatMap).

Berikut kodenya: https://Gist.github.com/mkubala/046ae20946411f80ac52

Karena basis kode proyek saya masih menggunakan null sebagai nilai kembalian, saya memperkenalkan Optionals.lift(Function), yang dapat digunakan untuk menggabungkan hasil ke dalam Opsional.

Mengapa mengangkat hasil menjadi Opsional? Untuk menghindari situasi ketika fungsi yang diteruskan ke transformasi mungkin mengembalikan nol dan seluruh ekspresi akan mengembalikan "hadiah nol" (yang tidak mungkin dilakukan dengan Opsional Guava, karena kondisi pasca ini -> lihat baris #71 dari https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71).

Beberapa contoh

Mari kita asumsikan bahwa findEntity() mengembalikan Opsional dan Entity.getDecimalField(..) dapat mengembalikan BigDecimal atau null:

Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    new Function<Entity, Optional<BigDecimal>> () {
        @Override 
        public Optional<BigDecimal> apply(Entity input) {
            return Optional.fromNullable(input.getDecimalField(..));
        }
    }
);

Contoh lain, dengan asumsi saya sudah memiliki beberapa Fungsi, yang mengekstrak nilai desimal dari Entitas, dan mungkin mengembalikan nol:

Function<Entity, Decimal> extractDecimal = .. // extracts decimal value or null
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    Optionals.lift(extractDecimal)
);

Dan yang terakhir, namun tidak kalah pentingnya - kasus penggunaan Anda sebagai contoh:

Optional<Publisher> maybePublisher = Optionals.flatMap(findBook(id), Optionals.lift(Book.getPublisher));

// Assuming that getPublishedBooks may return null..
Optional<List<Book>> maybePublishedBooks = Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks));

// ..or simpler, in case when getPublishedBooks never returns null
Optional<List<Book>> maybePublishedBooks2 = maybePublisher.transform(Publisher.getPublishedBooks);

// as a one-liner:
Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks)).transform(Publisher.getPublishedBooks);
person Community    schedule 25.06.2014

Anda mungkin sudah mengetahuinya, tetapi Anda dapat menambahkan .or(Optional.absent) setelah setiap transformasi yang mengembalikan Optional (dalam kasus Anda setelah .transform(Book.getPublisher) mengurangi Optional<Optional<T>> menjadi Optional<T>:

Optional<List<Book>> publishedBooks = findBook(id).transform(Book.getPublisher).
        or(Optional.absent()).transform(Publisher.getPublishedBooks);

Sayangnya, tipe Optional.absent tidak dapat disimpulkan di sini, sehingga kodenya menjadi:

Optional<List<Book>> publishedBooks = book.transform(Book.getPublisher).
        or(Optional.<Publisher> absent()).transform(Publisher.getPublishedBoooks);

Tidak terlalu nyaman tetapi sepertinya tidak ada cara lain.

person siledh    schedule 26.10.2013