Sử dụng tùy chọn của Java 8 với Stream :: FlatMap


239

Khung công tác luồng Java 8 mới và bạn bè tạo ra một số mã java rất súc tích, nhưng tôi đã gặp một tình huống có vẻ đơn giản và khó thực hiện chính xác.

Hãy xem xét một List<Thing> thingsvà phương pháp Optional<Other> resolve(Thing thing). Tôi muốn ánh xạ Things đến Optional<Other>s và nhận đầu tiên Other. Giải pháp rõ ràng sẽ là sử dụng things.stream().flatMap(this::resolve).findFirst(), nhưng flatMapyêu cầu bạn trả về một luồng và Optionalkhông có stream()phương thức (hoặc đó là Collectionhoặc cung cấp phương thức để chuyển đổi nó thành hoặc xem nó như một Collection).

Điều tốt nhất tôi có thể đưa ra là:

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

Nhưng điều đó dường như vô cùng dài dòng đối với những gì có vẻ như là một trường hợp rất phổ biến. Bất cứ ai có một ý tưởng tốt hơn?


Sau khi mã hóa một chút với ví dụ của bạn, tôi thực sự thấy phiên bản rõ ràng dễ đọc hơn phiên bản liên quan, nếu nó tồn tại .flatMap(Optional::toStream), với phiên bản của bạn, bạn thực sự thấy những gì đang diễn ra.
skiwi

19
@skiwi Chà, Optional.streamtồn tại trong JDK 9 ngay bây giờ ....
Stuart Marks

Tôi tò mò nơi này được ghi lại, và quá trình để có được nó là gì. Có một số phương pháp khác thực sự có vẻ như chúng nên tồn tại và tôi tò mò nơi thảo luận về các thay đổi API đang diễn ra.
Yona Appletree


10
Điều buồn cười là JDK-8050820 thực sự đề cập đến câu hỏi này trong phần mô tả của nó!
Didier L

Câu trả lời:


263

Java 9

Optional.stream đã được thêm vào JDK 9. Điều này cho phép bạn thực hiện các thao tác sau mà không cần bất kỳ phương thức trợ giúp nào:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(Optional::stream)
          .findFirst();

Java 8

Đúng, đây là một lỗ nhỏ trong API, ở chỗ nó hơi bất tiện khi biến Tùy chọn thành Luồng có độ dài bằng 0 hoặc một. Bạn có thể làm điều này:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
          .findFirst();

Tuy nhiên, có toán tử ternary bên trong FlatMap hơi cồng kềnh, do đó, tốt hơn là viết một hàm trợ giúp nhỏ để làm điều này:

/**
 * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
 * whether a value is present.
 */
static <T> Stream<T> streamopt(Optional<T> opt) {
    if (opt.isPresent())
        return Stream.of(opt.get());
    else
        return Stream.empty();
}

Optional<Other> result =
    things.stream()
          .flatMap(t -> streamopt(resolve(t)))
          .findFirst();

Ở đây, tôi đã thực hiện cuộc gọi để giải quyết () thay vì có một hoạt động bản đồ () riêng biệt, nhưng đây là vấn đề của hương vị.


2
Tôi không nghĩ api có thể thay đổi cho đến Java 9 bây giờ.
assylias

5
@ Xin cảm ơn. Kỹ thuật .filter (). Map () không quá tệ và tránh được sự phụ thuộc vào các phương thức của trình trợ giúp. Mặc dù vậy, hãy trở nên tốt đẹp nếu có một cách ngắn gọn hơn. Tôi sẽ điều tra về việc thêm tùy chọn.stream ().
Stuart Marks

43
Tôi thích:static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
kubek2k

5
Tôi ước họ chỉ cần thêm một sự Optionalquá tải vào Stream#flatMap... theo cách mà bạn có thể viếtstream().flatMap(this::resolve)
vẩy vào

4
@flkes Vâng, chúng tôi đã đá xung quanh ý tưởng này, nhưng dường như nó không thêm tất cả giá trị đó bây giờ (trong JDK 9) Optional.stream().
Stuart Marks

69

Tôi đang thêm câu trả lời thứ hai này dựa trên bản chỉnh sửa được đề xuất bởi người dùng srborlongan cho câu trả lời khác của tôi . Tôi nghĩ rằng kỹ thuật được đề xuất là thú vị, nhưng nó không thực sự phù hợp như là một chỉnh sửa cho câu trả lời của tôi. Những người khác đồng ý và đề xuất chỉnh sửa đã được bỏ phiếu xuống. (Tôi không phải là một trong những cử tri.) Tuy nhiên, kỹ thuật này có giá trị. Sẽ là tốt nhất nếu srborlongan đã đăng câu trả lời của riêng anh ấy / cô ấy. Điều này chưa xảy ra và tôi không muốn kỹ thuật bị mất trong sương mù của lịch sử chỉnh sửa StackOverflow bị từ chối, vì vậy tôi quyết định tự mình xem nó như một câu trả lời riêng biệt.

Về cơ bản, kỹ thuật này là sử dụng một số Optionalphương thức một cách thông minh để tránh phải sử dụng toán tử ternary ( ? :) hoặc câu lệnh if / other.

Ví dụ nội tuyến của tôi sẽ được viết lại theo cách này:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
          .findFirst();

Một ví dụ của tôi sử dụng phương thức trợ giúp sẽ được viết lại theo cách này:

/**
 * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
 * whether a value is present.
 */
static <T> Stream<T> streamopt(Optional<T> opt) {
    return opt.map(Stream::of)
              .orElseGet(Stream::empty);
}

Optional<Other> result =
    things.stream()
          .flatMap(t -> streamopt(resolve(t)))
          .findFirst();

NHẬN XÉT

Hãy so sánh trực tiếp các phiên bản gốc với các phiên bản sửa đổi:

// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())

// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))

Bản gốc là một cách tiếp cận đơn giản nếu người lao động: chúng ta có một Optional<Other>; nếu nó có giá trị, chúng tôi trả về một luồng chứa giá trị đó và nếu nó không có giá trị, chúng tôi trả về một luồng trống. Khá đơn giản và dễ giải thích.

Việc sửa đổi là thông minh và có lợi thế là nó tránh được các điều kiện. (Tôi biết rằng một số người không thích toán tử ternary. Nếu sử dụng sai nó thực sự có thể làm cho mã khó hiểu.) Tuy nhiên, đôi khi mọi thứ có thể quá thông minh. Mã sửa đổi cũng bắt đầu với một Optional<Other>. Sau đó, nó gọi Optional.mapđược định nghĩa như sau:

Nếu có một giá trị, áp dụng hàm ánh xạ được cung cấp cho nó và nếu kết quả là không rỗng, hãy trả về Tùy chọn mô tả kết quả. Nếu không, trả lại một tùy chọn trống.

Cuộc map(Stream::of)gọi trả về một Optional<Stream<Other>>. Nếu một giá trị đã có trong tùy chọn đầu vào, Tùy chọn được trả về chứa Luồng chứa kết quả Khác. Nhưng nếu giá trị không có, kết quả là Tùy chọn trống.

Tiếp theo, cuộc gọi để orElseGet(Stream::empty)trả về một giá trị của loại Stream<Other>. Nếu giá trị đầu vào của nó là hiện tại, nó nhận được giá trị, đó là phần tử đơn Stream<Other>. Mặt khác (nếu giá trị đầu vào không có) nó sẽ trả về một sản phẩm nào Stream<Other>. Vì vậy, kết quả là chính xác, giống như mã điều kiện ban đầu.

Trong các ý kiến ​​thảo luận về câu trả lời của tôi, liên quan đến chỉnh sửa bị từ chối, tôi đã mô tả kỹ thuật này là "ngắn gọn hơn nhưng cũng tối nghĩa hơn". Tôi đứng bên này Tôi phải mất một thời gian để tìm ra những gì nó đang làm, và tôi cũng mất một lúc để viết mô tả ở trên về những gì nó đang làm. Sự tinh tế quan trọng là sự chuyển đổi từ Optional<Other>sang Optional<Stream<Other>>. Một khi bạn mò mẫm điều này, nó có ý nghĩa, nhưng nó không rõ ràng đối với tôi.

Tuy nhiên, tôi sẽ thừa nhận rằng những thứ ban đầu tối nghĩa có thể trở thành thành ngữ theo thời gian. Nó có thể là kỹ thuật này kết thúc là cách tốt nhất trong thực tế, ít nhất là cho đến khi Optional.streamđược thêm vào (nếu nó đã từng).

CẬP NHẬT: Optional.stream đã được thêm vào JDK 9.


16

Bạn không thể làm điều đó ngắn gọn hơn như bạn đã làm.

Bạn tuyên bố rằng bạn không muốn .filter(Optional::isPresent) .map(Optional::get) .

Điều này đã được giải quyết bằng phương pháp @StuartMarks mô tả, tuy nhiên, kết quả là bây giờ bạn ánh xạ nó tới một Optional<T>, vì vậy bây giờ bạn cần sử dụng .flatMap(this::streamopt)get()cuối cùng.

Vì vậy, nó vẫn bao gồm hai câu lệnh và bây giờ bạn có thể có ngoại lệ với phương thức mới! Bởi vì, nếu mọi tùy chọn đều trống thì sao? Sau đó, findFirst()sẽ trả lại một tùy chọn trống và bạn get()sẽ thất bại!

Vì vậy, những gì bạn có:

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

thực sự là cách tốt nhất để thực hiện những gì bạn muốn, và đó là bạn muốn lưu kết quả như một T, không phải là một Optional<T>.

Tôi đã tự do tạo ra một CustomOptional<T>lớp bao bọc Optional<T>và cung cấp một phương thức bổ sung , flatStream(). Lưu ý rằng bạn không thể gia hạn Optional<T>:

class CustomOptional<T> {
    private final Optional<T> optional;

    private CustomOptional() {
        this.optional = Optional.empty();
    }

    private CustomOptional(final T value) {
        this.optional = Optional.of(value);
    }

    private CustomOptional(final Optional<T> optional) {
        this.optional = optional;
    }

    public Optional<T> getOptional() {
        return optional;
    }

    public static <T> CustomOptional<T> empty() {
        return new CustomOptional<>();
    }

    public static <T> CustomOptional<T> of(final T value) {
        return new CustomOptional<>(value);
    }

    public static <T> CustomOptional<T> ofNullable(final T value) {
        return (value == null) ? empty() : of(value);
    }

    public T get() {
        return optional.get();
    }

    public boolean isPresent() {
        return optional.isPresent();
    }

    public void ifPresent(final Consumer<? super T> consumer) {
        optional.ifPresent(consumer);
    }

    public CustomOptional<T> filter(final Predicate<? super T> predicate) {
        return new CustomOptional<>(optional.filter(predicate));
    }

    public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) {
        return new CustomOptional<>(optional.map(mapper));
    }

    public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) {
        return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional())));
    }

    public T orElse(final T other) {
        return optional.orElse(other);
    }

    public T orElseGet(final Supplier<? extends T> other) {
        return optional.orElseGet(other);
    }

    public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X {
        return optional.orElseThrow(exceptionSuppier);
    }

    public Stream<T> flatStream() {
        if (!optional.isPresent()) {
            return Stream.empty();
        }
        return Stream.of(get());
    }

    public T getTOrNull() {
        if (!optional.isPresent()) {
            return null;
        }
        return get();
    }

    @Override
    public boolean equals(final Object obj) {
        return optional.equals(obj);
    }

    @Override
    public int hashCode() {
        return optional.hashCode();
    }

    @Override
    public String toString() {
        return optional.toString();
    }
}

Bạn sẽ thấy rằng tôi đã thêm flatStream(), như ở đây:

public Stream<T> flatStream() {
    if (!optional.isPresent()) {
        return Stream.empty();
    }
    return Stream.of(get());
}

Được dùng như:

String result = Stream.of("a", "b", "c", "de", "fg", "hij")
        .map(this::resolve)
        .flatMap(CustomOptional::flatStream)
        .findFirst()
        .get();

Bạn vẫn sẽ cần phải trả lại Stream<T>ở đây, vì bạn không thể quay lại T, bởi vì nếu !optional.isPresent(), sau đó T == nullnếu bạn khai báo như vậy, nhưng sau đó bạn .flatMap(CustomOptional::flatStream)sẽ cố gắng thêm nullvào một luồng và điều đó là không thể.

Ví dụ như:

public T getTOrNull() {
    if (!optional.isPresent()) {
        return null;
    }
    return get();
}

Được dùng như:

String result = Stream.of("a", "b", "c", "de", "fg", "hij")
        .map(this::resolve)
        .map(CustomOptional::getTOrNull)
        .findFirst()
        .get();

Bây giờ sẽ ném một NullPointerExceptionbên trong các hoạt động dòng.

Phần kết luận

Phương pháp bạn đã sử dụng, thực sự là phương pháp tốt nhất.


6

Một phiên bản ngắn hơn một chút bằng cách sử dụng reduce:

things.stream()
  .map(this::resolve)
  .reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );

Bạn cũng có thể di chuyển hàm less sang một phương thức tiện ích tĩnh và sau đó nó trở thành:

  .reduce(Optional.empty(), Util::firstPresent );

6
Tôi thích điều này, nhưng đáng để chỉ ra rằng điều này sẽ đánh giá mọi mục trong Luồng, trong khi findFirst () sẽ chỉ đánh giá cho đến khi tìm thấy mục hiện tại.
Duncan McGregor

1
Và thật không may, thực hiện mỗi giải quyết là một phá vỡ thỏa thuận. Nhưng nó thông minh.
Yona Appletree

5

câu trả lời trước của tôi dường như không phổ biến lắm, tôi sẽ đưa ra câu hỏi này.

Một câu trả lời ngắn gọn:

Bạn chủ yếu đi đúng hướng. Mã ngắn nhất để có được đầu ra mong muốn của bạn, tôi có thể đưa ra là:

things.stream()
      .map(this::resolve)
      .filter(Optional::isPresent)
      .findFirst()
      .flatMap( Function.identity() );

Điều này sẽ phù hợp với tất cả các yêu cầu của bạn:

  1. Nó sẽ tìm thấy phản hồi đầu tiên giải quyết thành không trống Optional<Result>
  2. Nó gọi this::resolvemột cách lười biếng khi cần thiết
  3. this::resolve sẽ không được gọi sau kết quả không trống đầu tiên
  4. Nó sẽ trở lại Optional<Result>

Câu trả lời dài hơn

Sửa đổi duy nhất so với phiên bản ban đầu của OP là tôi đã xóa .map(Optional::get)trước khi gọi đến .findFirst()và thêm vào .flatMap(o -> o)như cuộc gọi cuối cùng trong chuỗi.

Điều này có tác dụng tốt trong việc loại bỏ Tùy chọn kép, bất cứ khi nào luồng tìm thấy kết quả thực tế.

Bạn thực sự không thể đi ngắn hơn cái này trong Java.

Đoạn mã thay thế sử dụng forkỹ thuật vòng lặp thông thường hơn sẽ có cùng số dòng mã và có ít nhiều thứ tự và số lượng thao tác bạn cần thực hiện:

  1. Calling this.resolve,
  2. lọc dựa trên Optional.isPresent
  3. trả lại kết quả và
  4. một số cách xử lý kết quả âm tính (khi không tìm thấy gì)

Chỉ để chứng minh rằng giải pháp của tôi hoạt động như quảng cáo, tôi đã viết một chương trình thử nghiệm nhỏ:

public class StackOverflow {

    public static void main( String... args ) {
        try {
            final int integer = Stream.of( args )
                    .peek( s -> System.out.println( "Looking at " + s ) )
                    .map( StackOverflow::resolve )
                    .filter( Optional::isPresent )
                    .findFirst()
                    .flatMap( o -> o )
                    .orElseThrow( NoSuchElementException::new )
                    .intValue();

            System.out.println( "First integer found is " + integer );
        }
        catch ( NoSuchElementException e ) {
            System.out.println( "No integers provided!" );
        }
    }

    private static Optional<Integer> resolve( String string ) {
        try {
            return Optional.of( Integer.valueOf( string ) );
        }
        catch ( NumberFormatException e )
        {
            System.out.println( '"' + string + '"' + " is not an integer");
            return Optional.empty();
        }
    }

}

(Nó có thêm vài dòng để gỡ lỗi và xác minh rằng chỉ có nhiều cuộc gọi để giải quyết khi cần ...)

Thực hiện điều này trên một dòng lệnh, tôi đã nhận được các kết quả sau:

$ java StackOferflow a b 3 c 4
Looking at a
"a" is not an integer
Looking at b
"b" is not an integer
Looking at 3
First integer found is 3

Tôi nghĩ giống như Roland Tepp. Tại sao ai đó tạo luồng <stream <? >> và phẳng khi bạn chỉ có thể căn hộ với một tùy chọn <tùy chọn <? >>
Young Hyun Yoo

3

Nếu bạn không ngại sử dụng thư viện của bên thứ ba, bạn có thể sử dụng Javaslang . Nó giống như Scala, nhưng được triển khai trong Java.

Nó đi kèm với một thư viện bộ sưu tập bất biến hoàn toàn rất giống với thư viện được biết đến từ Scala. Các bộ sưu tập này thay thế các bộ sưu tập của Java và Luồng của Java 8. Nó cũng có triển khai riêng của Tùy chọn.

import javaslang.collection.Stream;
import javaslang.control.Option;

Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar"));

// = Stream("foo", "bar")
Stream<String> strings = options.flatMap(o -> o);

Đây là một giải pháp cho ví dụ về câu hỏi ban đầu:

import javaslang.collection.Stream;
import javaslang.control.Option;

public class Test {

    void run() {

        // = Stream(Thing(1), Thing(2), Thing(3))
        Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3));

        // = Some(Other(2))
        Option<Other> others = things.flatMap(this::resolve).headOption();
    }

    Option<Other> resolve(Thing thing) {
        Other other = (thing.i % 2 == 0) ? new Other(i + "") : null;
        return Option.of(other);
    }

}

class Thing {
    final int i;
    Thing(int i) { this.i = i; }
    public String toString() { return "Thing(" + i + ")"; }
}

class Other {
    final String s;
    Other(String s) { this.s = s; }
    public String toString() { return "Other(" + s + ")"; }
}

Tuyên bố miễn trừ trách nhiệm: Tôi là người tạo ra Javaslang.


3

Đi dự tiệc muộn, nhưng còn

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .findFirst().get();

Bạn có thể thoát khỏi get () cuối cùng nếu bạn tạo một phương thức tiện dụng để chuyển đổi tùy chọn thành luồng theo cách thủ công:

things.stream()
    .map(this::resolve)
    .flatMap(Util::optionalToStream)
    .findFirst();

Nếu bạn trả lại luồng ngay từ chức năng giải quyết của mình, bạn sẽ lưu thêm một dòng.


3

Tôi muốn quảng bá các phương thức xuất xưởng để tạo trợ giúp cho các API chức năng:

Optional<R> result = things.stream()
        .flatMap(streamopt(this::resolve))
        .findFirst();

Phương pháp nhà máy:

<T, R> Function<T, Stream<R>> streamopt(Function<T, Optional<R>> f) {
    return f.andThen(Optional::stream); // or the J8 alternative:
    // return t -> f.apply(t).map(Stream::of).orElseGet(Stream::empty);
}

Lý do:

  • Như với các tham chiếu phương thức nói chung, so với các biểu thức lambda, bạn không thể vô tình nắm bắt một biến từ phạm vi có thể truy cập, như:

    t -> streamopt(resolve(o))

  • Nó có thể kết hợp được, ví dụ bạn có thể gọi Function::andThenkết quả của phương thức xuất xưởng:

    streamopt(this::resolve).andThen(...)

    Trong trường hợp của lambda, bạn cần bỏ nó trước:

    ((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)


3

Null được hỗ trợ bởi Stream cung cấp Thư viện của tôi AbacusUtil . Đây là mã:

Stream.of(things).map(e -> resolve(e).orNull()).skipNull().first();

3

Nếu bạn bị mắc kẹt với Java 8 nhưng có quyền truy cập vào Guava 21.0 hoặc mới hơn, bạn có thể sử dụng Streams.streamđể chuyển đổi tùy chọn thành luồng.

Như vậy, đã cho

import com.google.common.collect.Streams;

bạn có thể viết

Optional<Other> result =
    things.stream()
        .map(this::resolve)
        .flatMap(Streams::stream)
        .findFirst();

0

Thế còn cái đó?

private static List<String> extractString(List<Optional<String>> list) {
    List<String> result = new ArrayList<>();
    list.forEach(element -> element.ifPresent(result::add));
    return result;
}

https://stackoverflow.com/a/58281000/3477539


Tại sao làm điều này khi bạn có thể truyền phát và thu thập?
OneCricketeer

return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList())), giống như câu hỏi (và câu trả lời được liên kết của bạn) có ...
OneCricketeer

Tôi có thể sai, nhưng tôi coi việc sử dụng isPftime () và sau đó get () không phải là một cách thực hành tốt. Vì vậy, tôi cố gắng để thoát khỏi đó.
rastaman

Nếu bạn sử dụng .get() mà không có isPresent() , thì bạn sẽ nhận được cảnh báo trong IntelliJ
OneCricketeer

-5

Nhiều khả năng Bạn đang làm sai.

Java 8 Tùy chọn không có nghĩa là được sử dụng theo cách này. Nó thường chỉ dành riêng cho các hoạt động dòng thiết bị đầu cuối có thể hoặc không thể trả về một giá trị, như tìm ví dụ.

Trong trường hợp của bạn, tốt nhất là trước tiên bạn nên cố gắng tìm một cách rẻ tiền để lọc ra những mục có thể phân giải được và sau đó lấy mục đầu tiên làm tùy chọn và giải quyết nó như một thao tác cuối cùng. Tốt hơn nữa - thay vì lọc, hãy tìm mục có thể phân giải đầu tiên và giải quyết nó.

things.filter(Thing::isResolvable)
      .findFirst()
      .flatMap(this::resolve)
      .get();

Nguyên tắc chung là bạn nên cố gắng giảm số lượng mục trong luồng trước khi bạn chuyển đổi chúng sang mục khác. YMMV tất nhiên.


6
Tôi nghĩ rằng phương thức giải quyết () của OP trả về Tùy chọn <Khác> là cách sử dụng Tùy chọn hoàn toàn hợp lý. Dĩ nhiên, tôi không thể nói chuyện với miền vấn đề của OP, nhưng đó có thể là cách để xác định liệu một cái gì đó có thể giải quyết được hay không là cố gắng giải quyết nó. Nếu vậy, Tùy chọn hợp nhất kết quả boolean của "là điều này có thể giải quyết được" với kết quả của độ phân giải, nếu thành công, trong một lệnh gọi API duy nhất.
Stuart Marks

2
Stuart về cơ bản là chính xác. Tôi có một bộ thuật ngữ tìm kiếm theo thứ tự mong muốn và tôi đang tìm kiếm kết quả của cụm từ đầu tiên trả về bất cứ thứ gì. Về cơ bản Optional<Result> searchFor(Term t). Điều đó dường như phù hợp với ý định của Tùy chọn. Ngoài ra, các luồng () nên được đánh giá một cách lười biếng, do đó, không có công việc bổ sung nào giải quyết các thuật ngữ phù hợp với điều khoản phù hợp đầu tiên sẽ xảy ra.
Yona Appletree

Câu hỏi là hoàn toàn hợp lý và sử dụng FlatMap với Tùy chọn thường được thực hiện bằng các ngôn ngữ lập trình tương tự khác, như Scala.
dzs
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.