Zacard's Notes

java list循环中删除元素的坑

背景

当我们要循环一个list中的元素,并且要删除某个元素的时候,一点要小心谨慎!其中深埋了好几个坑!

坑1

请看如下代码:

/**
 * 测试删除集合中的空白元素
 */
@Test
public void removeBlank() {
    List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add(" ");
    list.add("  ");
    for (String s : list) {
        if (StringUtils.isBlank(s)) {
            list.remove(s);
        }
    }

    System.out.println("list:" + list);
}

输出结果:list:[1, 2, 3, ] 。可以看到空白元素没有删除干净。

坑1解决办法

请看如下代码:

/**
 * 测试删除集合中的空白元素
 */
@Test
public void removeBlank() {
    List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add(" ");
    list.add("  ");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String s = iterator.next();
        if (StringUtils.isBlank(s)) {
            iterator.remove();
        }
    }

    System.out.println("list:" + list);
}

结果输出:list:[1, 2, 3]。解决办法其实就是用Iterator迭代器代替for循环。但是这个解决方法里还是隐藏了一个坑。

坑2

请看如下代码:

/**
 * 测试删除集合中的空白元素
 */
@Test
public void removeBlank() {
    List<String> list = Arrays.asList("1","2","3",""," ");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String s = iterator.next();
        if (StringUtils.isBlank(s)) {
            iterator.remove();
        }
    }

    System.out.println("list:" + list);
}

结果会直接报错:java.lang.UnsupportedOperationException。意思是不支持remove操作。
只是把list的定义换成了Arrays.asList,却有完全不一样的运行结果,非常神奇。查看Arrays.asList的源码:

/**
 * Returns a fixed-size list backed by the specified array.  (Changes to
 * the returned list "write through" to the array.)  This method acts
 * as bridge between array-based and collection-based APIs, in
 * combination with {@link Collection#toArray}.  The returned list is
 * serializable and implements {@link RandomAccess}.
 *
 * <p>This method also provides a convenient way to create a fixed-size
 * list initialized to contain several elements:
 * <pre>
 *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
 * </pre>
 *
 * @param <T> the class of the objects in the array
 * @param a the array by which the list will be backed
 * @return a list view of the specified array
 */
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

单从代码看:return new ArrayList<>(a);应该是一个普通的ArrayList啊?!查看注释:返回一个固定大小的list!也就是说add和remove操作肯定会报错。同时也说明了这里的ArrayList不是我们平时使用的ArrayList。继续跟踪这个ArrayList:

/**
 * @serial include
 */
private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private static final long serialVersionUID = -2764017481108945198L;
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }
    ...
}

原来此处的ArrayList是Arrays的一个实现了AbstractList的内部类,并且没有覆盖add和remove方法,默认这2个方法是会直接报“UnsupportedOperationException”的。

坑2解决办法

既然明白了报错原因,解决办法也很明显了:

/**
 * 测试删除集合中的空白元素
 */
@Test
public void removeBlank() {
    List<String> list = Arrays.asList("1","2","3",""," ");

    List<String> result= new ArrayList<>(list);
    Iterator<String> iterator = result.iterator();
    while (iterator.hasNext()) {
        String s = iterator.next();
        if (StringUtils.isBlank(s)) {
            iterator.remove();
        }
    }

    System.out.println("list:" + result);
}

反思

通过踩这几个坑,再次验证了一个真理:在设计一个对外方法的时候,一点要谨慎处理集合和数组。因为你永远不知道客户端传给你的集合是什么,也不知道客户端是否会有对此集合有任何其他的不可控的操作。所以在使用客户端传递的集合对象时,最好拷贝一个新集合后再操作。

更新-2016.01.17

之前的解决方法中违反了java其中的一个编码准则:变量的作用范围越小约好。

请看如下代码:

@Test
public void testForAndWhile() {
    List<String> names1 = Lists.newArrayList("张三", "李四");
    Iterator<String> iterator1= names1.iterator();
    while (iterator1.hasNext()) {
        String name = iterator1.next();
        System.out.println("name1:" + name);
        iterator1.remove();
    }

    List<String> names2 = Lists.newArrayList("赵六", "钱七");
    Iterator<String> iterator2= names2.iterator();
    while (iterator1.hasNext()) {
        String name = iterator2.next();
        System.out.println("name2:" + name);
        iterator2.remove();
    }
}

这段代码的第二个循环不会打印任何东西,因为while条件中误用了第一个循环中的变量,造成的bug。但是这种bug却不会有任何编译错误和运行时错误,危害甚大。但是只要改成旧版for循环的形式,就可以很好的避免这种错误。

请看如下改进代码:

@Test
public void testForAndWhile() {
    List<String> names1 = Lists.newArrayList("张三", "李四");
    for (Iterator<String> iterator= names1.iterator();iterator.hasNext();) {
        System.out.println("name1:"+iterator.next());
        iterator.remove();
    }

    List<String> names2 = Lists.newArrayList("赵六", "钱七");
    for (Iterator<String> iterator= names2.iterator();iterator.hasNext();) {
        System.out.println("name2:"+iterator.next());
        iterator.remove();
    }
}

这个时候,如果错误的引用循环1中的代码,会有编译时错误,就可以很好的避免这种问题、

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章