當前位置: 妍妍網 > 碼農

千萬不要濫用Stream.toList(),有坑!

2024-03-13碼農

Stream toList() 返回的是唯讀List原則上不可修改, collect(Collectors.toList()) 預設返回的是ArrayList,可以增刪改查

1. 背景

在公司看到開發環境突然發現了 UnsupportedOperationException 報錯,想到了不是自己throw的應該就是操作collection不當。

發現的確是同事使用了類似 stringList.stream().filter(number -> Long.parseLong(number) > 1).toList() 以stream.toList() 作為返回, 後繼續使用了返回值做add操作,導致報錯

2. Stream toList()和 collect(Collectors.toList())的區別

  • JDK version: 21

  • IDE: IDEA

  • 從Java16開始,Stream有了直接toList方法, java8時候常用的方法是 stringList.stream().filter(number -> Long.parseLong(number) > 1).collect(Collectors.toList())

    Stream toList()

    /**
     * Accumulates the elements of this stream into a {@code List}. The elements in
     * the list will be in this stream's encounter order, if one exists. The returned List
     * is unmodifiable; calls to any mutator method will always cause
     * {@code UnsupportedOperationException} to be thrown. There are no
     * guarantees on the implementation type or serializability of the returned List.
     *
     * <p>The returned instance may be <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
     * Callers should make no assumptions about the identity of the returned instances.
     * Identity-sensitive operations on these instances (reference equality ({@code ==}),
     * identity hash code, and synchronization) are unreliable and should be avoided.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
     *
     * @apiNote If more control over the returned object is required, use
     * {@link Collectors#toCollection(Supplier)}.
     *
     * @implSpec The implementation in this interface returns a List produced as if by the following:
     * <pre>{@code
     * Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())))
     * }</pre>
     *
     * @implNote Most instances of Stream will override this method and provide an implementation
     * that is highly optimized compared to the implementation in this interface.
     *
     * @return a List containing the stream elements
     *
     * @since 16
     */
    @SuppressWarnings("unchecked")
    default List<T> toList() {
    return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
    }

    檢視源碼 Stream toList呼叫的是 Collections.unmodifiableList 而在 unmodifiableList(List<? extends T> list) 實作中, 都會返回一個不可修改的List,所以不能使用set/add/remove等改變list陣列的方法。

    return (list instanceof RandomAccess ?
    new UnmodifiableRandomAccessList<>(list) :
    new UnmodifiableList<>(list));

    圖片

    但其實也可以修改List的元素的某些內容,例如

    List<String> stringList = List.of("1""2""3");
    List<String> largeNumberList = stringList.stream().filter(number -> Long.parseLong(number) > 1).toList();
    List<String> largeNumberList2 = stringList.stream().filter(number -> Long.parseLong(number) > 1).collect(Collectors.toList());
    largeNumberList.add("4"); // java.lang.UnsupportedOperationException
    largeNumberList2.add("4"); //success
    // modify custom object attribute
    User userZhang = new User("ZhangSan");
    User userLi = new User("LiSi");
    List<User> userList = List.of(userZhang, userLi);
    List<User> filterList = userList.stream().filter(user -> "LiSi".equals(user.name)).toList();
    User first = filterList.getFirst();//java 21
    first.name = "WangWu";
    filterList.forEach(u -> System.out.println(u.name));
    //List.of返回的也是不能修改的List
    userList.forEach(u -> System.out.print(u.name));

    輸出結果是:

    WangWu
    ZhangSanWangWu

    Stream collect(Collectors.toList())

    返回一個ArrayList 如果沒有在 toList() 方法入參中傳入指定Supplier的話, 可以做ArrayList的任何操作

    /**
     * Returns a {@code Collector} that accumulates the input elements into a
     * new {@code List}. There are no guarantees on the type, mutability,
     * serializability, or thread-safety of the {@code List} returned; if more
     * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}.
     *
     * @param <T> the type of the input elements
     * @return a {@code Collector} which collects all the input elements into a
     * {@code List}, in encounter order
     */
    public static <T>
    Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>(ArrayList::new, List::add,
    (left, right) -> { left.addAll(right); return left; },
    CH_ID);
    }

    tips: List.of(),返回的也是不可修改的list, an unmodifiable list. 關於 Unmodifiable Lists 說明

    of和List.copyOf靜態工廠方法為建立不可修改的列表提供了一種方便的方法。這些方法建立的List例項具有以下特征:

  • 它們是不可修改的。元素不能被添加、刪除或替換。呼叫List上的任何mutator方法將始終導致引發 UnsupportedOperationException 。但是,如果包含的元素本身是可變的,這可能會導致List的內容看起來發生變化。

  • 它們不允許空元素。嘗試使用空元素建立它們會導致 NullPointerException

  • 如果所有元素都是可序列化的,那麽它們就是可序列化的。

  • 列表中元素的順序與提供的參數或提供的陣列中元素的順序相同。列表及其子List檢視實作了 RandomAccess 介面。

  • 它們以價值為基礎。應將相等的例項視為可互換的,不應將它們用於同步,否則可能會發生不可預知的行為。如,在將來的版本中,同步可能會失敗。呼叫端不應假設傳回執行個體的辨識。工廠可以自由地建立新的例項或重用現有的例項。

  • 它們將按照「序列化表單」頁上的指定進行序列化。

  • 該介面是Java集合框架的成員。

  • 3.如何使用(不考慮效能)

    確定其是一個不再被set/add/remove的list 可使用 Stream toList;

    如果使用 collect(Collectors.toList()) ,sonar或idea內建以及第三方的一些code checker會爆warning,以本人經驗,可以使用 collect(Collectors.toCollection(ArrayList::new)) 來代替

    來源:juejin.cn/post/7325717778597904420

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中