线上事故回顾
晚饭时,当我正沉迷于排骨煲肉质鲜嫩,汤汁浓郁时,产研沟通群内发出一条消息,显示用户存在可用劵,但进去劵列表却什么也没有,并附含了一个视频。于是我一边吃了排骨,一边查看消息点开了视频,en~,视频跟描述一样。但没有系统告警,用户界面也没有明显的报错提示,怀疑是小部分特殊情况导致的,查看消息后几秒,我直接被@来处理问题,擦,只好把外卖盒重新盖好,先去处理问题。
处理经过
通过群内产品发的用户邮箱查到了用户id,再根据接口的相关日志结合uid在日志平台进行关联查询,查到日志后,再拿到traceId进行链路查询,果不其然,发现了异常日志,如下部分日志所示
java.lang.UnsupportedOperationException: null
at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_151]
at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_151]
乍一看,这不是空指针嘛,so easy啊
仔细一瞧,这UnsupportedOperationException
是个什么玩意
于是,根据日志找到代码中报错的那一行,下面给大家简单模拟下
@Slf4j
@SpringBootTest
public class Demo {
public void test(Context context) {
context.getList().add("Code皮皮虾");
}
}
@Data
class Context {
private List<String> list;
}
基本操作就是拿到上下文中的List,然后再add一个元素
讲道理,add操作是不会有问题的,有问题的还得是List,追根溯源,让我康康这个List是怎么来的
于是我一顿狂点,来到了set这个list的位置
@Slf4j
@SpringBootTest
public class Demo {
public void test(Context context) {
context.setList(Arrays.asList("Code皮皮虾"));
}
}
@Data
class Context {
private List<String> list;
}
context.setList(Arrays.asList("Code皮皮虾"));
这行看起来好像没问题啊
Arrays.asList(T... a)
我们平时也会用,传入一个数组,返回出一个List
没啥问题呀
那我再试试add
方法
擦,问题复现了,还真是Arrays.asList(T... a)
生成的List
的add方法报错
由于线上存在问题,则先修改为以下代码上线,也就是修改为我们平时正常的写法
上线后,观察了下日志,群里回复已解决问题,也让用户重试,发现没问题,自此问题解决。
接下来,咱们来看看为啥Arrays.asList(T... a)
的add
方法会报错
追根溯源
进入asList
方法,发现底层new
了一个ArrayList
,并将数组传入作为List
的元素
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
emm,看起来很简单啊,没问题啊,咋会报错呢
别着急,咱们在点开这个ArrayList
瞅瞅
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
// ... 省略
}
擦,这ArrayList
是Arrays
类的一个静态内部类,不是我们经常用的java.util.ArrayList
真是离谱他妈给离谱开门,离谱大家了,还是我源码看得太少了,呜呜呜~
继续看,这个静态内部类ArrayList
继承了AbstractList
,而且默认是没有实现add
方法的
也就是说调用add
方法会直接调用父类,也就是AbstractList
的add
方法,源码点开一看,真相大白了
AbstractList
的add
方法直接抛出UnsupportedOperationException
异常,跟线上报错一模一样!!!
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
至此,排查结束,继续吃饭去排骨去咯~~~
小彩蛋
在使用Arrays.asList(T... a)
方法时,如果只是单个元素的话,Idea会提示我们更建议Collections.singletonList
别用!!!真的别用!!!
因为Collections.singletonList
底层跟Arrays.asList(T... a)
差不多
SingletonList
也是继承了AbstractList
的一个内部类,调用add
一样会报UnsupportedOperationException
异常
public static <T> List<T> singletonList(T o) {
return new SingletonList<>(o);
}
private static class SingletonList<E>
extends AbstractList<E>
implements RandomAccess, Serializable {
private static final long serialVersionUID = 3093736618740652951L;
private final E element;
SingletonList(E obj) {
element = obj;
}
}
结尾
当然咯,也不是禁止使用Collections.singletonList
和Arrays.asList(T... a)
,只是我们在使用的时候一定要区分一下场景,如果创建的是一个不会再添加元素的List,那么则可以使用
但我们平时不想写那么麻烦,想要在创建的时候就把元素塞到List中,那咋办呢?
我们其实能使用google的工具类
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
如下写法
@Slf4j
@SpringBootTest
public class Demo {
public static void main(String[] args) {
List<String> strings = Lists.newArrayList("Code皮皮虾", "哈哈哈");
strings.add("憨憨熊");
System.out.println(strings);
}
}
其内部已经为我们封装好了,拿来即用即可,哈哈
@SafeVarargs
@CanIgnoreReturnValue // TODO(kak): Remove this
@GwtCompatible(serializable = true)
public static <E> ArrayList<E> newArrayList(E... elements) {
checkNotNull(elements); // for GWT
// Avoid integer overflow when a large array is passed in
int capacity = computeArrayListCapacity(elements.length);
ArrayList<E> list = new ArrayList<>(capacity);
Collections.addAll(list, elements);
return list;
}
我是 皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!
今天的文章假ArrayList导致的线上事故……分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19275.html