最近在看 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
中添加了a
和b
,并在后续操作中移除了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)
方法,a
和b
的值当然想等咯(都等于 200 ),于是就出现了上述的怪状。
至于contains(Object o)
方法,其内部判断元素是否存在时,就是利用indexOf(Object o)
方法查询某个元素的索引,若返回值为大于或等于零(即不为 -1 ),则表示该元素存在,返回true
。所以也出现这样的情况。其实remove(Object o)
方法也在其内部用了equals(Object obj)
方法作比较,这里暂且不表。
如果不了解这种重写了equals(Object obj)
方法的类和一些使用equals(Object obj)
方法作比较的类,就会很容易出现误解,因此还需要多多阅读源码呀!
比如不了解
StringBuffer
和StringBuilder
的话,很容易理所当然的想既然String
重写了equals(Object obj)
,从而可以直接进行值比较,就认为StringBuffer
和StringBuilder
也应当如此,但是却并非这样。StringBuffer
和StringBuilder
都没有重写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 。本文链接:
- Github 博客:https://sooxin.github.io/posts/2017/9/14/lost-in-methods-of-ArrayList-in-java.html
- 简书:http://www.jianshu.com/p/394d370cd6fe
转载请保留本署名。