Java 8 - Sự khác biệt giữa Options.flatMap và Options.map


162

Sự khác biệt giữa hai phương pháp này là gì: Optional.flatMap()Optional.map()?

Một ví dụ sẽ được đánh giá cao.



5
@AlexisC. Liên kết của bạn là về bản đồ của Stream và FlatMap, không bắt buộc.
Eran

1
@Eran Điều đó không quan trọng, nếu bạn hiểu cách map / FlatMap hoạt động cho dù đó là Stream hay không, thì nó cũng tương tự đối với Tùy chọn. Nếu op hiểu cách nó hoạt động cho Stream, thì anh ta không nên hỏi câu hỏi này. Khái niệm này là như nhau.
Alexis C.

2
@AlexisC. Không hẳn vậy. FlatMap tùy chọn có ít điểm chung với FlatMap của Stream.
Eran

1
@Eran Tôi đang nói về sự khác biệt về khái niệm giữa bản đồ và bản đồ phẳng, tôi không tạo ra sự tương ứng một-một giữa Stream#flatMapOptional#flatMap.
Alexis C.

Câu trả lời:


166

Sử dụng mapnếu hàm trả về đối tượng bạn cần hoặc flatMapnếu hàm trả về Optional. Ví dụ:

public static void main(String[] args) {
  Optional<String> s = Optional.of("input");
  System.out.println(s.map(Test::getOutput));
  System.out.println(s.flatMap(Test::getOutputOpt));
}

static String getOutput(String input) {
  return input == null ? null : "output for " + input;
}

static Optional<String> getOutputOpt(String input) {
  return input == null ? Optional.empty() : Optional.of("output for " + input);
}

Cả hai bản in đều in cùng một thứ.


5
Câu hỏi: [flat]Mapbao giờ gọi hàm ánh xạ với một input == null? Sự hiểu biết của tôi là Optionalsắp xếp nếu nó vắng mặt - [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/iêu ) dường như sao lưu điều này - " Nếu có giá trị, hãy áp dụng .. . ".
nhện của

1
@BoristheSpider Options.of (null)! = Tùy chọn.empty ()
Diego Martinoia

14
@DiegoMartinoia Optional.of(null)là một Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider

1
@BoristheSpider có, bạn đúng ,. Tôi đã cố gắng trả lời câu hỏi của bạn nhưng tôi nghĩ rằng tôi thậm chí còn không rõ ràng hơn: về mặt khái niệm, không bắt buộc.
Diego Martinoia

1
Tôi nghĩ đầu vào không bao giờ nên rỗng trong cả getOutputOpt hoặc getOutput
DanyalBurke

55

Cả hai đều có một chức năng từ loại tùy chọn đến một cái gì đó.

map()áp dụng chức năng " như hiện tại " trên tùy chọn bạn có:

if (optional.isEmpty()) return Optional.empty();
else return Optional.of(f(optional.get()));

Điều gì xảy ra nếu chức năng của bạn là một chức năng từ T -> Optional<U>?
Kết quả của bạn bây giờ là một Optional<Optional<U>>!

Đó là những gì flatMap()về: nếu chức năng của bạn đã trả về một Optional, flatMap()thông minh hơn một chút và không gấp đôi nó, trả lại Optional<U>.

Đó là thành phần của hai thành ngữ chức năng: mapflatten.


7

Lưu ý: - bên dưới là hình minh họa của chức năng bản đồ và bản đồ, nếu không, Tùy chọn chủ yếu được thiết kế để chỉ được sử dụng làm loại trả về.

Như bạn đã biết Tùy chọn là một loại thùng chứa có thể có hoặc không chứa một đối tượng, do đó, nó có thể được sử dụng bất cứ nơi nào bạn dự đoán giá trị null (Bạn có thể không bao giờ thấy NPE nếu sử dụng Tùy chọn đúng cách). Ví dụ: nếu bạn có một phương thức mong đợi một đối tượng người có thể là null, bạn có thể muốn viết phương thức như thế này:

void doSome(Optional<Person> person){
  /*and here you want to retrieve some property phone out of person
    you may write something like this:
  */
  Optional<String> phone = person.map((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}
class Person{
  private String phone;
  //setter, getters
}

Ở đây bạn đã trả về một loại Chuỗi được tự động bọc trong một loại Tùy chọn.

Nếu lớp người trông như thế này, tức là điện thoại cũng là Tùy chọn

class Person{
  private Optional<String> phone;
  //setter,getter
}

Trong trường hợp này, chức năng gọi bản đồ sẽ bao bọc giá trị được trả về trong Tùy chọn và mang lại kết quả như:

Optional<Optional<String>> 
//And you may want Optional<String> instead, here comes flatMap

void doSome(Optional<Person> person){
  Optional<String> phone = person.flatMap((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}

PS; Không bao giờ gọi phương thức get (nếu bạn cần) trên Tùy chọn mà không kiểm tra phương thức đó bằng isPftime () trừ khi bạn không thể sống mà không có NullPulumExceptions.


1
Tôi nghĩ rằng ví dụ này có khả năng làm mất tập trung vào bản chất câu trả lời của bạn vì lớp học của bạn Personđang sử dụng sai Optional. Việc sử dụng API cho Optionalcác thành viên như thế này là trái với ý định - xem mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/
8bitjunkie

@ 8bitjunkie Cảm ơn bạn đã chỉ ra rằng, nó khác với Tùy chọn của Scala ..
SandeepGodara

6

Điều giúp tôi là một cái nhìn vào mã nguồn của hai chức năng.

Bản đồ - bao bọc kết quả trong Tùy chọn.

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value)); //<--- wraps in an optional
    }
}

FlatMap - trả về đối tượng 'thô'

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value)); //<---  returns 'raw' object
    }
}

1
Bạn có ý nghĩa gì khi flatMap"trả về đối tượng 'thô'? flatMapcũng trả về đối tượng được ánh xạ "bọc" trong một Optional. Sự khác biệt là trong trường hợp flatMap, hàm ánh xạ bao bọc đối tượng được ánh xạ trong Optionalkhi mapchính nó bao bọc đối tượng Optional.
Derek Mahar

@DerekMahar đã xóa của tôi, không cần phải đăng lại, vì bạn đã chỉnh sửa nhận xét của mình ngay.
maxxyme

3
  • Optional.map():

Lấy mọi phần tử và nếu giá trị tồn tại, nó được truyền cho hàm:

Optional<T> optionalValue = ...;
Optional<Boolean> added = optionalValue.map(results::add);

Bây giờ được thêm vào có một trong ba giá trị: truehoặc falseđược gói thành Tùy chọn , nếu optionalValuecó, hoặc Tùy chọn trống nếu không.

Nếu bạn không cần xử lý kết quả mà bạn có thể sử dụng ifPresent(), thì nó không có giá trị trả về:

optionalValue.ifPresent(results::add); 
  • Optional.flatMap():

Hoạt động tương tự như cùng một phương pháp của luồng. Làm phẳng dòng suối. Với sự khác biệt là nếu giá trị được trình bày, nó được áp dụng cho hàm. Nếu không, một tùy chọn trống được trả lại.

Bạn có thể sử dụng nó để soạn các cuộc gọi hàm giá trị tùy chọn.

Giả sử chúng ta có các phương thức:

public static Optional<Double> inverse(Double x) {
    return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

public static Optional<Double> squareRoot(Double x) {
    return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}

Sau đó, bạn có thể tính căn bậc hai của nghịch đảo, như:

Optional<Double> result = inverse(-4.0).flatMap(MyMath::squareRoot);

hoặc, nếu bạn thích:

Optional<Double> result = Optional.of(-4.0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);

Nếu inverse()hoặc squareRoot()trả về Optional.empty(), kết quả là trống rỗng.


1
Điều này không biên dịch. Cả hai biểu thức của bạn đều trả về <Double> tùy chọn thay vì Double mà bạn đang gán kết quả.
JL_SO

@JL_SO bạn nói đúng. Bởi vì nghịch đảo có Optional<Double>kiểu như kiểu trả về.
nazar_art

3

Được chứ. Bạn chỉ cần sử dụng 'FlatMap' khi bạn phải đối mặt với các Tùy chọn lồng nhau . Đây là ví dụ.

public class Person {

    private Optional<Car> optionalCar;

    public Optional<Car> getOptionalCar() {
        return optionalCar;
    }
}

public class Car {

    private Optional<Insurance> optionalInsurance;

    public Optional<Insurance> getOptionalInsurance() {
        return optionalInsurance;
    }
}

public class Insurance {

    private String name;

    public String getName() {
        return name;
    }

}

public class Test {

    // map cannot deal with nested Optionals
    public Optional<String> getCarInsuranceName(Person person) {
        return person.getOptionalCar()
                .map(Car::getOptionalInsurance) // ① leads to a Optional<Optional<Insurance>
                .map(Insurance::getName);       // ②
    }

}

Giống như Luồng, bản đồ # tùy chọn sẽ trả về giá trị được bao bọc bởi Tùy chọn. Đó là lý do tại sao chúng ta có một Tùy chọn lồng nhau - Optional<Optional<Insurance>. Và tại, chúng tôi muốn ánh xạ nó như một ví dụ Bảo hiểm, đó là cách thảm kịch xảy ra. Các gốc được lồng tùy chọn. Nếu chúng ta có thể nhận được giá trị cốt lõi bất kể vỏ, chúng ta sẽ hoàn thành nó. Đó là những gì FlatMap làm.

public Optional<String> getCarInsuranceName(Person person) {
    return person.getOptionalCar()
                 .flatMap(Car::getOptionalInsurance)
                 .map(Insurance::getName);
}

Cuối cùng, tôi đã hết sức giới thiệu Java 8 In Action cho bạn nếu bạn muốn nghiên cứu Java8 một cách có hệ thống.

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.