读了这一篇,让你少踩 ArrayList 的那些坑
我是风筝,公众号「古时的风筝」,一个不只有技术的技术公众号,一个在程序圈混迹多年,主业 Java,另外 Python、React 也玩儿的 6 的斜杠开发者。
Spring Cloud 系列文章已经完成,可以到 我的github 上查看系列完整内容。也可以在公众号内回复「pdf」获取我精心制作的 pdf 版完整教程。
请看下面的代码,谁能看出它有什么问题吗?
String a = "古时的";
String b = "风筝";
List<String> stringList = Arrays.asList(a,b);
stringList.add("!!!");
这是一个小白程序员问我的问题。
他说:成哥,帮我看看这代码有什么问题吗,为什么报错呢,啥操作都没有啊?
我:看上去确实没什么问题,但是我确实没用过 Arrays.asList
这个方法,报什么错误?
他:异常信息是 java.lang.UnsupportedOperationException
,是调用 add
方法时抛出的。
恩,我大概明白了,这可能是 ArrayList
的又一个坑,和 subList
应该有异曲同工之妙。
Arrays.asList
Arrays.asList
方法接收一个变长泛型,最后返回 List,好像是个很好用的方法啊,有了它,我们总是说的 ArrayList
初始化方式是不是就能更优雅了,既不用{{
这种双括号方式,也不用先 new ArrayList
,然后再调用 add
方法一个个往里加了。但是,为啥没有提到这种方式呢?
虽然问题很简单,但还是有必要看一下原因的。于是,写了上面这 4 行代码做个测试,运行起来确实抛了异常,异常如下:
直接看源码吧,定位到 Arrays.asList
方法看一看。
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
咦,是 new 了一个 ArrayList
出来呀,怎么会不支持 add
操作呢,不仔细看还真容易被唬住,此ArrayList
非彼ArrayList
,这是一个内部类,但是类名也叫 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);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
里面定义了 set
、get
等基本的方法,但是没有重写add
方法,这个类也是继承了 AbstractList
,但是 add
方法并没有具体的实现,而是抛了异常出来,具体的逻辑需要子类自己去实现的。
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
所以说,Arrays.asList
方法创建出来的 ArrayList
和真正我们平时用的 ArrayList
只是继承自同一抽象类的两个不同子类,而 Arrays.asList
创建的 ArrayList
只能做一些简单的视图使用,不能做过多操作,所以 ArrayList
的几种初始化方式里没有 Arrays.asList
这一说。
subList 方法
上面提到了那个问题和 subList
的坑有异曲同工之妙,都是由于返回的对象并不是真正的 ArrayList
类型,而是和 ArrayList
集成同一父类的不同子类而已。
坑之一
所以会产生第一个坑,就是把当把 subList
返回的对象转换成 ArrayList
的时候
List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");
List<String> subList = (ArrayList) stringList.subList(0, 2);
会抛出下面的异常:
java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
原因很明了,因为这俩根本不是一个对象,也不存在继承关系,如果真说有什么关系,顶多算是兄弟关系,因为都继承了 AbstractList
嘛 。
坑之二
当你在 subList 中操作的时候,其实就是在操作原始的 ArrayList
,不明所以的同学以为这是一个副本列表,然后在 subList 上一顿操作猛如虎,最后回头一看原始 ArrayList
已然成了二百五。
例如下面这段代码,在 subList 上新增了一个元素,然后又删除了开头的一个元素,结果回头一看原始的 ArrayList,发现它的结果也发生了变化。
List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");
List<String> subList = stringList.subList(0, 3);
subList.add("!!!");
subList.remove(0);
System.out.println("------------------");
System.out.println("修改后的 subList");
System.out.println("------------------");
for (String s : subList) {
System.out.println(s);
}
System.out.println("------------------");
System.out.println("原始 ArrayList");
System.out.println("------------------");
for (String a : stringList) {
System.out.println(a);
}
以上代码的输出结果:
------------------
修改后的 subList
------------------
是
风筝
!!!
------------------
原始 ArrayList
------------------
是
风筝
!!!
为什么会发生这样的情况呢,因为 subList
的实现就是这样子啊,捂脸。我们可以看一下 subList 这个方法的源码。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
看到它内部是 new 了一个 SubList 类,这个类就是上面提到的 ArrayList
的子类,看到第一个参数 this
了吗,this
就是当前的 ArrayList
原始列表,之后的增删改其实都是在 this
上操作,最终也就是在原始列表上进行的操作,所以你的一举一动最后都会诚实的反应到原始列表上,之后你再想用原始列表,对不起,已经找不到了。
坑之三
如果你使用 subList 方法获取了一个子列表,这之后又在原始列表上进行了新增或删除的操作,这是,你之前获取到的 subList 就已经废掉了,不能用了,不能用的意思就是你在 subList 上进行遍历、增加、删除操作都会抛出异常,没错,连遍历都不行了。
例如下面这段代码
List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");
List<String> subList = stringList.subList(0, 3);
// 原始列表元素个数改变
stringList.add("!!!");
// 遍历 subList
for (String s : subList) {
System.out.println(s);
}
// get 元素
subList.get(0);
// remove 元素
subList.remove(0);
//增加元素
subList.add("hello");
遍历、get、remove、add 都会抛出以下异常
其实与二坑的原因相同,subList 其实操作的是原始列表,当你在 subList 上进行操作时,会执行 checkForComodification
方法,此方法会检查原始列表的个数是否和最初的相同,如果不相同,直接抛出 ConcurrentModificationException
异常。
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
最后
没有在项目中踩过 JDK 坑的程序员,不足以谈人生。所以,各位同学在使用一些看似简单、优雅的方法时,一定要清楚它的特性和原理,不然就离坑不远了。
壮士且慢,先给点个推荐吧,总是被白嫖,身体吃不消!
我是风筝,公众号「古时的风筝」。一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!你可选择现在就关注我,或者看看历史文章再关注也不迟。
读了这一篇,让你少踩 ArrayList 的那些坑的更多相关文章
- 搭载Dubbo+Zookeeper踩了这么多坑,我终于决定写下这篇!
大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦. 这篇文章我们不谈数据结构了,来谈谈入门分布式踩过的坑.感觉到了分布式这一层,由于技术更新迭代 ...
- Redis上踩过的一些坑
来自: http://blog.csdn.net//chenleixing/article/details/50530419 上上周和同事(龙哥)参加了360组织的互联网技术训练营第三期,美团网的DB ...
- 安装python爬虫scrapy踩过的那些坑和编程外的思考
这些天应朋友的要求抓取某个论坛帖子的信息,网上搜索了一下开源的爬虫资料,看了许多对于开源爬虫的比较发现开源爬虫scrapy比较好用.但是以前一直用的java和php,对python不熟悉,于是花一天时 ...
- [原创]阿里云RocketMQ踩过的哪些坑
由于公司的最近开始使用RocketMQ来做支付业务处理, 便开启了学习阿里云RocketMQ的学习与实践之路, 其中踩了不少的坑, 大部份是由于没有仔细查看阿里云的技术文档而踩的坑. 但是有一个非常大 ...
- [转帖]美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题
美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题 博客分类: redis 运维 redis clustercluster-node-timeoutfailover 转载请 ...
- [转帖]美团在Redis上踩过的一些坑-4.redis内存使用优化
美团在Redis上踩过的一些坑-4.redis内存使用优化 博客分类: 运维 redis redisstringhash优化segment-hash 转载请注明出处哈:http://carlosfu ...
- [转帖]美团在Redis上踩过的一些坑-1.客户端周期性出现connect timeout
美团在Redis上踩过的一些坑-1.客户端周期性出现connect timeout 博客分类: redis 运维 jedisconnect timeoutnosqltcp 转载请注明出处哈:http ...
- 三分之一的程序猿之社交类app踩过的那些坑
三分之一的程序猿之社交类app踩过的那些坑 万众创新,全民创业.哪怕去年陌生人社交不管融资与否都倒闭了不知道多少家,但是依然有很多陌生人社交应用层出不穷的冒出来.各种脑洞大开,让人拍案叫起. 下面我们 ...
- 【Fine原创】JMeter分布式测试中踩过的那些坑
最近因为项目需要,研究了性能测试的相关内容,并且最终选用了jmeter这一轻量级开源工具.因为一直使用jmeter的GUI模式进行脚本设计,到测试执行阶段工具本身对资源的过量消耗给性能测试带来了瓶颈, ...
随机推荐
- python的unittest框架中的assert断言
unittest框架自带断言,如果想用assert断言,一定要引入unittest.TestCase框架才行,不然不会自动识别assert断言
- django源码分析——本地runserver分析
本文环境python3.5.2,django1.10.x系列 1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据django的官方实例此时 ...
- 工厂模式(factory pattern)
工厂模式主要用来封装对象的创建,有3种分类:简单工厂(simple factory).工厂方法(factory method).抽象工厂(abstract factory). 简单工厂包括3种组成元素 ...
- php使用curl post josn数据
今天在工作中使用到要使用("Content-Type", "application/json;charset=UTF-8")格式传送和接受数据,再次做个记录 p ...
- Tomcat服务器的下载与安装,修改端口号
安装及简单配置Tomcat服务器: 1.登录www.apache.org 网站,之后点击Projects , 点击Project List,找到Tomcat. 2.点击Tomcat之后,之后进入Tom ...
- 【poj 2406】Power Strings 后缀数组DC3模板 【连续重复子串】
Power Strings 题意 给出一个字符串s,求s最多由几个相同的字符串重复而成(最小循环节的重复次数) 思路 之前学习KMP的时候做过. 我的思路是:枚举字符串的长度,对于当前长度k,判断\( ...
- bootstrap基本页面
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...
- php基本语法学习
1.基本的 PHP 语法 PHP 脚本可以放在文档中的任何位置. PHP 脚本以 <?php 开始,以 ?> 结束: <?php// PHP 代码?> 2.简单的脚本-输出 ...
- C# Sign In With Apple苹果登陆后端验证
苹果App授权登录 苹果官方的授权文档: 生成Token:https://developer.apple.com/documentation/sign_in_with_apple/generate_a ...
- node的url模块
.parse(url,query2obj[boolean],ignorePrototype[boolean]) .format({}) 和.parse相反,将带有url参数属性的对象组装成url .r ...