最近這一周總是迷迷糊糊的,各種踩坑,這不朴摊,踩了一個(gè) ArrayList 的 remove 方法的坑鲸鹦,下面我就來介紹一下這個(gè)方法的坑。膝擂。。
/** 初始化一個(gè)集合 */
private static List<Integer> integers = Lists.newArrayList(1, 2, 2, 3, 4, 25);
/**
* 使用 main 方法調(diào)用
* 想調(diào)用哪個(gè)方法就將哪個(gè)方法解開注釋吧
* @param args
*/
public static void main(String[] args) {
remove();
// foreachRemove();
// foreachRemove2();
// iteratorRemove();
// iteratorRemove2();
// removeEven();
// 遍歷集合
integers.stream().forEach(System.out::println);
}
初始化一個(gè)集合列表,使用 main 方法調(diào)用醋奠,接下來就讓我們看看有哪些坑吧。
/**
* remove(int index)伊佃、remove(Object o) 猜猜下面刪除的到底是哪個(gè)元素
*/
static void remove() {
// 在這里窜司,25 不會(huì)自動(dòng)裝箱,表示的還是索引值
integers.remove(25);
}
看到注釋中的兩個(gè)方法航揉,一個(gè)是根據(jù)索引刪除指定的值塞祈,一個(gè)是刪除指定對(duì)象。當(dāng)集合中存儲(chǔ)的是 Integer 類型的時(shí)候帅涂,傳入數(shù)字1议薪,刪除到底是根據(jù)索引還是根據(jù)對(duì)象刪除呢?我們猜一下吧媳友,根據(jù)對(duì)象刪除斯议!
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 25, Size: 6
咦,怎么報(bào)錯(cuò)了醇锚,根據(jù)報(bào)錯(cuò)的信息我們很清楚的看到哼御,ArrayList 是根據(jù)索引來刪除元素的坯临,由于沒有索引值為 25 的元素,所以就報(bào)越界異常了恋昼。其實(shí)我們了解重載方法我們就會(huì)知道 Java 會(huì)根據(jù)重載方法精確匹配看靠,看下面的重載方法就明白了。
static void revoew(int i) {
System.out.println("我是普通變量");
}
static void revoew(Integer i) {
System.out.println("我是對(duì)象");
}
public static void main(String[] args) {
revoew(1);
}
// 輸出
我是普通變量
跨過重載刪除的坑液肌,我們來看看迭代器中使用 remove 方法刪除的坑吧挟炬。
/**
* 使用迭代器刪除指定元素,兩個(gè)元素都出現(xiàn)在元素中的前幾個(gè)
*/
static void iteratorRemove() {
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (Integer.valueOf(1).equals(next)) {
integers.remove(next);
}
if (Integer.valueOf(2).equals(next)) {
integers.remove(next);
}
}
}
來嗦哆,猜猜上面方法會(huì)輸出啥谤祖?我猜應(yīng)該輸出 3 4 5,運(yùn)行 main 方法試試老速。泊脐。。
Exception in thread "main" java.util.ConcurrentModificationException
納尼烁峭?怎么報(bào)出了個(gè)并發(fā)修改的異常叭菘汀!我明明跑的是單線程啊约郁,嚇得我趕緊看斷點(diǎn)調(diào)一調(diào)到底是哪個(gè)地方拋出來的異常缩挑。
源碼如下
public E next() {
// 判斷當(dāng)前鏈表結(jié)構(gòu)是否被改變,如果改變了則 拋出 ConcurrentModificationException 異常
checkForComodification();
// 游標(biāo)鬓梅,迭代器每改變 list 中的結(jié)構(gòu)游標(biāo)都會(huì)改變供置,前提是通過迭代器來修改,否則游標(biāo)不會(huì)改變
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 當(dāng)數(shù)組中元素個(gè)數(shù)小于游標(biāo)值時(shí)绽快,就會(huì)報(bào)出下面這個(gè)錯(cuò)誤芥丧,也就是我們要找的
// 使用迭代器的時(shí)候不要使用 list 的 remove 方法,而要使用 迭代器的remove 方法的原因
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
現(xiàn)在終于明白為啥會(huì)報(bào)這個(gè)錯(cuò)了坊罢,弄懂了這個(gè)坑续担,下一個(gè)坑又來了。代碼如下:
/**
* 使用迭代器刪除指定元素活孩,刪除指定元素物遇,兩個(gè)元素是集合中最后兩個(gè)
*/
static void iteratorRemove2() {
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (Integer.valueOf(4).equals(next)) {
integers.remove(next);
}
if (Integer.valueOf(25).equals(next)) {
integers.remove(next);
}
}
}
大家一看,肯定會(huì)說憾儒,這個(gè)方法和上面那個(gè)方法不是類似么询兴,肯定一運(yùn)行就報(bào)并發(fā)修改的那個(gè)錯(cuò)。還別說起趾,真不是報(bào)那個(gè)錯(cuò)诗舰,而且還有結(jié)果輸出呢!
1
2
2
3
25
咦训裆,為啥沒有報(bào)錯(cuò)眶根?4 也被刪除了蜀铲,可是為啥 25 還在呢,可怕汛闸,有 bug蝙茶。冷靜分析一波艺骂,4 和 25 是集合中最后兩個(gè)元素诸老,刪除 4 的時(shí)候,迭代器的游標(biāo)值為 4钳恕,當(dāng)把 4 刪掉之后别伏,游標(biāo)還是 4 ,而size 也是4 忧额,那么繼續(xù)循環(huán)應(yīng)該會(huì)報(bào)錯(cuò)才對(duì)厘肮。但此時(shí) iterator.hashNext() 返回的卻是 false,直接終止循環(huán)睦番,那么也就不會(huì)調(diào)用 iterator.next() 報(bào)錯(cuò)了类茂。因?yàn)楦揪筒粫?huì)執(zhí)行了,那現(xiàn)在大家應(yīng)該會(huì)問托嚣。25 去哪了巩检?從結(jié)果看 25 并沒有被刪除。那么我們看看 remove 方法示启,由于 remove 最終還是調(diào)用 fastRemove(int index) ,直接看這個(gè)方法
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
// 刪除一個(gè)元素兢哭,數(shù)組會(huì)將刪除的元素后面的元素往前移
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
}
正是因?yàn)?4 后面的元素往前移動(dòng)了一個(gè)位置,所以 25 就放到了原來 4 的位置夫嗓,而 iterator 指針還是指向4的位置迟螺,那么調(diào)用 iterator.hashNext() 發(fā)現(xiàn)沒有元素了,所以就退出循環(huán)舍咖,這也就是為什么不會(huì)報(bào)錯(cuò)矩父,而且 25 還沒有被刪除的原因。
下面我們紀(jì)錄一下迭代器的字節(jié)碼排霉,后面會(huì)用到
static void iteratorRemove();
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: getstatic #3 // Field integers:Ljava/util/List;
3: invokeinterface #10, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
8: astore_0
9: aload_0
10: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
15: ifeq 73
18: aload_0
19: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
24: checkcast #13 // class java/lang/Integer
27: astore_1
28: iconst_1
29: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
32: aload_1
33: invokevirtual #15 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
36: ifeq 49
39: getstatic #3 // Field integers:Ljava/util/List;
42: aload_1
43: invokeinterface #16, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
48: pop
49: iconst_2
50: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
53: aload_1
54: invokevirtual #15 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
57: ifeq 70
60: getstatic #3 // Field integers:Ljava/util/List;
63: aload_1
64: invokeinterface #16, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
69: pop
70: goto 9
73: return
LineNumberTable:
line 77: 0
line 78: 9
line 79: 18
line 80: 28
line 81: 39
line 83: 49
line 84: 60
line 86: 70
line 87: 73
LocalVariableTable:
Start Length Slot Name Signature
28 42 1 next Ljava/lang/Integer;
9 65 0 iterator Ljava/util/Iterator;
LocalVariableTypeTable:
Start Length Slot Name Signature
9 65 0 iterator Ljava/util/Iterator<Ljava/lang/Integer;>;
StackMapTable: number_of_entries = 4
frame_type = 252 /* append */
offset_delta = 9
locals = [ class java/util/Iterator ]
frame_type = 252 /* append */
offset_delta = 39
locals = [ class java/lang/Integer ]
frame_type = 250 /* chop */
offset_delta = 20
frame_type = 2 /* same */
好啦浙垫!迭代器中使用 list 的 remove 方法刪除元素的坑踩完了,我們踩踩 foreach 循環(huán)里面刪除元素的坑吧郑诺,代碼如下:
/**
* 使用 foreach 循環(huán)夹姥,刪除指定元素,兩個(gè)元素都出現(xiàn)在元素中的前幾個(gè)
*/
static void foreachRemove() {
for (Integer integer : integers) {
if (Integer.valueOf(1).equals(integer)) {
integers.remove(integer);
}
if (Integer.valueOf(2).equals(integer)) {
integers.remove(integer);
}
}
}
/**
* 使用 foreach 循環(huán)辙诞,刪除指定元素辙售,兩個(gè)元素是集合中最后兩個(gè)
*/
static void foreachRemove2() {
for (Integer integer : integers) {
if (Integer.valueOf(3).equals(integer)) {
integers.remove(integer);
}
if (Integer.valueOf(4).equals(integer)) {
integers.remove(integer);
}
}
}
這兩個(gè)方法和上面迭代器的坑是一樣的,我們看看 forEach循環(huán)的字節(jié)碼吧
static void foreachRemove();
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: getstatic #3 // Field integers:Ljava/util/List;
3: invokeinterface #10, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
8: astore_0
9: aload_0
10: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
15: ifeq 73
18: aload_0
19: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
24: checkcast #13 // class java/lang/Integer
27: astore_1
28: iconst_1
29: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
32: aload_1
33: invokevirtual #15 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
36: ifeq 49
39: getstatic #3 // Field integers:Ljava/util/List;
42: aload_1
43: invokeinterface #16, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
48: pop
49: iconst_2
50: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
53: aload_1
54: invokevirtual #15 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
57: ifeq 70
60: getstatic #3 // Field integers:Ljava/util/List;
63: aload_1
64: invokeinterface #16, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
69: pop
70: goto 9
73: return
LineNumberTable:
line 49: 0
line 50: 28
line 51: 39
line 53: 49
line 54: 60
line 56: 70
line 57: 73
LocalVariableTable:
Start Length Slot Name Signature
28 42 1 integer Ljava/lang/Integer;
StackMapTable: number_of_entries = 4
frame_type = 252 /* append */
offset_delta = 9
locals = [ class java/util/Iterator ]
frame_type = 252 /* append */
offset_delta = 39
locals = [ class java/lang/Integer ]
frame_type = 250 /* chop */
offset_delta = 20
frame_type = 250 /* chop */
offset_delta = 2 /* same */
有沒有發(fā)現(xiàn) forEach 字節(jié)碼和迭代器的字節(jié)碼一樣飞涂,沒錯(cuò)旦部,就是一樣祈搜,連坑都一樣,知道上面的坑后那么 forEach 里面的坑也就明白是怎么一回事了士八。
正確的刪除方法, 應(yīng)該使用迭代器里面的 remove 方法
static void correctRemove() {
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()){
Integer next = iterator.next();
if (Integer.valueOf(1).equals(next)) {
iterator.remove();
}
if (Integer.valueOf(2).equals(next)) {
iterator.remove();
}
}
}
總結(jié):以上就是我使用 ArrayList 的 remove 方法遇到的一些坑容燕,都是自己使用了不正確的方法來刪除集合中的元素,希望大家以后都是用正確的刪除方式刪除集合中的元素婚度。Iterator 里面有 remove 方法蘸秘,存在即是道理。走上正確的道路才能成功;茸隆4茁病!