Sự khác biệt giữa `Options.orElse ()` và `Options.orElseGet ()`


206

Tôi đang cố gắng để hiểu sự khác biệt giữa Optional<T>.orElse()Optional<T>.orElseGet()phương pháp.

Mô tả cho orElse()phương thức là "Trả về giá trị nếu có, nếu không thì trả về khác."

Trong khi, mô tả cho orElseGet()phương thức là "Trả về giá trị nếu có, nếu không thì gọi khác và trả về kết quả của lệnh gọi đó".

Các orElseGet()phương pháp có một giao diện chức năng cung cấp, trong đó chủ yếu không có bất kỳ thông số và lợi nhuận T.

Trong tình huống nào bạn sẽ cần sử dụng orElseGet()? Nếu bạn có một phương pháp T myDefault()tại sao bạn không làm optional.orElse(myDefault())chứ không phải optional.orElseGet(() -> myDefault())?

Dường như không orElseGet()trì hoãn việc thực hiện biểu thức lambda vào một thời gian sau đó hoặc một cái gì đó, vậy ý ​​nghĩa của nó là gì? (Tôi đã nghĩ rằng nó sẽ hữu ích hơn nếu nó trả lại một cách an toàn hơn Optional<T>get()không bao giờ ném a NoSuchElementExceptionisPresent()luôn trả về đúng ... nhưng rõ ràng là không, nó chỉ trả về Tnhư thế orElse()).

Có một số khác biệt tôi đang thiếu?


7
Lý do là khi bạn sử dụng orElseGetnó chỉ gọi nhà cung cấp nếu không có giá trị.
Alex Salauyou

9
À, hiểu rồi Vì vậy, trong trường hợp orElse()các myDefault()phương pháp vẫn được gọi, nhưng giá trị trả về của nó chỉ là không được sử dụng.
jbx

3
Câu hỏi được nêu lên bởi vì từ những gì tôi đã thấy sự hiểu lầm hoặc đơn giản là quên sử dụng orElseGet()có thể dẫn đến một số lỗi nghiêm trọng: Medium.com/alphadev- Dùts / Lỗi
softarn

Một lời giải thích tốt được tìm thấy ở đây: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Câu trả lời:


172

Lấy hai kịch bản sau:

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

Nếu optkhông chứa giá trị, cả hai thực sự tương đương nhau. Nhưng nếu opt chứa một giá trị, có bao nhiêu Foođối tượng sẽ được tạo?

Ps: tất nhiên trong ví dụ này, sự khác biệt có lẽ sẽ không thể đo lường được, nhưng nếu bạn phải lấy giá trị mặc định của mình từ một dịch vụ web từ xa, hoặc từ cơ sở dữ liệu, nó đột nhiên trở nên rất quan trọng.


22
Cảm ơn các bạn đã làm rõ. Vì vậy, sự khác biệt là tinh tế nhưng đáng kể. Trong trường hợp thứ hai, nó sẽ không tạo một Foođối tượng mới , trong trường hợp đầu tiên, nó sẽ tạo ra nó, nhưng không sử dụng nó nếu có một giá trị bên trong Optional.
jbx

5
@jbx Có, và trong ví dụ gật đầu của tôi, có thể cho rằng nó không tạo ra sự khác biệt thực sự nào, nhưng nếu bạn phải lấy giá trị mặc định của mình từ một dịch vụ web từ xa, hoặc từ cơ sở dữ liệu, sự khác biệt đột nhiên trở nên rất quan trọng.
biziclop

2
@jbx: bạn đang trộn hai thứ lên. Đã có những câu hỏi về SO liên quan đến kết quả điểm chuẩn lạ mà nguyên nhân đơn giản là do không sử dụng kết quả tính toán. JVM có thể làm điều đó. Mặt khác, System.out.println()không tính toán nhưng một tuyên bố sản xuất một tác dụng phụ quan sát được. Và tôi đã nói rằng các hiệu ứng phụ có thể quan sát được sẽ cản trở tối ưu hóa (luồng đầu ra của bàn điều khiển một tài nguyên bên ngoài).
Holger

7
Đó là lần đầu tiên tôi thấy một câu hỏi thay vì một câu trả lời được chấp nhận.
Kirill G.

4
" Nếu bạn phải lấy giá trị mặc định của mình từ một dịch vụ web từ xa chẳng hạn " thì đây chính xác là kịch bản của tôi. Trong trường hợp của tôi, tùy chọn là một truy vấn và mặc định khi không có truy vấn là tìm nạp tất cả các giá trị ... vâng, orElseGet đã giảm 1000 lần thời gian chạy của hoạt động đó.
scottysseus

109

Câu trả lời ngắn:

  • orElse () sẽ luôn gọi hàm đã cho dù bạn muốn hay không, bất kể Optional.isPresent()giá trị là bao nhiêu
  • orElseGet () sẽ chỉ gọi hàm đã cho khiOptional.isPresent() == false

Trong mã thực, bạn có thể muốn xem xét cách tiếp cận thứ hai khi tài nguyên cần thiết đắt tiền để có được .

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

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

Để biết thêm chi tiết, hãy xem xét ví dụ sau với chức năng này:

public Optional<String> findMyPhone(int phoneId)

Sự khác biệt như sau:

                           X : buyNewExpensivePhone() called

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

Khi optional.isPresent() == false, không có sự khác biệt giữa hai cách. Tuy nhiên, khi nào optional.isPresent() == true, orElse()luôn gọi hàm tiếp theo cho dù bạn có muốn hay không.

Cuối cùng, trường hợp thử nghiệm được sử dụng như sau:

Kết quả:

------------- 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

Mã số:

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");
    }
}

Tôi nghĩ rằng bạn có thể có một lỗi trong hình ảnh của bạn, nó sẽ nói "orElseGet" bên phải? Bên cạnh đó, ví dụ tuyệt vời.
Yalla T.

Ư, bạn đung. Cảm ơn bạn :) Tôi sẽ cập nhật nó trong vài giờ tới
nxhoaf

Đối với điểm đạn thứ hai, dường như nên Optional.isPresent() == falsethay thế (sai, không đúng)
Manuel Jordan

Ví dụ tuyệt vời - nhưng tôi thực sự không hiểu làm thế nào Javadocs Optional.orElsemà các quốc gia If a value is present, returns the value, otherwise returns othercó thể ám chỉ hành vi này ...
Erik Finnman

Dựa trên lời giải thích của bạn, đối với tôi có vẻ như nó orElse()hoạt động tương tự như finallytrong try-catchbiểu hiện. Tôi có đúng không?
Mike B.

63

Tôi đã đến đây cho vấn đề Kudo đã đề cập.

Tôi đang chia sẻ kinh nghiệm của mình cho người khác.

orElsehoặc orElseGet, đó là câu hỏi:

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()));
}

in

B()...
A
A

orElseđánh giá giá trị của B () phụ thuộc lẫn nhau vào giá trị của tùy chọn. Như vậy, orElseGetlà lười biếng.


7
Nó không phải là vấn đề'. Đó là một thực tế đơn giản rằng đối số cho một phương thức được đánh giá trước khi thực hiện phương thức. Nếu bạn chuyển B()đến một phương thức có tên orElse()hoặc abc()nó không tạo ra sự khác biệt nào, hãy B()đánh giá.
jbx

11
Vấn đề ở đây thực sự là việc đặt tên cho các phương thức. Các ornhà phát triển gây nhầm lẵn tiền tố (bao gồm cả bản thân mình khi tôi hỏi những vấn đề) vào suy nghĩ rằng nó là một hoạt động ngắn mạch, bởi vì đó là những gì chúng tôi được sử dụng để trong điều kiện boolean. Tuy nhiên, thực tế không phải vậy, nó chỉ là một tên phương thức có ortrong tiền tố của nó, vì vậy các đối số của nó sẽ được đánh giá, bất kể việc Optionalcó mang giá trị hay không. Thật không may là việc đặt tên là khó hiểu, không phải là chúng ta có thể làm bất cứ điều gì về nó.
jbx

37

Tôi muốn nói sự khác biệt lớn nhất giữa orElseorElseGetđến khi chúng ta muốn đánh giá một cái gì đó để có được giá trị mới trong elseđiều kiện.

Hãy xem xét ví dụ đơn giản này -

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

Bây giờ hãy chuyển đổi ví dụ trên sang sử dụng Optionalcùng với orElse,

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

Bây giờ hãy chuyển đổi ví dụ trên sang sử dụng Optionalcùng với orElseGet,

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

Khi orElseđược gọi, apicall().valueđược đánh giá và truyền cho phương thức. Trong khi đó, trong trường hợp orElseGetđánh giá chỉ xảy ra nếu oldValuetrống. orElseGetcho phép đánh giá lười biếng.


4
Tôi đã lãng phí rất nhiều lần vì hành vi "lạ" này của ifElse (). Tôi muốn nói rằng sẽ hợp lý hơn nếu thích ifElseGet () hơn ifElse ()
Enrico Giurin

3

Ví dụ sau đây sẽ chứng minh sự khác biệt:

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());

Câu trả lời cũng xuất hiện trong các tài liệu.

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

Trả về giá trị nếu có, nếu không thì gọi khác và trả về kết quả của lệnh đó.

Các Supplier sẽ không được viện dẫn nếu Optionalnhững món quà. trong khi,

public T orElse(T other):

Trả lại giá trị nếu có, nếu không trả lại khác.

Nếu otherlà một phương thức trả về một chuỗi, nó sẽ được gọi, nhưng giá trị của nó sẽ không được trả về trong trường hợp Optionaltồn tại.


3

Sự khác biệt là khá tinh tế và nếu bạn không chú ý nhiều thì bạn sẽ giữ nó sử dụng sai cách.

Cách tốt nhất để hiểu sự khác biệt giữa orElse()orElseGet()được rằng orElse()sẽ luôn luôn được thực thi nếu Optional<T>rỗng hay không , Nhưng orElseGet()sẽ chỉ được thực hiện khi Optional<T>rỗng .

Ý nghĩa từ điển của orElse: - thực hiện phần khi không có thứ gì đó, nhưng ở đây nó mâu thuẫn, xem ví dụ dưới đây:

    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";
    }

Đầu ra: nonEmptyOptional không phải là NULL, tôi vẫn đang được thực thi


    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";
    }

Đầu ra : blankOptional là NULL, tôi đang được thực thi, nó là bình thường theo từ điển

Đối với orElseGet(), Phương thức đi theo nghĩa của từ điển, Phần orElseGet()sẽ chỉ được thực hiện khi Tùy chọn là null .

Điểm chuẩn :

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

Lưu ý : orElseGet()rõ ràng là tốt hơn so orElse()với ví dụ cụ thể của chúng tôi.

Hy vọng nó sẽ xóa tan nghi ngờ của những người như tôi, những người muốn có ví dụ rất cơ bản :)


2

Trước hết kiểm tra khai báo của cả hai phương pháp.

1) OrElse: Thực thi logic và truyền kết quả làm đối số.

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

2) OrElseGet: Thực thi logic nếu giá trị bên trong tùy chọn là null

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

Một số giải thích về khai báo trên: Đối số của Tùy chọn.orElse, luôn luôn được thực thi bất kể giá trị của đối tượng trong tùy chọn (null, rỗng hoặc có giá trị). Luôn luôn xem xét các điểm đã đề cập ở trên trong khi sử dụng tùy chọn của Cameron.orElse, nếu không thì sử dụng tùy chọn của Cameron tùy chọn.orElse có thể rất rủi ro trong tình huống sau.

Rủi ro-1) Vấn đề ghi nhật ký: Nếu nội dung bên trong orElse chứa bất kỳ tuyên bố nhật ký nào: Trong trường hợp này, bạn sẽ kết thúc việc đăng nhập mỗi lần.

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

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

Rủi ro-2) Vấn đề về hiệu suất: Nếu nội dung bên trong orElse tốn nhiều thời gian: Nội dung đòi hỏi nhiều thời gian có thể là bất kỳ hoạt động i / o cuộc gọi DB, cuộc gọi API, đọc tệp. Nếu chúng tôi đưa nội dung đó vào orElse (), hệ thống sẽ kết thúc thực thi một mã không sử dụng.

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

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

Rủi ro-3) Trạng thái bất hợp pháp hoặc Sự cố lỗi: Nếu nội dung bên trong orElse đang làm biến đổi một số trạng thái đối tượng: Chúng ta có thể đang sử dụng cùng một đối tượng ở một nơi khác, giả sử bên trong hàm Options.map và nó có thể khiến chúng ta gặp phải một lỗi nghiêm trọng.

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;
}

Sau đó, khi nào chúng ta có thể đi với orElse ()? Thích sử dụng orElse khi giá trị mặc định là một số đối tượng không đổi, enum. Trong tất cả các trường hợp trên, chúng ta có thể đi với Options.orElseGet () (chỉ thực thi khi Tùy chọn chứa giá trị không trống) thay vì Tùy chọn.orElse (). Tại sao?? Trong orElse, chúng tôi chuyển giá trị kết quả mặc định, nhưng trong orElseGet, chúng tôi vượt qua Nhà cung cấp và phương thức của Nhà cung cấp chỉ thực hiện nếu giá trị trong Tùy chọn là null.

Những điểm chính của việc này:

  1. Không sử dụng Tùy chọn.orElse, nếu nó có chứa bất kỳ câu lệnh đăng nhập nào.
  2. Không sử dụng tùy chọn của Cameron.orElse nếu nó chứa logic tốn nhiều thời gian.
  3. Không sử dụng Tùy chọn.orElse, nếu nó đang làm thay đổi một số trạng thái đối tượng.
  4. Sử dụng dịch vụ Tùy chọn.orElse nếu chúng ta phải trả về một hằng số, enum.
  5. Ưu tiên tùy chọn.orElseGetv trong các tình huống được đề cập trong 1,2 và 3 điểm.

Tôi đã giải thích điều này ở điểm 2 ( Tùy chọn chiều.map /Optional.orElse)! = Nếu if / other khác ) blog trung bình của tôi. Sử dụng Java8 như một lập trình viên chứ không phải là một lập trình viên


0

Xem xét các mã sau đây:

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();
  }
}

nếu chúng ta có được valuetheo cách này: Optional.<String>ofNullable(null), không có sự khác biệt giữa orElseGet () và OrElse (), nhưng nếu chúng ta có được valuetheo cách này: Optional.<String>ofNullable("test"), orelesMethod()trong orElseGet()sẽ không được gọi nhưng orElse()nó sẽ được gọi là

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.