Kiểu chức năng của Java 8. Tùy chọn.ifPftime và if-not-Present?


274

Trong Java 8, tôi muốn làm một cái gì đó cho một Optionalđối tượng nếu nó có mặt và làm một điều khác nếu nó không có mặt.

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

Đây không phải là một "phong cách chức năng", mặc dù.

Optionalcó một ifPresent()phương thức, nhưng tôi không thể xâu chuỗi một orElse()phương thức.

Vì vậy, tôi không thể viết:

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

Trả lời @assylias, tôi không nghĩ sẽ Optional.map()hoạt động cho trường hợp sau:

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

Trong trường hợp này, khi optcó mặt, tôi cập nhật thuộc tính của nó và lưu vào cơ sở dữ liệu. Khi nó không có sẵn, tôi tạo một cái mới objvà lưu vào cơ sở dữ liệu.

Lưu ý trong hai lambdas tôi phải trở lại null.

Nhưng khi optcó mặt, cả hai lambdas sẽ bị xử tử. objsẽ được cập nhật và một đối tượng mới sẽ được lưu vào cơ sở dữ liệu. Điều này là do return nulltrong lambda đầu tiên. Và orElseGet()sẽ tiếp tục thực thi.


53
Sử dụng mẫu đầu tiên của bạn. Nó là đẹp .
Sotirios Delimanolis

3
Tôi khuyên bạn nên ngừng ép buộc một số hành vi nhất định khi sử dụng API không được thiết kế cho hành vi đó. Ví dụ rfirst của bạn có vẻ tốt đối với tôi ngoài một số nhận xét phong cách nhỏ, nhưng những điều đó bị mờ đục.
skiwi

4
@smallufo: thay thế return null;bằng return o;(cả hai). Tuy nhiên, tôi có cảm giác mạnh mẽ rằng bạn đang làm việc không đúng chỗ. Bạn nên làm việc tại trang web sản xuất ra điều đó Optional. Tại nơi đó nên có một cách để thực hiện các hoạt động mong muốn mà không cần trung gian Optional.
Holger

10
Java 9 triển khai một giải pháp cho vấn đề của bạn: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
Tôi nghĩ rằng lý do này không thể được thực hiện dễ dàng là có mục đích. Tùy chọn không nên thực hiện kiểm soát dòng chảy, mà là chuyển đổi giá trị. Tôi biết những ifPresentmâu thuẫn này. Tất cả các phương pháp khác đề cập đến giá trị và không hành động.
AlikElzin-kilaka

Câu trả lời:


109

Đối với tôi câu trả lời của @Dane White là OK, đầu tiên tôi không thích sử dụng Runnable nhưng tôi không thể tìm thấy bất kỳ lựa chọn thay thế nào, ở đây một triển khai khác tôi thích hơn

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

Sau đó :

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

Cập nhật 1:

giải pháp trên cho cách phát triển truyền thống khi bạn có giá trị và muốn xử lý nó, nhưng nếu tôi muốn xác định chức năng và thực thi thì sẽ thế nào, hãy kiểm tra nâng cao bên dưới;

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

Sau đó có thể được sử dụng như:

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

Trong mã mới này, bạn có 3 điều:

  1. có thể xác định chức năng trước khi tồn tại của đối tượng dễ dàng.
  2. không tạo sự điều chỉnh đối tượng cho mỗi Tùy chọn, chỉ có một, bạn có ít bộ nhớ hơn nên ít GC hơn.
  3. nó đang thực hiện cho người tiêu dùng để sử dụng tốt hơn với các thành phần khác.

bằng cách bây giờ tên của nó được mô tả nhiều hơn nó thực sự là người tiêu dùng>


3
Nên sử dụng Tùy
chọn.ofNullable

2
Bạn cần sử dụng ofNullable nếu bạn không chắc chắn giá trị bạn sẽ sử dụng có null hay không và không cần phải đối mặt với NPE, và trong trường hợp bạn chắc chắn rằng nó không null hoặc bạn không quan tâm nếu nhận được NPE.
Bassem Reda Zohdy

1
Tôi nghĩ rằng lớp OptionsConsumer trông tốt hơn so với if / other trong mã. Cảm ơn! :)
witek1902

207

Nếu bạn đang sử dụng Java 9+, bạn có thể sử dụng ifPresentOrElse()phương thức:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

3
Đẹp vì nó gần như sạch sẽ như khớp mẫu trong Scala
sscarduzio

Hai lambda như thế là khá xấu xí. Tôi nghĩ rằng nếu / khác là sạch hơn nhiều cho những trường hợp này.
john16384

1
@ john16384 OK, nếu bạn thấy nó xấu, thì tôi sẽ xóa câu trả lời của tôi (không).
ZhekaKozlov

Điều này rất hay nhưng câu hỏi được dành riêng cho JDK8 vì ifPftimeOrElse không có sẵn.
hreinn

81

Giới thiệu Java 9

ifPftimeOrElse nếu có một giá trị, thực hiện hành động đã cho với giá trị, nếu không thì thực hiện hành động dựa trên trống đã cho.

Xem tùy chọn tuyệt vời trong bảng cheat Java 8 .

Nó cung cấp tất cả các câu trả lời cho hầu hết các trường hợp sử dụng.

Tóm tắt ngắn gọn dưới đây

ifPftime () - làm một cái gì đó khi Tùy chọn được đặt

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

bộ lọc () - từ chối (lọc ra) một số giá trị tùy chọn.

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

map () - biến đổi giá trị nếu có

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

orElse () / orElseGet () - biến trống Tùy chọn thành T mặc định

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

orElseThrow () - ném ngoại lệ một cách lười biếng vào tùy chọn trống

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

66
Điều này không thực sự trả lời câu hỏi của OP. Nó trả lời rất nhiều công dụng phổ biến nhưng không phải là những gì OP yêu cầu.
Thuyền trưởng Man

1
@CaptainMan thực sự làm điều đó; biểu thức opt.map ("Found"). orElse ("không tìm thấy") điền vào hóa đơn.
Matt

4
@Matt không, OP đặc biệt yêu cầu các hành động mà anh ta thực hiện khi tùy chọn không có / không có, không trả về giá trị khi có hoặc không. OP thậm chí còn đề cập đến một cái gì đó tương tự trong câu hỏi bằng cách sử dụng orElseGet giải thích lý do tại sao nó không hoạt động.
Thuyền trưởng Man

2
@CaptainMan Tôi thấy quan điểm của bạn. Tôi nghĩ rằng anh ta có thể làm cho nó hoạt động nếu anh ta không trả về null từ map, nhưng hơi lạ khi yêu cầu một giải pháp chức năng để bạn có thể gọi DAO. Dường như với tôi sẽ có ý nghĩa hơn khi trả về đối tượng được cập nhật / mới từ map.orElsekhối này và sau đó làm những gì bạn cần làm với đối tượng được trả về.
Matt

1
Tôi nghĩ rằng maptập trung vào chính luồng đó và không nhằm mục đích "thực hiện mọi thứ cho đối tượng khác tùy thuộc vào trạng thái của thành phần này trong luồng". Điều tốt để biết rằng đã ifPresentOrElseđược thêm vào trong Java 9.
WesternGun

53

Một thay thế là:

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

Tôi không nghĩ rằng nó cải thiện khả năng đọc mặc dù.

Hoặc như Marko đề xuất, hãy sử dụng toán tử ternary:

System.out.println(opt.isPresent() ? "Found" : "Not found");

2
Cảm ơn @assylias, nhưng tôi không nghĩ Options.map () hoạt động cho trường hợp này (xem cập nhật ngữ cảnh của tôi).
smallufo

2
@smallufo Bạn sẽ cần phải trở lại new Object();trong lambda đầu tiên của mình nhưng thành thật mà nói nó trở nên rất xấu xí. Tôi sẽ dính vào một if / khác cho ví dụ cập nhật của bạn.
assylias

Đồng ý, sử dụng mapđể quay trở lại Optionalchuỗi làm cho mã khó hiểu hơn trong khi mapđược giả định là ánh xạ theo nghĩa đen vào một cái gì đó.
Tiina

40

Một giải pháp khác là sử dụng các hàm bậc cao hơn như sau

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

8
Trong mắt tôi, giải pháp tốt nhất cho đến nay mà không cần JDK 9.
Semaphor

5
Một lời giải thích sẽ là tuyệt vời. Tôi đang tự hỏi, tại sao bạn cần sử dụng bản đồ có thể chạy được (?) Và value -> () -> sysophần đó có nghĩa gì.
froehli

Cảm ơn giải pháp này! Tôi nghĩ lý do của việc sử dụng Runnable là vì bản đồ ngoài không trả lại bất kỳ giá trị nào và với Runnable, nó trả về lambda và như kết quả của bản đồ là lambda, chúng tôi sẽ chạy nó sau. Vì vậy, nếu bạn có giá trị trả về, bạn có thể sử dụng các giá trị sau:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

Không có cách nào hay để làm điều đó. Nếu bạn muốn sử dụng cú pháp sạch hơn một cách thường xuyên, thì bạn có thể tạo một lớp tiện ích để trợ giúp:

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

Sau đó, bạn có thể sử dụng nhập tĩnh ở nơi khác để nhận cú pháp gần với nội dung bạn đang theo sau:

import static com.example.OptionalEx.ifPresent;

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

Cảm ơn. Giải pháp này thật đẹp. Tôi biết có lẽ không có giải pháp tích hợp nào (trừ khi JDK kết hợp phương thức đó). Bạn OptionsEx rất hữu ích. Dẫu sao cũng cám ơn bạn.
smallufo

Vâng, tôi thích kết quả, và phong cách nó hỗ trợ. Vậy, tại sao không có trong API tiêu chuẩn?
guthrie

Câu trả lời tốt. Chúng tôi làm như vậy. Tôi đồng ý rằng nó phải có trong API (hoặc ngôn ngữ!), Nhưng nó đã bị từ chối: bug.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith

Đẹp. Đây phải là một phần của JDK 8.1 để xem xét.
peter_pilgrim

35
Optional.ifPresentOrElse()đã được thêm vào JDK 9.
Stuart Marks

9

Nếu bạn chỉ có thể sử dụng Java 8 trở xuống:

1) nếu bạn không có spring-datacách tốt nhất cho đến nay là:

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

Bây giờ tôi biết nó không dễ đọc và thậm chí khó hiểu đối với ai đó, nhưng cá nhân tôi có vẻ ổn và tôi không thấy một cách trôi chảy tốt đẹp nào khác cho trường hợp này.

2) nếu bạn đủ may mắn và bạn có thể sử dụng spring-datacách tốt nhất là Tùy chọn # ifPftimeOrElse :

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

Nếu bạn có thể sử dụng Java 9, bạn chắc chắn nên sử dụng:

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

2

Hành vi được mô tả có thể đạt được bằng cách sử dụng Vavr (trước đây gọi là Javaslang), một thư viện chức năng đối tượng cho Java 8+, thực hiện hầu hết các cấu trúc Scala (là Scala một ngôn ngữ biểu cảm hơn với hệ thống kiểu giàu hơn được xây dựng trên JVM). Đây là một thư viện rất tốt để thêm vào các dự án Java của bạn để viết mã chức năng thuần túy.

Vavr cung cấp các Optionđơn vị cung cấp các chức năng để làm việc với loại Tùy chọn, chẳng hạn như:

  • fold: để ánh xạ giá trị của tùy chọn trên cả hai trường hợp (được xác định / trống)
  • onEmpty: cho phép thực thi Runnabletùy chọn khi trống
  • peek: cho phép tiêu thụ giá trị của tùy chọn (khi được xác định).
  • và nó cũng Serializabletrái ngược với Optionalđiều đó có nghĩa là bạn có thể sử dụng nó một cách an toàn như là đối số phương thức và thành viên thể hiện.

Tùy chọn tuân theo các luật đơn nguyên khác với "giả đơn nguyên" tùy chọn của Java và cung cấp API phong phú hơn. Và tất nhiên, bạn có thể làm cho nó từ Tùy chọn của Java (và theo cách khác): Option.ofOptional(javaOptional)EDVavr tập trung vào khả năng tương tác.

Đi đến ví dụ:

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

đọc thêm

Tài liệu tham khảo vô nghĩa, sai lầm tỷ đô

NB Đây chỉ là một ví dụ rất nhỏ về những gì Vavr cung cấp (khớp mẫu, các luồng hay các danh sách đánh giá lười biếng, các loại đơn âm, các bộ sưu tập không thay đổi, ...).


1

Một giải pháp khác có thể là sau:

Đây là cách bạn sử dụng nó:

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

Hoặc trong trường hợp bạn trong trường hợp sử dụng ngược lại là đúng:

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

Đây là thành phần:

  1. Giao diện hàm ít được sửa đổi, chỉ dành cho phương thức "otherApply"
  2. Tăng cường tùy chọn
  3. Một chút dòng chảy :-)

Giao diện Chức năng nâng cao "thẩm mỹ".

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

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

}

Và lớp bao bọc tùy chọn để tăng cường:

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

Điều này sẽ thực hiện các mẹo và có thể phục vụ như một khuôn mẫu cơ bản để đối phó với các yêu cầu như vậy.

Ý tưởng cơ bản ở đây là sau đây. Trong một thế giới lập trình kiểu không có chức năng, có lẽ bạn sẽ thực hiện một phương thức lấy hai tham số trong đó đầu tiên là một loại mã có thể chạy được nên được thực thi trong trường hợp giá trị có sẵn và tham số khác là mã có thể chạy được trong trường hợp giá trị không có sẵn. Để dễ đọc hơn, bạn có thể sử dụng dòng điện để phân chia chức năng của hai tham số trong hai chức năng của một tham số. Đây là những gì tôi về cơ bản đã làm ở đây.

Gợi ý: Opt cũng cung cấp trường hợp sử dụng khác trong đó bạn muốn thực thi một đoạn mã chỉ trong trường hợp giá trị không có sẵn. Điều này cũng có thể được thực hiện thông qua Options.filter. Ware nhưng tôi thấy điều này dễ đọc hơn nhiều.

Mong rằng sẽ giúp!

Lập trình tốt :-)


Bạn có thể nói những gì không làm việc? Tôi đã thử nghiệm nó một lần nữa và cho tôi nó hoạt động?
Alessandro Giusa

Xin lỗi, nhưng Fkt ckass được định nghĩa ở đâu? Cuối cùng nhưng không kém phần quan trọng, tôi có một vấn đề biên dịch: Lỗi: (35, 17) java: biến tùy chọn có thể chưa được khởi tạo
Enrico Giurin

Fkt được định nghĩa ở trên là giao diện. Chỉ cần đọc toàn bộ bài viết :-)
Alessandro Giusa

Đúng. Tôi đã thay đổi giao diện EFeft thành Fkt như tên. Có một lỗi đánh máy. Cảm ơn đã xem xét :-) xin lỗi vì điều đó.
Alessandro Giusa

Tôi không nghĩ rằng đó là ý tưởng hay để viết mã như thế này ... Bạn nên sử dụng tiện ích jdk khi bạn có thể.
Tyulpan Tyulpan

0

Trong trường hợp bạn muốn lưu trữ giá trị:

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

0

Giả sử rằng bạn có một danh sách và tránh isPresent() (liên quan đến các tùy chọn) bạn có thể sử dụng .iterator().hasNext()để kiểm tra nếu không có mặt.

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.