Java中ArrayList remove()的不良行为[重复]

Posted

技术标签:

【中文标题】Java中ArrayList remove()的不良行为[重复]【英文标题】:Undesired behavior of ArrayList remove() in Java [duplicate] 【发布时间】:2015-08-01 16:47:18 【问题描述】:

我有以下两种情况:

1. int 值作为参数

int intNum = 2;
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.remove(intNum);
System.out.println(list.size());
// output: 2

2。长值作为参数

long longNum = 2;
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.remove(longNum);
System.out.println(list.size());
// output: 3

在这两种情况下,我都将 2 作为值传递,但我得到的列表大小值不同。这种行为的真正原因是什么?

Properly removing an Integer from a List 没有解释关于具有相同值但行为不同的内置数据类型,如上所述

【问题讨论】:

现在您看到 list.remove(2) 正在删除索引 2 处的元素而不是整数 2,您应该指出明确转换为“int”或“Integer”,否则您只会冒险迷惑自己。 这可能是一个技术面试的好问题。 【参考方案1】:

自动装箱

list.remove方法被重载,两个不同的签名用于不同的目的。一个,list.remove(int),根据其索引删除一个项目,另一个,list.remove(Object),根据对象相等性删除一个项目。 您的第一种情况触发第一种类型,而您的第二个示例(使用long longNum)触发第二种类型,将long 原语自动装箱为java.lang.Long 对象。这不等于添加到列表中的java.lang.Integer(自动装箱)值,因此不会从列表中删除任何内容并且大小将保持不变。

【讨论】:

而第二种情况之所以选择Object重载是因为long不能转换为intObject重载是唯一可以申请的, 即使第一种情况触发了第二种情况,结果也会不一样。【参考方案2】:

来自 List.remove() documentation :

remove(int index) 删除指定位置的元素 此列表(可选操作)。

remove(Object o) 删除指定元素的第一个匹配项 从这个列表中,如果它存在(可选操作)。

removeAll(Collection c) 从此列表中删除其所有元素 包含在指定集合中(可选操作)。

如果您的第二个示例确实很长,则不会将其删除(因为它使用了第二个删除方法)。

【讨论】:

【参考方案3】:

小心:第一个删除了index = 2 处的Integer。见ArrayList.remove(int)

第二个尝试使用ArrayList.remove(Object) 删除对象,但您要删除的对象不存在,因为它是Long 对象。

【讨论】:

【参考方案4】:

List 接口包含两个 remove() 方法 - remove(Object)remove(int)

remove(Object)在Java 6中的实现如下:

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(int)在Java 6中的实现是:

public E remove(int index) 
RangeCheck(index);

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

int numMoved = size - index - 1;
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
elementData[--size] = null; // Let gc do its work

return oldValue;

在您的第一个示例中,您实际上调用了remove(int) 方法,该方法删除了指定索引处的对象。在本例中,您指定了索引 2,它实际上是值“3”。

在您的第二个示例中,您正在调用remove(Object) 方法,因为没有remove(long) 方法并且long 不会转换为int。基于remove(Object) 方法的实现,它寻找对象相等性。由于您的列表包含 Integer 类型的对象,并且您提供的是 Long,因此没有任何匹配项。

以下方法可能是正在发生的事情的更好示例:

public static void main(String[] args) 
    ArrayList<Integer> list;

    System.out.println("Removing intNum");
    int intNum = 2;
    list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    System.out.println("List = " + list);
    list.remove(intNum);
    System.out.println("List = " + list);

    System.out.println("Removing longNum");
    long longNum = 2;
    list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    System.out.println("List = " + list);
    list.remove(longNum);
    System.out.println("List = " + list);

这段代码的输出是:

Removing intNum
List = [1, 2, 3]
List = [1, 2]
Removing longNum
List = [1, 2, 3]
List = [1, 2, 3]

【讨论】:

【参考方案5】:

使用 int 参数调用 List.remove() 将匹配签名 remove(int index) 并且索引 list[2] 上的项目将被删除,无论列表是否具有值为 2 的 Integer 项目。

使用 long 参数调用 List.remove() 将导致编译器自动装箱到 Long 对象中并匹配签名 remove(Object o)。该列表将迭代,询问是否 o.equals(each item) 并且如前所述,Long 不等于 Integer。

【讨论】:

【参考方案6】:

当您执行list.remove(intNum); 时,它正在执行List 类的remove(int index); 签名,这会删除给定索引的项目。

但是,当您执行 list.remove(longNum);(考虑到您的意思是 longNumlong)时,它执行了 List 类的 boolean remove(Object o); 签名,它检查对象是否存在于列表中,以及是否是的,删除它。

由于列表是Integer 列表,它找不到对象并且没有删除任何内容,这就是为什么第二个结果是 3,所以没有删除任何内容。

【讨论】:

【参考方案7】:

Collection 中的List 有两个重载方法 1. public E remove(int index) 2. public boolean remove(Object o)

在您的情况下,第二种方法被调用。因为您正在传递 long(隐式转换为 Long wrapper 类所以 Object 类)。

现在 List.remove(longNum) 删除具有最低索引的元素 i 使得 (longNum==null ? get(i)==null : longNum.equals(get(i) ) ) (如果存在这样的元素)。

现在在您的情况下,计数器位于第二个位置(即 i=1)。 longNum.equals(get(1)) 返回 false 因为 get(1) 返回值 = 2 的 java.lang.Integer 的对象,而 longNum 是值 = 的 java.lang.Long 的一个实例2.所以 equals 方法返回 false 因此它不会删除元素。

你可以通过类名检查值的类型

   System.out.println(" Element Value :"+list.get(1));
   System.out.println(" Element Class :"+list.get(1).getClass());

【讨论】:

以上是关于Java中ArrayList remove()的不良行为[重复]的主要内容,如果未能解决你的问题,请参考以下文章

Java ArrayList.remove() 不起作用? [复制]

关于java里ArrayList类的remove(index)方法

Java Arraylist.remove 删除第一次出现而不是索引,即使我输入了一个 int? [复制]

ArrayList <Integer> 与 get/remove 方法

Java.ArrayList。方法删除()

Java中ArrayList类的方法(源码)