Java 中的 ArrayList 踩坑记

最近在看 JDK8 中java.util.ArrayList的源码,发现其中一些方法的精妙,也启发了我写代码的一些方式。

除此以外,阅读中我注意到ArrayList里一些方法的内部实现,不加注意的话,在使用该方法过程中容易造成一些不必要的麻烦。

本文只提两个方法。

indexOf(Object o)

indexOf(Object o)方法用来返回某个元素在ArrayList实例中的索引,若这个元素不存在,则返回 -1 。需要注意的是这个方法内部比较非null元素时,使用的是equals(Object obj)方法。这本不是什么大问题,但是对有些重写了equals(Object obj)方法的类来说,就需要注意了。

举个例子,运行下面这串代码:

ArrayList<Integer> arr = new ArrayList<Integer>();
Integer a = new Integer(200);
Integer b = new Integer(200);
// 添加 a、b 到 arr 中
arr.add(a);
arr.add(b);
// 打印 arr
System.out.println("arr" + arr.toString());
// 移除 arr 中的元素 a
arr.remove(a);
// 打印移除 a 后的 arr
System.out.println("arr" + arr.toString());
// 打印 a 的索引
System.out.println("a 的索引:" + arr.indexOf(a));
// 打印 arr 中是否存在 a
System.out.println("a 是否存在:" + arr.contains(a));

输出结果如下:

arr[200, 200]
arr[200]
a 的索引:0
a 是否存在:true

结果变得奇怪了,我们虽然一开始在arr中添加了ab,并在后续操作中移除了a,但是查询a的索引和a的存在时,却出现了意想不到的结果。

其实看看indexOf(Object o)方法的源码就知道了,源码如下:

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int indexOf(Object o) {
  if (o == null) {
    for (int i = 0; i < size; i++)
      if (elementData[i]==null)  return i;
  } else { // o 不为 null时
    for (int i = 0; i < size; i++)
      // 使用 equals() 方法判断元素是否相等
      if (o.equals(elementData[i]))  return i;
  }
  // 未查找到指定元素,则返回 -1
  return -1;
}

可以看到,在传入参数不为null的时候,使用了equals(Object obj)方法来判断参数是否与集合中的元素相等。我们知道在没有重写的情况下,equals(Object obj)方法内部其实就是使用==作比较,相当于比较两者的内存地址是否相等。而不巧的是Integer等一些特殊的类(如String),都有重写equals(Object obj)方法,因此变成了比较值,而非比较内存地址。这样就很清楚了,虽然用remove(a)删去了a,但是在indexOf(a)中,比较a与留在arr中的元素用的是equals(Object obj)方法,ab的值当然想等咯(都等于 200 ),于是就出现了上述的怪状。

至于contains(Object o)方法,其内部判断元素是否存在时,就是利用indexOf(Object o)方法查询某个元素的索引,若返回值为大于或等于零(即不为 -1 ),则表示该元素存在,返回true。所以也出现这样的情况。其实remove(Object o)方法也在其内部用了equals(Object obj)方法作比较,这里暂且不表。

如果不了解这种重写了equals(Object obj)方法的类和一些使用equals(Object obj)方法作比较的类,就会很容易出现误解,因此还需要多多阅读源码呀!

比如不了解StringBufferStringBuilder的话,很容易理所当然的想既然String重写了equals(Object obj),从而可以直接进行值比较,就认为StringBufferStringBuilder也应当如此,但是却并非这样。StringBufferStringBuilder都没有重写equals(Object obj)方法,因此调用方法还是进行的内存地址的比较(相当于使用==)。

remove(int index)

众所周知,remove(int index)用来删除集合中指定索引处的元素,似乎不会出现什么问题,那么先来看一个例子:

ArrayList<String> arr = new ArrayList<String>();
// 此处直接定义一个索引,通常应该由某个业务方法返回
Integer index = 0;
// 随意添加两个元素
arr.add("hello");
arr.add("world");
System.out.println("arr" + arr.toString());
// 删除 index 索引处的 "hello"
arr.remove(index);
// 再次打印 arr 集合
System.out.println("arr" + arr.toString());

输出结果如下:

arr[hello, world]
arr[hello, world]

相信有很多人已经看出原因所在了,如果没有看出请继续往下阅读。

这里我们先创建了一个集合对象arr,并分别放入两个字符串,接着我们想要删除索引index处的元素,即“hello”字符串,但是调用remove()后并未出现想要的结果,元素并未被删除。

那么肯定是哪里出了问题。我们看一下remove()方法的源码,可以发现其实有两个remove()方法。

// 第一个 remove() 方法
public E remove(int index) {
  rangeCheck(index);

  modCount++;
  E oldValue = elementData(index);

  int numMoved = size - index - 1;
  if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,numMoved);
        
  elementData[--size] = null; // clear to let GC do its work
  return oldValue;
}

// 第二个 remove 方法
public boolean remove(Object o) {
  if (o == null) {
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);
        return true;
      }
  } else {
    for (int index = 0; index < size; index++)
      if (o.equals(elementData[index])) {
        fastRemove(index);
        return true;
      }
  }
  return false;
}

还出现了个remove(Object o)重载的方法,这个方法我们也不陌生。显然原来的代码中arr.remove(index)没有调用第一个remove(int index)。再仔细看看代码,index实际上是Integer类的对象,因此我们的代码最终调用的是第二个remove(Object o)。于是我们原意想删除索引 0 处的元素变成了删除元素中与index对应(equals)的元素。

其实这两个方法因传入的参数类型不同,乍看之下很容易区分。但是这里的坑在于有时使用者没有注意到自己的索引是一个包装类Integer对象,从而导致了想要调用的方法和实际调用方法不符。

后记:

仔细想一想,其实所谓的“坑”都是自己了解的过少导致的,如果不深入学习,迟早会遇到更多的坑,这些“后果”都是有“前因”的。阅读源码能学到很多东西,不只是上面提到的方法细节,还有一些高效率的方法以及代码风格,都值得去慢慢咀嚼。加油努力吧!

本文由 Sooxin 创建于 2017-09-14 。本文链接:

转载请保留本署名。

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

推荐阅读更多精彩内容

  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,477评论 0 3
  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX阅读 861评论 0 1
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,514评论 18 399
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,246评论 0 16
  • 2016/01/29 『白月光』 阳光透过了纱窗 温暖的荡起了波漾 想要呼吸新鲜de胃道 却被玻璃生生止住了脚步。...
    蒙奇奇_阿蒙蛮好的阅读 212评论 0 1