千万不要滥用Stream.toList(),有坑!

Stream toList()返回的是只读List原则上不可修改,collect(Collectors.toList())默认返回的是ArrayList,可以增删改查

  1. 背景
    在公司看到开发环境突然发现了UnsupportedOperationException 报错,想到了不是自己throw的应该就是操作collection不当。

发现的确是同事使用了类似stringList.stream().filter(number -> Long.parseLong(number) > 1).toList() 以stream.toList()作为返回, 后继续使用了返回值做add操作,导致报错

  1. 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))来代替

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

princeAladdin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值