反向思维的妙处---解决正向循环删除元素的问题

     循环是我们代码中最常使用的结果,在遍历的基础上进行其他操作,比如删除。如果是使用List容器,那么就更加简单了,因为List封装了许多实用的方法,拿删除来说,就有remove()和removeAll()。拿来主义固然是好事,但是不注意拿来的东西到底怎么用,就会出问题。鲁迅的文章早已经指出这点,所以,我们也要对我们”拿来“的东西研究一下。

remove()首当其冲就给了我一个”下马威"。remove()这个方法最大的毛病就是改变List的结构,它会将List中想要移除的元素后面的所有元素向前移动一位。我们可以通过下面的代码来看看这个“可怕”的副作用:

      代码如下:

         

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("java");
        list.add("C++");
        list.add("java");
        list.add("java");
        list.add("java");
        list.add("C");
        System.out.println("看看原先要删除的元素所在的位置:");
        for (int i = 0, len = list.size(); i < len; i++) {
            if (list.get(i).equals("java")) {
                System.out.println("location:" + i + ":" + list.get(i));
            }

        }
        System.out.println("看看是哪些位置的元素被删除:");
        for (int i = 0, len = list.size(); i < len; i++) {
            if (list.get(i).equals("java")) {
                System.out.println("delete:" + i + ":" + list.get(i));
                list.remove(i);
            }
        }
        System.out.println("看看现在那些剩下的元素的位置:");
        for (int i = 0, len = list.size(); i < len; i++) {
            System.out.println(i + ":" + list.get(i));
        }
    }

 

输入结果如下:

看看原先要删除的元素所在的位置:
location:0:java
location:2:java
location:3:java
location:4:java
看看是哪些位置的元素被删除:
delete:0:java
delete:1:java
delete:2:java
看看现在那些剩下的元素的位置:
0:C++
1:java
2:C

       看到没有?有一个元素是没有被删除的,而且删除的位置与它们原来的位置也不一样啊!我们把这个过程模拟出来:java(0),C++(1),java(2),java(3),java(4),C(5) --->C++(0),java(1),java(2),java(3),C(4)--->C++(0),java(1),java(2),C(3)--->C++(0),java(1),C(2)。这里面要删除的是两个java(1)!!但是remove()中的每个索引参数只会被删除一次,所以,倒数第二个java是不会被删除的!!

      如果大家还不信,我还有一个验证代码可以做证据:

     

    public static void main(String[] args) {
        List<RatingBook> list = new ArrayList<RatingBook>();
        RatingBook book1 = new RatingBook();
        book1.setBook("java");
        book1.setRating(0);
        RatingBook book2 = new RatingBook();
        book2.setBook("C++");
        book2.setRating(1);
        RatingBook book3 = new RatingBook();
        book3.setBook("java");
        book3.setRating(2);
        RatingBook book4 = new RatingBook();
        book4.setBook("java");
        book4.setRating(3);
        RatingBook book5 = new RatingBook();
        book5.setBook("java");
        book5.setRating(4);
        RatingBook book6 = new RatingBook();
        book6.setBook("C");
        book6.setRating(5);
        list.add(book1);
        list.add(book2);
        list.add(book3);
        list.add(book4);
        list.add(book5);
        list.add(book6);
        for (int i = 0, len = list.size(); i < len; i++) {
            if (((list.get(i).getBook()).equals("java"))) {
                list.remove(i);
            }
        }
        for (int i = 0, len = list.size(); i < len; i++) {
            System.out.println(i + ":" + list.get(i).getBook() + " "
                    + list.get(i).getRating());
        }
    }

      RatingBook的代码如:

     

public class RatingBook {
    private int rating;
    private String book;

    public void setRating(int rating) {
        this.rating = rating;
    }

    public void setBook(String book) {
        this.book = book;
    }

    public String getBook() {
        return book;
    }

    public int getRating() {
        return rating;
    }

}

结果如下:

0:C++ 1
1:java 3
2:C 5

      留下来的是倒数第二个java啊!!!

      所以,在循环中使用remove()来删除重复的元素必须打起十二分精神。这个问题的解决也不难,犯不着从此不用remove(),它依然是非常好用的,只是我们会不会用而已。我这里有一个小技巧,无论序列中是否有重复的元素都可以使用,就是反向遍历List然后删除元素。道理很简单,既然remove()是将目标元素后面的元素向前移动,那么,只要目标元素后面没有元素不就可以了?反向就是利用了这点,只要将我上面的代码改成这样:

    

for(int i = list.size() - 1; i >= 0; i++){
    ...
}

      这样子处理List是比较好的,因为它可以避免我们使用remove()这些会改变结构的方法带来的影响,也可以一定程度上提高我们的循环效率。循环是程序中耗费最大的一块,关于它的优化的话题一直都是重点,层出不穷,这里就是一点。我们很少注意到,int i = 0; i < list.size()是要付出很大的开销的,因为我们每次判断循环终点条件的时候都会执行一次list.size()的计算,如果list很大的话,开销就很大了。反向的话,list.size()是放在前面的,就不用每次都计算了。

 如果只是想要完全删除重复的元素,我们可以使用removeAll()。它里面的参数类型是Collection<?>,是一个容器,至于为什么是容器,接下来我会讲的,就是有关于removeAll()的原理。

         先上代码:

        

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("java");
        list.add("C++");
        list.add("java");
        list.add("java");
        list.add("java");
        list.add("C");
        System.out.println("看看原先要删除的元素所在的位置:");
        for (int i = 0, len = list.size(); i < len; i++) {
            if (list.get(i).equals("java")) {
                System.out.println("location:" + i + ":" + list.get(i));
            }

        }
        List<String> sublist = new ArrayList<String>();
        sublist.add("java");
        list.removeAll(sublist);
        System.out.println("看看现在那些剩下的元素的位置:");
        for (int i = 0, len = list.size(); i < len; i++) {
            System.out.println(i + ":" + list.get(i));
        }
    }

结果如:

看看原先要删除的元素所在的位置:
location:0:java
location:2:java
location:3:java
location:4:java
看看现在那些剩下的元素的位置:
0:C++
1:C

       之所以不像上面那样显示要删除的元素的位置,是因为这个过程是将整个list都遍历一次,然后将与sublist相同的元素找出来并且删除掉,而且因为是容器,你可以添加更多的元素,反正只要list中有sublist的元素,就会全部删掉。这样就会避免上面的错误,但是,必须注意,最好是原先的list是怎样的形式,作为参数的容器也是同样的形式,以免出现不可匹配的问题。

      至于如何在循环中删除重复的元素,欢迎看一下我的文章:http://www.cnblogs.com/wenjiang/archive/2012/10/24/2737746.html

posted @ 2012-09-15 16:01  文酱  阅读(1432)  评论(0编辑  收藏  举报