遍歷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是安全的,請千萬不要這么做。
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。根據這個特性,我們就能做點事情了。
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<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也是同樣的道理,這里就不改了。
至此,關於這個話題,我們算是討論完畢了,因為我們想要的效果已經達成了,但是依然是有很多問題亟待我們去研究,因為代碼是永遠不會有完美的一天。