Tùy chọn orElse Tùy chọn trong Java


137

Tôi đã làm việc với loại Tùy chọn mới trong Java 8 và tôi đã bắt gặp một hoạt động có vẻ như là một hoạt động phổ biến không được hỗ trợ về mặt chức năng: "orElseOptional"

Hãy xem xét các mẫu sau:

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

Có nhiều hình thức của mẫu này, nhưng nó có ý định muốn "orElse" trên một tùy chọn có chức năng tạo ra một tùy chọn mới, chỉ được gọi nếu hiện tại không tồn tại.

Việc thực hiện sẽ như thế này:

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

Tôi tò mò liệu có lý do nào mà một phương pháp như vậy không tồn tại hay không, nếu tôi chỉ sử dụng Tùy chọn theo cách không có chủ ý, và những cách khác mà mọi người đã đưa ra để giải quyết trường hợp này.

Tôi nên nói rằng tôi nghĩ rằng các giải pháp liên quan đến các lớp / phương thức tiện ích tùy chỉnh không thanh lịch vì những người làm việc với mã của tôi sẽ không nhất thiết biết chúng tồn tại.

Ngoài ra, nếu có ai biết, liệu một phương thức như vậy có được đưa vào JDK 9 không, và tôi có thể đề xuất một phương thức như vậy ở đâu? Điều này có vẻ như là một thiếu sót khá rõ ràng đối với API đối với tôi.


13
Xem vấn đề này . Để làm rõ: điều này đã có trong Java 9 - nếu không có trong bản cập nhật tương lai của Java 8.
Obicere

Nó là! Cảm ơn, đã không tìm thấy điều đó trong tìm kiếm của tôi.
Yona Appletree 2/03/2015

2
@Obicere Vấn đề đó không áp dụng ở đây vì đó là về hành vi trên Tùy chọn trống, không phải về kết quả thay thế . Tùy chọn đã có sẵn orElseGet()cho những gì OP cần, chỉ có điều nó không tạo ra cú pháp xếp tầng đẹp.
Marko Topolnik


1
Các hướng dẫn tuyệt vời về Java Tùy chọn: codeflex.co/java-optional-no-more-nullpulumexception
John Detroit

Câu trả lời:


86

Đây là một phần của JDK 9 ở dạng or, trong đó có một Supplier<Optional<T>>. Ví dụ của bạn sẽ là:

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

Để biết chi tiết, xem Javadoc hoặc bài đăng này tôi đã viết.


Đẹp. Sự bổ sung đó phải là một năm tuổi và tôi đã không thông báo. Liên quan đến câu hỏi trong blog của bạn, việc thay đổi loại trả về sẽ phá vỡ tính tương thích nhị phân, vì các hướng dẫn gọi bằng mã byte đề cập đến chữ ký đầy đủ, bao gồm loại trả về, do đó không có cơ hội thay đổi loại trả về ifPresent. Nhưng dù sao, tôi nghĩ rằng cái tên ifPresentnày không phải là một cái tốt. Đối với tất cả các phương pháp khác không mang “khác” trong tên (như map, filter, flatMap), nó được ngụ ý rằng họ không làm gì cả nếu không có giá trị là hiện nay, vậy tại sao nên ifPresent...
Holger

Vì vậy, việc thêm một Optional<T> perform(Consumer<T> c)phương thức để cho phép xâu chuỗi perform(x).orElseDo(y)( orElseDothay thế cho đề xuất của bạn ifEmpty, phải nhất quán trong elsetên của tất cả các phương thức có thể làm điều gì đó cho các giá trị vắng mặt). Bạn có thể bắt chước performtrong Java 9 thông qua stream().peek(x).findFirst()việc đó là lạm dụng API và vẫn không có cách nào để thực thi Runnablemà không chỉ định Consumercùng một lúc
Holger

65

Cách tiếp cận dịch vụ dùng thử sạch nhất của Tiếp cận được cung cấp API hiện tại là:

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

Khía cạnh quan trọng không phải là chuỗi hoạt động (không đổi) mà bạn phải viết một lần mà là dễ dàng thêm dịch vụ khác (hoặc sửa đổi danh sách dịch vụ nói chung). Ở đây, thêm hoặc loại bỏ một ()->serviceX(args)là đủ.

Do sự đánh giá lười biếng của các luồng, sẽ không có dịch vụ nào được gọi nếu một dịch vụ trước trả về không trống Optional.


13
chỉ sử dụng nó trong một dự án, cảm ơn chúa, chúng tôi không thực hiện đánh giá mã.
Ilya Smagin

3
Nó sạch sẽ hơn nhiều so với một chuỗi "orElseGet", nhưng nó cũng khó đọc hơn rất nhiều.
slartidan

4
Điều này chắc chắn là đúng ... nhưng thành thật mà nói, tôi phải mất một giây để phân tích nó và tôi không tự tin rằng nó đúng. Nhắc bạn, tôi nhận ra rằng ví dụ này là, nhưng tôi có thể tưởng tượng một thay đổi nhỏ sẽ khiến nó không được đánh giá một cách lười biếng hoặc có một số lỗi khác không thể phân biệt được trong nháy mắt. Đối với tôi, điều này rơi vào danh mục chức năng tiện ích.
Yona Appletree

3
Tôi tự hỏi nếu chuyển đổi .map(Optional::get)với .findFirst()sẽ giúp "đọc" dễ dàng hơn, ví dụ: .filter(Optional::isPresent).findFirst().map(Optional::get)có thể "đọc" như "tìm phần tử đầu tiên trong luồng mà Tùy chọn :: isPftime là đúng và sau đó làm phẳng nó bằng cách áp dụng Tùy chọn :: get"?
schatten

3
Thật buồn cười, tôi đã đăng một giải pháp rất giống nhau cho một câu hỏi tương tự vài tháng trước. Đây là lần đầu tiên tôi bắt gặp điều này.
shmosel

34

Nó không đẹp, nhưng nó sẽ hoạt động:

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)là một mẫu khá tiện dụng để sử dụng với Optional. Nó có nghĩa là "Nếu cái này Optionalchứa giá trị v, hãy cho tôi func(v), nếu không thì cho tôi sup.get()".

Trong trường hợp này, chúng tôi gọi serviceA(args)và nhận được một Optional<Result>. Nếu Optionalgiá trị đó chứa giá trị v, chúng tôi muốn nhận Optional.of(v), nhưng nếu nó trống, chúng tôi muốn nhận serviceB(args). Rửa sạch lặp lại với nhiều lựa chọn thay thế.

Các ứng dụng khác của mẫu này là

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)

1
huh, khi tôi sử dụng chiến lược này, nhật thực nói: "Phương thức orElseGet (Nhà cung cấp <? extends String>) trong loại Tùy chọn <String> không áp dụng cho các đối số (() -> {})" Có vẻ như không để xem xét trả lại Tùy chọn một chiến lược hợp lệ, trên Chuỗi ??
chrismarx

@chrismarx () -> {}không trả lại một Optional. Bạn đang cố gắng để thực hiện?
Misha

1
Tôi chỉ đang cố gắng làm theo ví dụ. Xâu chuỗi các cuộc gọi bản đồ không hoạt động. Các dịch vụ của tôi trả về chuỗi và tất nhiên không có tùy chọn .map () khả dụng sau đó
chrismarx

1
Sự thay thế tuyệt vời có thể đọc được cho sự ra mắt or(Supplier<Optional<T>>)của Java 9
David M.

1
@Sheepy bạn nhầm rồi. .map()trên một sản phẩm nào Optionalsẽ sản xuất một sản phẩm nào Optional.
Misha

27

Có lẽ đây là những gì bạn đang theo đuổi: Nhận giá trị từ Tùy chọn này hoặc Tùy chọn khác

Nếu không, bạn có thể muốn có một cái nhìn Optional.orElseGet. Đây là một ví dụ về những gì tôi nghĩ rằng bạn đang theo đuổi:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

2
Đây là điều mà các thiên tài thường làm. Đánh giá tùy chọn là không thể và gói nó ofNullablelà điều tuyệt vời nhất tôi từng thấy.
Jin Kwon

5

Giả sử bạn vẫn còn trên JDK8, có một số tùy chọn.

Tùy chọn # 1: tạo phương thức trợ giúp của riêng bạn

Ví dụ:

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

Vì vậy, bạn có thể làm:

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

Tùy chọn # 2: sử dụng thư viện

Ví dụ: Tùy chọn của google guava hỗ trợ một or()hoạt động đúng (giống như JDK9), ví dụ:

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

(Trường hợp mỗi dịch vụ trả về com.google.common.base.Optional, thay vì java.util.Optional).


Tôi không tìm thấy Optional<T>.or(Supplier<Optional<T>>)trong các tài liệu ổi. Bạn có một liên kết cho điều đó?
Tamas Hegedus

.orElseGet trả về T nhưng Tùy chọn :: trả về trống Tùy chọn <T>. Tùy chọn.ofNullable (........ orElse (null)) có hiệu ứng mong muốn như được mô tả bởi @aioobe.
Miguel Pereira

@TamasHegedus Ý bạn là trong lựa chọn số 1? Đó là một triển khai tùy chỉnh trên snipped ở trên nó. ps: xin lỗi vì đã trả lời trễ
Sheepy

@MiguelPereira .orElseGet trong trường hợp này trả về Tùy chọn <T> vì nó hoạt động trên Tùy chọn <T >> Tùy chọn
Sheepy

2

Điều này có vẻ phù hợp với việc khớp mẫu và giao diện Tùy chọn truyền thống hơn với các triển khai Một số và Không (chẳng hạn như trong Javaslang , FunctionalJava ) hoặc lười biếng Có thể thực hiện trong cyclops- Reac. Tôi là tác giả của thư viện này.

Với cyclops-Reac, bạn cũng có thể sử dụng khớp mẫu kết cấu trên các loại JDK. Đối với Tùy chọn, bạn có thể khớp với các trường hợp hiện tại và vắng mặt thông qua mẫu khách truy cập . nó sẽ trông giống như thế này -

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

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
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.