Perbedaan antara `Optional.orElse ()` dan `Optional.orElseGet ()`

206

Saya mencoba memahami perbedaan antara metode Optional<T>.orElse()dan Optional<T>.orElseGet()metode.

Deskripsi untuk orElse()metode ini adalah "Kembalikan nilainya jika ada, jika tidak kembalikan yang lain."

Sementara, deskripsi untuk orElseGet()metode ini adalah "Kembalikan nilainya jika ada, jika tidak panggil orang lain dan kembalikan hasil dari pemanggilan itu."

The orElseGet()Metode mengambil antarmuka fungsional Supplier, yang pada dasarnya tidak mengambil parameter dan kembali T.

Dalam situasi apa Anda perlu menggunakan orElseGet()? Jika Anda memiliki sebuah metode T myDefault()mengapa tidak Anda hanya melakukan optional.orElse(myDefault())daripada optional.orElseGet(() -> myDefault())?

Tampaknya tidak orElseGet()menunda eksekusi dari ekspresi lambda ke beberapa waktu kemudian atau lebih, jadi apa gunanya? (Saya akan berpikir bahwa itu akan lebih berguna jika mengembalikan yang lebih aman Optional<T>yang get()tidak pernah melempar NoSuchElementExceptiondan isPresent()selalu mengembalikan true ... tetapi ternyata tidak, itu hanya mengembalikan Tseperti orElse()).

Apakah ada perbedaan lain yang saya lewatkan?

jbx
sumber
7
Alasannya adalah ketika Anda menggunakannya orElseGethanya memanggil pemasok jika nilainya tidak ada.
Alex Salauyou
9
Ah ok, mengerti. Jadi dalam kasus orElse()ini myDefault()metode masih disebut, tetapi nilai kembali hanya tidak digunakan.
jbx
3
Pertanyaan ter-upgrade karena dari apa yang saya lihat salah paham atau lupa menggunakan orElseGet()dapat mengakibatkan beberapa bug serius: medium.com/alphadev-thoughts/…
softarn
Penjelasan yang baik dapat ditemukan di sini: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Jawaban:

172

Ambil dua skenario ini:

Optional<Foo> opt = ...
Foo x = opt.orElse( new Foo() );
Foo y = opt.orElseGet( Foo::new );

Jika opttidak mengandung nilai, keduanya memang setara. Tetapi jika opt memang mengandung nilai, berapa banyak Fooobjek yang akan dibuat?

Ps: tentu saja dalam contoh ini perbedaannya mungkin tidak akan terukur, tetapi jika Anda harus mendapatkan nilai default Anda dari layanan web jarak jauh misalnya, atau dari database, tiba-tiba menjadi sangat penting.

biziclop
sumber
22
Terima kasih atas klarifikasi kalian. Jadi perbedaannya halus tetapi signifikan. Dalam kasus kedua, itu tidak akan membuat Fooobjek baru , sedangkan dalam kasus pertama itu akan membuatnya, tetapi tidak menggunakannya jika ada nilai di dalam Optional.
jbx
5
@ jbx Ya, dan dalam contoh noddy saya ini bisa dibilang tidak membuat perbedaan nyata, tetapi jika Anda harus mendapatkan nilai default Anda dari layanan web jarak jauh misalnya, atau dari database, perbedaannya tiba-tiba menjadi sangat penting.
biziclop
2
@ jbx: Anda mencampur dua hal. Sudah ada pertanyaan di SO mengenai hasil benchmark aneh yang hanya disebabkan oleh tidak menggunakan hasil perhitungan. JVM dapat melakukan itu. Di sisi lain, System.out.println()ini bukan perhitungan tetapi pernyataan yang menghasilkan efek samping yang dapat diamati. Dan saya sudah mengatakan bahwa efek samping yang dapat diamati akan menghambat optimasi (aliran output konsol adalah sumber daya eksternal).
Holger
7
Itulah pertama kali saya melihat pertanyaan alih-alih jawaban diterima.
Kirill G.
4
" jika Anda harus mendapatkan nilai default Anda dari layanan web jarak jauh misalnya " ini persis skenario saya. Dalam kasus saya, opsionalnya adalah kueri, dan defaultnya jika tidak ada kueri adalah mengambil semua nilai ... ya, orElseGet mengurangi runtime operasi itu sebanyak 1000 kali.
scottysseus
109

Jawaban singkat:

  • orElse () akan selalu memanggil fungsi yang diberikan apakah Anda menginginkannya atau tidak, berapapun Optional.isPresent()nilainya
  • orElseGet () hanya akan memanggil fungsi yang diberikan saatOptional.isPresent() == false

Dalam kode nyata, Anda mungkin ingin mempertimbangkan pendekatan kedua saat sumber daya yang dibutuhkan mahal untuk didapatkan .

// Always get heavy resource
getResource(resourceId).orElse(getHeavyResource()); 

// Get heavy resource when required.
getResource(resourceId).orElseGet(() -> getHeavyResource()) 

Untuk lebih jelasnya, perhatikan contoh berikut dengan fungsi ini:

public Optional<String> findMyPhone(int phoneId)

Perbedaannya adalah sebagai berikut:

                           X : buyNewExpensivePhone() called

+——————————————————————————————————————————————————————————————————+——————————————+
|           Optional.isPresent()                                   | true | false |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElse(buyNewExpensivePhone())          |   X  |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElseGet(() -> buyNewExpensivePhone()) |      |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+

Ketika optional.isPresent() == false, tidak ada perbedaan antara dua cara. Namun, kapan optional.isPresent() == true,orElse() selalu memanggil fungsi berikutnya apakah Anda menginginkannya atau tidak.

Akhirnya, test case yang digunakan adalah sebagai berikut:

Hasil:

------------- Scenario 1 - orElse() --------------------
  1.1. Optional.isPresent() == true
    Going to a very far store to buy a new expensive phone
    Used phone: MyCheapPhone

  1.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

------------- Scenario 2 - orElseGet() --------------------
  2.1. Optional.isPresent() == true
    Used phone: MyCheapPhone

  2.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

Kode:

public class TestOptional {
    public Optional<String> findMyPhone(int phoneId) {
        return phoneId == 10
                ? Optional.of("MyCheapPhone")
                : Optional.empty();
    }

    public String buyNewExpensivePhone() {
        System.out.println("\tGoing to a very far store to buy a new expensive phone");
        return "NewExpensivePhone";
    }


    public static void main(String[] args) {
        TestOptional test = new TestOptional();
        String phone;
        System.out.println("------------- Scenario 1 - orElse() --------------------");
        System.out.println("  1.1. Optional.isPresent() == true");
        phone = test.findMyPhone(10).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  1.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("------------- Scenario 2 - orElseGet() --------------------");
        System.out.println("  2.1. Optional.isPresent() == true");
        // Can be written as test::buyNewExpensivePhone
        phone = test.findMyPhone(10).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  2.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");
    }
}
nxhoaf
sumber
Saya pikir Anda mungkin memiliki Kesalahan di gambar Anda, seharusnya ada tulisan "orElseGet" di sebelah kanan? Selain itu, contoh yang bagus.
Yalla T.
Ya kamu benar. Terima kasih :) Saya akan memperbaruinya dalam beberapa jam ke depan
nxhoaf
Untuk poin kedua, sepertinya seharusnya Optional.isPresent() == false(false, not true)
Manuel Jordan
Contoh yang bagus - tapi saya benar-benar tidak mengerti bagaimana Javadocs Optional.orElseyang menyatakan If a value is present, returns the value, otherwise returns otherdapat menyiratkan perilaku ini ...
Erik Finnman
Berdasarkan penjelasan Anda, bagi saya itu tampak seperti itu orElse()berperilaku mirip dengan finallydi try-catchekspresi. Apakah saya benar?
Mike B.
63

Saya sampai di sini untuk masalah yang Kudo sebutkan.

Saya berbagi pengalaman saya untuk orang lain.

orElse, atau orElseGet, itulah pertanyaannya:

static String B() {
    System.out.println("B()...");
    return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.of("A").orElse(B()));
    System.out.println(Optional.of("A").orElseGet(() -> B()));
}

cetakan

B()...
A
A

orElsemengevaluasi nilai B () secara interdependen dari nilai opsional. Karena orElseGetitu malas.

Jin Kwon
sumber
7
Itu bukan masalah'. Ini adalah fakta sederhana bahwa argumen untuk suatu metode dievaluasi sebelum pelaksanaan metode. Jika Anda meneruskan B()ke metode bernama orElse()atau abc()tidak ada bedanya, B()dievaluasi.
jbx
11
Masalahnya di sini adalah penamaan metode. The ormenyesatkan awalan pengembang (termasuk saya ketika saya bertanya masalah) dengan berpikir bahwa itu adalah operasi hubungan arus pendek, karena itulah yang kita digunakan untuk dalam kondisi boolean. Namun, bukan, itu hanya nama metode yang ada ordi awalannya, sehingga argumennya akan dievaluasi, terlepas dari apakah Optionalmembawa nilai atau tidak. Sangat disayangkan bahwa penamaan itu membingungkan, bukan karena kita dapat melakukan apa-apa.
jbx
37

Saya akan mengatakan perbedaan terbesar antara orElsedan orElseGetdatang ketika kami ingin mengevaluasi sesuatu untuk mendapatkan nilai baru dalam elsekondisi tersebut.

Pertimbangkan contoh sederhana ini -

// oldValue is String type field that can be NULL
String value;
if (oldValue != null) {
    value = oldValue;
} else {
    value = apicall().value;
}

Sekarang mari kita ubah contoh di atas untuk digunakan Optionalbersama dengan orElse,

// oldValue is Optional type field
String value = oldValue.orElse(apicall().value);

Sekarang mari kita ubah contoh di atas untuk digunakan Optionalbersama dengan orElseGet,

// oldValue is Optional type field
String value = oldValue.orElseGet(() -> apicall().value);

Ketika orElsedipanggil, apicall().valuedievaluasi dan diteruskan ke metode. Padahal, dalam hal orElseGetevaluasi hanya terjadi jika oldValuekosong. orElseGetmemungkinkan evaluasi malas.

devang
sumber
4
Saya membuang banyak waktu karena perilaku "aneh" ifElse () ini. Menurut saya masuk akal untuk memilih ifElseGet () daripada ifElse ()
Enrico Giurin
3

Contoh berikut harus menunjukkan perbedaan:

String destroyTheWorld() {
  // destroy the world logic
  return "successfully destroyed the world";
}

Optional<String> opt = Optional.empty();

// we're dead
opt.orElse(destroyTheWorld());

// we're safe    
opt.orElseGet(() -> destroyTheWorld());

Jawabannya muncul di dokumen juga.

public T orElseGet(Supplier<? extends T> other):

Kembalikan nilai jika ada, panggil orang lain dan kembalikan hasil doa itu.

Itu Supplier tidak akan dipanggil jika Optionalhadiah. sedangkan,

public T orElse(T other):

Kembalikan nilai jika ada, jika tidak kembalikan yang lain.

Jika otheradalah metode yang mengembalikan string, itu akan dipanggil, tetapi nilainya tidak akan dikembalikan jika Optionalada.

Maroun
sumber
3

Perbedaannya cukup halus dan jika Anda tidak terlalu memperhatikan maka Anda akan tetap menggunakannya dengan cara yang salah.

Cara terbaik untuk memahami perbedaan antara orElse()dan orElseGet()adalah bahwa orElse()akan selalu dieksekusi jika Optional<T>ini nol atau tidak , Tapi orElseGet()hanya akan dieksekusi ketika Optional<T>adalah nol .

Arti kamus orElse adalah : - mengeksekusi bagian ketika sesuatu tidak ada, tetapi di sini bertentangan, lihat contoh di bawah ini:

    Optional<String> nonEmptyOptional = Optional.of("Vishwa Ratna");
    String value = nonEmptyOptional.orElse(iAmStillExecuted());

    public static String iAmStillExecuted(){
    System.out.println("nonEmptyOptional is not NULL,still I am being executed");
    return "I got executed";
    }

Output: nonEmptyOptional bukan NULL, masih saya sedang dieksekusi


    Optional<String> emptyOptional = Optional.ofNullable(null);
    String value = emptyOptional.orElse(iAmStillExecuted());
    public static String iAmStillExecuted(){
    System.out.println("emptyOptional is NULL, I am being executed, it is normal as 
    per dictionary");
    return "I got executed";
    }

Output : emptyOptional adalah NULL, saya sedang dieksekusi, itu normal sesuai kamus

Sebab orElseGet(), Metode berjalan sesuai arti kamus, Bagian orElseGet()akan dieksekusi hanya ketika Opsional adalah nol .

Benchmark :

+--------------------+------+-----+------------+-------------+-------+
| Benchmark          | Mode | Cnt | Score      | Error       | Units |
+--------------------+------+-----+------------+-------------+-------+
| orElseBenchmark    | avgt | 20  | 60934.425  | ± 15115.599 | ns/op |
+--------------------+------+-----+------------+-------------+-------+
| orElseGetBenchmark | avgt | 20  | 3.798      | ± 0.030     | ns/op |
+--------------------+------+-----+------------+-------------+-------+

Keterangan : orElseGet()jelas mengungguli orElse()untuk contoh khusus kami.

Semoga ini menghilangkan keraguan orang-orang seperti saya yang menginginkan contoh dasar yang sangat mendasar :)

Vishwa Ratna
sumber
2

Pertama-tama periksa deklarasi kedua metode.

1) OrElse: Jalankan logika dan berikan hasil sebagai argumen.

public T orElse(T other) {    
 return value != null ? value : other;
}

2) OrElseGet: Jalankan logika jika nilai di dalam opsional adalah nol

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

Beberapa penjelasan tentang deklarasi di atas: Argumen "Optional.orElse" selalu dieksekusi terlepas dari nilai objek dalam opsional (nol, kosong atau dengan nilai). Selalu pertimbangkan hal yang disebutkan di atas dalam pikiran saat menggunakan "Optional.orElse", jika tidak, gunakan "Optional.orElse" bisa sangat berisiko dalam situasi berikut.

Risiko-1) Masalah Pencatatan: Jika konten di dalam orElse berisi pernyataan log apa pun: Dalam hal ini, Anda pada akhirnya akan mencatatnya setiap waktu.

Optional.of(getModel())
   .map(x -> {
      //some logic
   })
  .orElse(getDefaultAndLogError());

getDefaultAndLogError() {
  log.error("No Data found, Returning default");
  return defaultValue;
}

Risiko-2) Masalah Kinerja: Jika konten di dalam orElse padat waktu: Konten intensif waktu dapat berupa operasi DB panggilan, panggilan API, pembacaan file. Jika kami menaruh konten seperti itu di orElse (), sistem pada akhirnya akan mengeksekusi kode yang tidak digunakan.

Optional.of(getModel())
   .map(x -> //some logic)
   .orElse(getDefaultFromDb());

getDefaultFromDb() {
   return dataBaseServe.getDefaultValue(); //api call, db call.
}

Risiko-3) Keadaan Ilegal atau Masalah Bug: Jika konten di dalam orElse mengubah beberapa status objek: Kami mungkin menggunakan objek yang sama di tempat lain, misalkan dalam fungsi Optional.map dan itu dapat menempatkan kami dalam bug kritis.

List<Model> list = new ArrayList<>();
Optional.of(getModel())
  .map(x -> {
  })
  .orElse(get(list));

get(List < String > list) {
   log.error("No Data found, Returning default");
   list.add(defaultValue);
   return defaultValue;
}

Lalu, Kapan kita bisa menggunakan OrElse ()? Lebih suka menggunakan orElse ketika nilai default adalah beberapa objek konstan, enum. Dalam semua kasus di atas kita bisa menggunakan Optional.orElseGet () (yang hanya dijalankan ketika Opsional berisi nilai yang tidak kosong) alih-alih Optional.orElse (). Mengapa?? Di orElse, kami meneruskan nilai hasil default, tetapi di orElseGet kami melewati Pemasok dan metode Pemasok hanya dijalankan jika nilai dalam Opsional adalah nol.

Pengambilan kunci dari ini:

  1. Jangan gunakan "Optional.orElse" jika berisi pernyataan log.
  2. Jangan gunakan "Optional.orElse" jika mengandung logika waktu-intensif.
  3. Jangan gunakan "Optional.orElse" jika itu bermutasi beberapa keadaan objek.
  4. Gunakan "Optional.orElse" jika kita harus mengembalikan konstanta, enum.
  5. Pilih "Optional.orElseGet" dalam situasi yang disebutkan dalam poin 1,2 dan 3.

Saya telah menjelaskan hal ini pada poin-2 ( “Optional.map/Optional.orElse”! = “If / else” ) blog medium saya. Gunakan Java8 sebagai programmer bukan sebagai coder

Piyush N
sumber
0

Mempertimbangkan kode berikut:

import java.util.Optional;

// one class needs to have a main() method
public class Test
{
  public String orelesMethod() {
    System.out.println("in the Method");
    return "hello";
  }

  public void test() {
    String value;
    value = Optional.<String>ofNullable("test").orElseGet(this::orelesMethod);
    System.out.println(value); 

    value = Optional.<String>ofNullable("test").orElse(orelesMethod());
    System.out.println(value); 
  }

  // arguments are passed using the text field below this editor
  public static void main(String[] args)
  {
    Test test = new Test();

    test.test();
  }
}

jika kita mendapatkan valuecara ini: Optional.<String>ofNullable(null), tidak ada perbedaan antara orElseGet () dan OrElse (), tetapi jika kita mendapatkan valuecara ini: Optional.<String>ofNullable("test"), orelesMethod()di orElseGet()tidak akan dipanggil tetapi dalam orElse()itu akan disebut

ratzip
sumber