Optional 與 Stream 的 flatMap

在程式設計中有時會出現巢狀或瀑布式的流程,就結構來看每一層運算極為類似,只是傳回的型態不同,很難抽取流程重用。舉例來說,如果你的方法可能傳回null,你可能會設計出某個流程如下:

Customer customer = order.getCustomer();
if(customer != null) {
    String address = customer.getAddress();
    if(address != null) {
        return address;
    }
}
return "n.a.";

巢狀的層次可能還會更深,像是 ...

Customer customer = order.getCustomer();
if(customer != null) {
    Address address = customer.getAddress();
    if(address != null) {
        City city = address.getCity();
        if(city != null) {
            ....
        }
    }
}
return "n.a.";

連續的層次不深時,也許程式碼看來還算直覺,然後層次一深之後,顯然地,很容易迷失在層次之中,雖然每層都是判斷值是否為null,不過因為型態不同,看來不太好抽取流程重用。

使用 Optional 取代 null 中的說明, null 本身就不建議使用,如果讓 getCustomer() 傳回 Optional<Customer> 、讓 getAddress() 傳回 Optional<String> ,那一開始的程式片段可以先改為:

String addr = "n.a.";
Optional<Customer> customer = order.getCustomer();
if(customer.isPresent()) {
    Optional<String> address = customer.get().getAddress();
    if(address.isPresent()) {
        addr = address.get();
    }
}
return addr;

看來好像沒有高明到哪去,不過至少每一層都是 Optional 型態了,而每一層都是有無的判斷,然後將 Optional<T> 轉換為 Optional<U> ,如果將 Optional<T> 轉換為 Optional<U> 的方式可以由外部指定,那你就可以重用有無的判斷了,實際上 Optional 有個 flatMap() 方法,已經幫你寫好這個邏輯了:

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


所以,你大可以如下直接使用 Optional flatMap() 方法:

return order.getCustomer()
            .flatMap(Customer::getAddress)
            .orElse("n.a.");

如果層次不深,也許看不出使用這個的好處,若層次深比較有益處時,像是一開始第二個程式片段,改寫為以下就清楚多了…

return order.getCustomer()
            .flatMap(Customer::getAddress)
            .flatMap(Address::getCity)
            .orElse("n.a.");


Optional flatMap() 這個名稱令人困惑,可從 Optional<T> 呼叫 flatMap 後會得到 Optional<U> 來想像一下, flatMap() 就像是從盒子取出另一盒子置放一旁(flat就是平坦化的意思),過程中依指定之Lambda將前盒的 T 映射(map)為 U 再放入後盒,因為判斷是否有值的運算情境被隱藏了,使用者因此可明確指定感興趣的特定運算,從而使程式碼意圖顯露出來,又可接暢地接續運算,以避免巢狀或瀑布式的複雜檢查流程。

那麼如果你沒辦法修改程式,讓 getCustomer() getAddress() getCity() 等傳回 Optional 型態怎麼辦? Optional 是還有個 map() 方法,例如,若參數 order Order 型態,有 null 的可能性, getCustomer() getAddress() getCity() 等分別的傳回型態是 Customer Address City ,且有可能傳回 null ,那麼就可以這麼做:

return Optional.ofNullable(order)
               .map(Order::getCustomer)
               .map(Customer::getAddress)
               .map(Address::getCity)
               .orElse("n.a.");

flatMap() 的差別在於, map() 方法實作中,對 mapper.apply(value) 的結果使用了 Optional.ofNullable() 方法( flatMap 中使用的是 Objects.requireNonNull() ),因此有辦法持續處理 null 的情況:

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


如果之前的 Order 有個 getLineItems() 方法,可取得訂單中的產品項目 List<LineItem> ,想要取得 LineItem 的名稱,可以透過 getName 來取得,若你有個 List<Order> ,想取得所有的產品項目名稱會怎麼寫?直覺的寫法應該是用迴圈…

List<String> itemNames = new ArrayList<>();
for(Order order : orders) {
    for(LineItem lineItem : order.getLineItems()) {
        itemNames.add(lineItem.getName());
    }
}


當然,層次不深時這樣寫很直覺也還好閱讀,不過如果層次深時,例如,想進一步取得 LineItem 的贈品名稱的話,你又得多一層 for 迴圈,如果還要繼續取下去呢?...

你可以用 List stream() 方法取得 Stream 之後,使用 flatMap() 方法如下改寫:

List<String> itemNames = orders.stream()
                .flatMap(order -> order.getLineItems().stream())
                .map(LineItem::getName)
                .collect(toList());

就程式碼閱讀來說, stream() 方法會傳回 Stream<Order> ,把 Stream 當成是盒子, stream() 就是將一群 Order 物件全部放入盒中, flatMap() 指定的Lambda運算是 order.getLineItems().stream() ,意思就是從盒中那群 Order 物件逐一取得 List<LineItem> ,然後再用一個 Stream 將所有 LineItem 裝起來,也就是說, Stream<Order> 經由 flatMap 方法後映射為 Stream<LineItem> ,這類操作一個盒子一個盒子(一個 Stream 一個 Stream )接續下去,例如,想進一步取得 LineItem 的贈品名稱可以如下:

List<String> itemNames = orders.stream()
                .flatMap(order -> order.getLineItems().stream())
                .flatMap(lineItem -> lineItem.getPremiums().stream())
                .map(LineItem::getName)
                .collect(toList());

基本上,如果瞭解 Optinal Stream (或其他型態)的 flatMap() 方法,在一層一層盒子剝開後做了哪些運算,撰寫與閱讀程式碼時,忽略掉flatMap這個名稱,就能比較清楚程式碼的主要意圖。
StreamOptional是Java 8中新增的两个特性,它们都可以用于简化代码和提高代码的可读性。 Stream流简介: Stream是Java 8中新增的一种数据处理方式,它可以用于对集合、数组等数据进行批量操作。Stream提供了很多方法来进行数据处理,例如filter()、map()、reduce()等。Stream的使用可以大大简化代码,提高代码的可读性。 Stream的使用步骤: 1. 创建Stream对象:可以通过集合、数组等方式创建Stream对象。 2. 对Stream进行中间操作:可以使用filter()、map()、reduce()等方法对Stream进行中间操作。 3. 对Stream进行终止操作:可以使用count()、collect()等方法对Stream进行终止操作,获取最终结果。 Optional简介: Optional是Java 8中新增的一种特殊类型,它可以用于解决代码中的null值问题。Optional对象可以包含一个非null的值,也可以为空。使用Optional可以避免代码中出现空指针异常,提高代码的健壮性和可读性。 Optional的使用步骤: 1. 创建Optional对象:可以通过of()、ofNullable()等方法创建Optional对象。 2. 对Optional进行操作:可以使用isPresent()、orElse()等方法对Optional进行操作,获取Optional对象中的值。 3. 对Optional进行转换:可以使用map()、flatMap()等方法将Optional中的值进行转换。 Stream流和Optional的关系: StreamOptional都是Java 8中新增的特性,它们的使用可以大大简化代码,提高代码的可读性。StreamOptional常常一起使用,例如对集合中的数据进行处理时,使用Stream来处理数据,使用Optional来处理空值问题。 例如,以下代码使用StreamOptional来获取一个集合中的第一个元素: ``` List<String> list = Arrays.asList("apple", "banana", "cherry"); Optional<String> first = list.stream().findFirst(); if (first.isPresent()) { System.out.println(first.get()); } else { System.out.println("List is empty"); } ``` 在以上示例中,我们使用了Stream来处理集合中的数据,使用Optional来处理空值问题。通过调用stream()方法将集合转换为Stream对象,然后使用findFirst()方法获取第一个元素。使用isPresent()方法判断Optional对象是否为空,如果不为空,则使用get()方法获取Optional对象中的值。 总结: StreamOptional都是Java 8中新增的特性,它们的使用可以大大简化代码,提高代码的可读性。Stream可以用于对集合、数组等数据进行批量操作,而Optional可以用于解决代码中的null值问题。StreamOptional常常一起使用,例如对集合中的数据进行处理时,使用Stream来处理数据,使用Optional来处理空值问题。在使用StreamOptional时,需要注意使用恰当的操作方法,以提高代码的效率和可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值