看了这一篇,让你少踩 ArrayList 的那些坑

我是石头,,一个在程序圈混迹多年,主业 Java,另外 Python、React 也玩儿的 6 的斜杠开发者。

请看下面的代码,谁能看出它有什么问题吗?

Stringa ="古时的";

Stringb  ="石头";

List 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 方法看一看。

publicstaticListasList(T... a){

returnnewArrayList<>(a);

}

咦,是 new 了一个 ArrayList出来呀,怎么会不支持 add操作呢,不仔细看还真容易被唬住,此ArrayList非彼ArrayList,这是一个内部类,但是类名也叫 ArrayList,你说坑不坑。

privatestaticclassArrayListextendsAbstractList

implementsRandomAccess,java.io.Serializable{

privatestaticfinallongserialVersionUID = -2764017481108945198L;

privatefinalE[] a;

ArrayList(E[] array) {

a = Objects.requireNonNull(array);

}

@Override

publicintsize(){

returna.length;

}

@Override

publicObject[] toArray() {

returna.clone();

}

@Override

@SuppressWarnings("unchecked")

public T[] toArray(T[] a) {

intsize = size();

if(a.length < size)

returnArrays.copyOf(this.a, size,

(Class) a.getClass());

System.arraycopy(this.a,0, a,0, size);

if(a.length > size)

a[size] =null;

returna;

}

@Override

publicEget(intindex){

returna[index];

}

@Override

publicEset(intindex, E element){

E oldValue = a[index];

a[index] = element;

returnoldValue;

}

@Override

publicintindexOf(Object o){

E[] a =this.a;

if(o ==null) {

for(inti =0; i < a.length; i++)

if(a[i] ==null)

returni;

}else{

for(inti =0; i < a.length; i++)

if(o.equals(a[i]))

returni;

}

return-1;

}

@Override

publicbooleancontains(Object o){

returnindexOf(o) != -1;

}

@Override

publicSpliteratorspliterator(){

returnSpliterators.spliterator(a, Spliterator.ORDERED);

}

@Override

publicvoidforEach(Consumer action){

Objects.requireNonNull(action);

for(E e : a) {

action.accept(e);

}

}

@Override

publicvoidreplaceAll(UnaryOperator<E> operator){

Objects.requireNonNull(operator);

E[] a =this.a;

for(inti =0; i < a.length; i++) {

a[i] = operator.apply(a[i]);

}

}

@Override

publicvoidsort(Comparator c){

Arrays.sort(a, c);

}

}

里面定义了 set、get等基本的方法,但是没有重写add方法,这个类也是继承了 AbstractList,但是 add方法并没有具体的实现,而是抛了异常出来,具体的逻辑需要子类自己去实现的。

publicvoidadd(intindex, E element){

thrownewUnsupportedOperationException();

}

所以说,Arrays.asList方法创建出来的 ArrayList 和真正我们平时用的 ArrayList只是继承自同一抽象类的两个不同子类,而 Arrays.asList创建的 ArrayList 只能做一些简单的视图使用,不能做过多操作,所以 ArrayList的几种初始化方式里没有 Arrays.asList这一说。

subList 方法

上面提到了那个问题和 subList的坑有异曲同工之妙,都是由于返回的对象并不是真正的 ArrayList类型,而是和 ArrayList集成同一父类的不同子类而已。

坑之一

所以会产生第一个坑,就是把当把 subList返回的对象转换成 ArrayList 的时候

List stringList =newArrayList<>();

stringList.add("我");

stringList.add("是");

stringList.add("石头");

List subList = (ArrayList) stringList.subList(0,2);

会抛出下面的异常:

java.lang.ClassCastException: java.util.ArrayList$SubListcannot be cast to java.util.ArrayList

原因很明了,因为这俩根本不是一个对象,也不存在继承关系,如果真说有什么关系,顶多算是兄弟关系,因为都继承了 AbstractList 嘛 。

坑之二

当你在 subList 中操作的时候,其实就是在操作原始的 ArrayList,不明所以的同学以为这是一个副本列表,然后在 subList 上一顿操作猛如虎,最后回头一看原始 ArrayList已然成了二百五。

例如下面这段代码,在 subList 上新增了一个元素,然后又删除了开头的一个元素,结果回头一看原始的 ArrayList,发现它的结果也发生了变化。

List stringList =newArrayList<>();

stringList.add("我");

stringList.add("是");

stringList.add("石头");

List 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 这个方法的源码。

publicListsubList(intfromIndex,inttoIndex){

subListRangeCheck(fromIndex, toIndex, size);

returnnewSubList(this,0, fromIndex, toIndex);

}

看到它内部是 new 了一个 SubList 类,这个类就是上面提到的 ArrayList的子类,看到第一个参数 this了吗,this就是当前的 ArrayList 原始列表,之后的增删改其实都是在 this上操作,最终也就是在原始列表上进行的操作,所以你的一举一动最后都会诚实的反应到原始列表上,之后你再想用原始列表,对不起,已经找不到了。

坑之三

如果你使用 subList 方法获取了一个子列表,这之后又在原始列表上进行了新增或删除的操作,这是,你之前获取到的 subList 就已经废掉了,不能用了,不能用的意思就是你在 subList 上进行遍历、增加、删除操作都会抛出异常,没错,连遍历都不行了。

例如下面这段代码

List stringList =newArrayList<>();

stringList.add("我");

stringList.add("是");

stringList.add("石头");

List 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异常。

privatevoidcheckForComodification(){

if(ArrayList.this.modCount !=this.modCount)

thrownewConcurrentModificationException();

}

最后

没有在项目中踩过 JDK 坑的程序员,不足以谈人生。所以,各位同学在使用一些看似简单、优雅的方法时,一定要清楚它的特性和原理,不然就离坑不远了。

壮士且慢,先给点个赞吧,总是被白嫖,身体吃不消!

作者:古时的风筝

链接:https://juejin.im/post/5ed066fe6fb9a047a8622134    

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345