遍歷List刪除重復元素的方案探究


      遍歷List,然后刪除其中的元素,這種操作存在風險,如果改變List的結構,就會影響到我們接下來的操作,這是危險的,甚至有些數據我們在毫不知情的情況下就被刪除掉。這都是不允許的,為應對這種情況,我們可以做一個映射,將原來List映射到新的List上,比如說,可以將不需要刪除的元素放到一個新的List上,而不是在原有List上直接刪除得到,這樣更加安全,但是很多情況下,我們根本就不知道數據是怎樣的,它哪些是重復的,需要進行判斷,但是在循環中如果這樣寫:

    

      List<Integer>  mlist = new ArrayList<Integer>();
      for(int i = 0; i < list.size() - 1; i++){
          for(int j = i + 1; j < list.size(); j++){
               if(list.get(i) != list.get(j)){
                  mlist.add(list.get(i));
                }
            }
        }

 

      這可不是什么正確的代碼,因為你能找出來的,只有根本就不重復的元素,但是重復的元素也是要添加的啊(只是只添加一個而已)。那么,只要找到重復的,然后刪除掉就行了,是的,這就是萬惡的remove()之所以會被使用的原因。“只要刪除就行”,這句話是具有潛在風險的,如果你無法保證刪除后的List是安全的,請千萬不要這么做。

       List中有兩個方法是關於刪除元素的,就是remove()和removeAll() 。remove()會改變List的結構,就是它會刪除原來List中元素的索引,然后剩下的每個元素都向前移動一位,這樣,當我們要對List的下個元素進行操作的時候,該元素的索引已經發生改變,就可能會出現錯誤。使用removeAll()存在限制,因為它的參數是一個容器(通常都是與操作容器一樣類型),它的原理是把這個參數容器里的元素跟List中的元素進行一一比較(使用equals()),但是,如果這是一個對象容器,存儲的是對象,如何比較這是一個大問題。基本上,它是無法處理存儲對象的容器的,而且它是全部刪除,我們這里是刪除重復的,removeAll()顯然無法符合我們的需要。那么,我們應該怎么做呢?(想要知道更多有關於remove()和removeAll()的東西,可以參考一下我這篇文章: http://www.cnblogs.com/wenjiang/archive/2012/09/15/2686555.html
     使用迭代器的話,就使得這變成可能。迭代器的原理,是保留原有List元素的索引,這樣就不會破壞List的結構。我們來看一下代碼實現:
    
       Itreator iterator = list.iterator();
       while(iterator.hasNext()){
                Integer num = iterator.next();
                if(num == 2){
                   iterator.remove();
                 }
         }

       這樣確實能夠安全的刪除我們想要刪除的元素,但是,它依然不能解決我們上面的問題,因為我們根本就不知道重復的元素到底是什么。 但是這里還是要說一下,迭代器的處理功能是很強大的,它能夠處理對象容器,可以完全匹配也可以不用完全匹配,就像下面這樣:

     

                List<Book> list = new ArrayList<Book>();   //Book對象包含兩個數據,名字name和書號number
               //假設list里面兩本書,(java, 1)和(java, 2)
                Itreator iterator = list.iterator();
                while(iterator.hasNext()){
                       Book book = (Book)iterator.next();
                       if((book.getName()).equals("java")){  //刪除所有名字有java的書
                            iterator.remove();
                        }
                       if((book.getName()).equals("java")) && ((book.getNumber()) == 2)){  //刪除(java, 2)
                            iterator.remove();
                        }
                 }

      要想真正解決我們上面的問題,需要使用到Map中key-value的唯一性問題。我們知道,一個key只能存儲一個value,而且key是唯一的,如果我們的數據中,一個key存在多個value,那么,取最后一個value。根據這個特性,我們就能做點事情了。

       我們來舉個例子,就像這樣的數據:2,2,2,2,3,3,4,6,5,2,4,7,6,5,我們想要一個不重復的數列,利用map,我們可以這樣做:
      
int[] input = new int[]{ 2,2,2,2,3,3,4,6,5,2,4,7,6,5 };
Map<Integer, Integer> output = new HashMap<Integer, Integer>();
for(int i = 0; i < input.length; i++) {
    output.put(input[i], 0);
}
for(int i : output.keySet()) {
   System.out.println(i);
}

     因為每個key只能存儲一個value,而且key是唯一的,將我們的目標數據變成key,然后取出來,我們就能解決這個問題。

     但是我們要解決的是對象容器的問題,那么,map是否也可以呢?答案是可以的。
     還是Book的例子。我們的book可能存在這樣的重復:(java, 0), (java, 0),(c,1),(c++,2),
     那么,我們就可以這樣做:
    
Map<String, Integer> map = new HashMap<String, Integer>();
List<Book> list = new ArrayList<Book>();
for(int i = 0; i < list.size(); i++){
   map.put(list.get(i).getName(), list.get(i).getNumber());
}
Set entries = map.entrySet();
if (entries != null) {
    Iterator iterator = entries.iterator();
    while (iterator.hasNext()) {
    Book book = new Book();
    Map.Entry entry = (Entry) iterator.next();
    String key =(String) entry.getKey();
    Integer value = (Integer)entry.getValue();
    book.setName(key);
    book.setNumber(value);
    }
}

這種做法有個局限,就是無法將自定義對象作為Key值,因為Map無法判斷兩個自定義對象是否相等,因為它內部使用的是equals或==,如果是(java,0),(java,1),(java,0),(c,0),(c,1)這種情況,就很難說哪個能作為key值呢。所以,我們的方案還得繼續改進以滿足我們一開始的要求。

         上面的例子症結就是Map的equals()無法判斷兩個自定義對象是否相等,於是,我們的第一個想法就是為什么不自定義自己的equals()方法呢?因為編譯器是這樣來判斷兩個對象是否相等的:先是看這個對象是否有自定義的equals()方法,如果沒有,再調用默認的equals(),這個equals()是Object類的,它只是比較兩個引用的地址。這種比較方法一般情況下是夠用的,但是在比較一些更加復雜的對象時就會有問題。但是為什么equals()能夠比較String呢?語法上,即使內容相同的兩個String對象,也是兩個不同的引用,但是在比較的時候還是一樣的,因為它們的hashCode()是一樣的。所以,如果覆寫了Object的equals(),也要順便覆寫hashCode(),因為在比較對象的時候,先調用hashCode() ,如果相同,再調用equals()。

       我們這次選擇的容器是Set。我們知道,Set里的元素都是不重復的,但這只對於String和基本類型而言,自定義對象要覆寫equals()和hashCode()才能發揮作用。

       覆寫hashCode()有很多方法,只要確保散列碼不一樣就行,一般我們散列碼的結果是與我們對象的數據有關,這次我們的對象中包含兩個數據:name和rating,所以,hashCode()這么寫:

public int hashCode() {
      return this.name.hashCode() + this.rating;
}

       接着就是equals():

public boolean equals(Object obj) {
    RatingBook book = new RatingBook();
    if (obj instanceof RatingBook) {
        book = (RatingBook) obj;
    }
    return ((book.getRating() == this.rating) &(book.getName().equals(this.name)));
}

       千萬不要想當然的的將參數設為RatingBook,因為equals()是不支持泛型的(雖然java后來支持泛型)。這里我先判斷obj是否是RatingBook,這樣也能順便判斷是否為null。
       然后就是我們的測試類:

Set<RatingBook> set = new HashSet<RatingBook>();
       set.add(new RatingBook("java", 1));
       set.add(new RatingBook("java", 1));
       set.add(new RatingBook("c", 0));
       set.add(new RatingBook("c", 0));
       set.add(new RatingBook("c", 1));
       set.add(new RatingBook("java", 0));
       set.add(new RatingBook("java", 0));
       set.add(new RatingBook("java", 0));
       Iterator<RatingBook> iterator = set.iterator();
       while (iterator.hasNext()) {
             RatingBook book = iterator.next();
             System.out.println(book);
        }

       結果如下:

c評分:1
c評分:0
java評分:0
java評分:1

       HashMap也是同樣的道理,這里就不改了。
       至此,關於這個話題,我們算是討論完畢了,因為我們想要的效果已經達成了,但是依然是有很多問題亟待我們去研究,因為代碼是永遠不會有完美的一天。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM