本文主要是Java集合的概述和Set集合
1.Java集合概述
1)數組可以保存多個對象,但數組長度不可變,一旦在初始化數組時指定了數組長度,這個數組長度就是不可變的,如果需要保存數量變化的數據,數組就有點無能為力了;而且數組無法保存
具有映射關系的數據。為了保存數量不確定的數據,以及保存具有映射關系的數據,Java提供了集合類。集合類主要負責保存、盛裝其他數據,因此集合類也被稱為容器類。
2)Java集合類可用於存儲數量不等的多個對象,並可以實現常用的數據結構,如棧、隊列等。還可以用於保存具有映射關系的關聯數組。Java集合大致可以分為Set、List、Map三種體系,
其中Set代表無序、不可重復的集合;List代表有序、重復的集合;Map則代表具有映射關系的集合。Queue體系集合代表一種隊列集合實現。
3)集合類和數組不一樣,數組元素既可以是基本類型的值,也可以是對象(實際上保存的是對象的引用變量);而集合類里只能保存對象(實際上保存的是對象的引用變量)。
4)Java集合類主要由兩個接口派生出:Collection和Map。Set和List接口是Collection接口派生的兩個子接口,他們分別代表了無序集合和有序集合;Queue是Java提供的隊列實現。
Map實現類用於保存具有映射關系的數據。Map保存的每項數據都是key-value對,也就是由key和value兩個值組成。Map里的key是不可重復的,key用於標識集合里的每項數據,如果需要查閱
Map中的數據時,總是根據Map的key來獲取。
5)Collection接口是List、Set和Queue接口的父接口,該接口里定義的方法既可以用於操作Set集合、也可以用於操作List集合和Queue集合。
boolean add(Object o):該方法用於向集合里添加一個元素。
boolean addAll(Collection c):該方法把集合c里的所有元素添加到指定集合里。
void clear():清除集合里的所有元素,將集合長度變為0。
boolean contains(Object o):返回集合里是否包含指定元素。
boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。
boolean isEmpty():返回集合是否為空。當集合長度為0時返回true,否則返回false。
Iterator iterator():返回一個Iterator對象,用於遍歷集合里的元素。
boolean remove(Object o):刪除集合中的指定元素o,當集合中包含了一個或多個元素o時,這些元素將被刪除,該方法將返回true。
boolean removeAll(Collection c):將集合中刪除集合c里包含的所有元素(相當於用調用該方法的集合減集合c),如果刪除了一個或一個以上的元素,則該方法返回true。
boolean retainAll(Collection c):將集合中刪除集合c里不包含的元素(相當於把調用該方法的集合變成該集合的集合c的交集),如果該操作改變了調用該方法的集合,則該方法返回true。
int size():該方法返回集合里元素的個數。
Object[] toArray():該方法把集合轉換成一個數組,所有的集合元素變成對應的數組元素。
eg:
package cn.it.lsl; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; public class CollectionTest { public static void main(String[] args) { Collection c = new ArrayList(); c.add("小明"); c.add(6); System.out.println("c集合的元素個數為:"+c.size()); c.remove(6); System.out.println("c集合的元素個數為:"+c.size()); System.out.println("c集合是否包含\"小明\"字符串:"+c.contains("小明")); c.add("JavaEE"); System.out.println("c集合的元素:"+c); Collection books = new HashSet(); books.add("JavaEE"); books.add("Android"); System.out.println("c集合是否完全包含books集合?"+c.containsAll(books)); c.removeAll(books); System.out.println("c集合的元素:"+c); c.clear(); System.out.println("c集合的元素:"+c); //books集合里只剩下c集合里也包含的元素 books.retainAll(c); System.out.println("books集合的元素:"+books); } }
6)Iterator接口遍歷集合元素
Iterator接口也是Java集合框架的成員,主要用於遍歷Collection集合中的元素,Iterator對象也被稱為迭代器。
Iterator接口里定義了如下三個方法:
boolean hasNext():如果被迭代的集合元素還沒有被遍歷,則返回true。
Object next():返回集合里的下一個元素。
void remove():刪除集合里上一次next方法返回的元素。
eg:
package cn.it.lsl; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class IteratorTest { public static void main(String[] args) { Collection books = new HashSet(); books.add("Java ee"); books.add("Java"); books.add("Andrroid"); //獲取books集合對應的迭代器 Iterator it = books.iterator(); while(it.hasNext()){ //it.next()方法返回的數據類型是Object類型 String book = (String)it.next(); System.out.println(book); if(book.equals("Java")){ it.remove(); } book = "測試字符串"; } System.out.println(books); } }
如果要創建Iterator對象,則必須有一個被迭代的集合。
package cn.it.lsl; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class IteratorTest { public static void main(String[] args) { Collection books = new HashSet(); books.add("Java ee"); books.add("Java"); books.add("Android"); //獲取books集合對應的迭代器 Iterator it = books.iterator(); while(it.hasNext()){ //it.next()方法返回的數據類型是Object類型 String book = (String)it.next(); System.out.println(book); if(book.equals("Android")){ //it.remove(); books.remove(book); } //book = "測試字符串"; } //System.out.println(books); } }
//當使用Iterator迭代訪問Collection集合元素時,Colleection集合里的元素不能被改變,只有通過Iterator的remove方法刪除上一次next方法返回集合元素才可以。
eg:
package cn.it.lsl; import java.util.Collection; import java.util.HashSet; public class ForeachTest { public static void main(String[] args) { Collection books = new HashSet(); books.add("Java ee"); books.add("Java"); books.add("Android"); for(Object obj : books){ String book = (String)obj; System.out.println(book); if(book.equals("Android")){ //以下代碼會引發異常 //books.remove(book); } } System.out.println(books); } }
2.Set集合
Set集合與Collection基本上完全一樣,它沒有提供任何額外的方法。實際上Set就是Collection,只是行為略有不同。(Set不允許包含重復元素)。
Set集合不允許包含相同的元素,如果試圖把兩個相同的元素加入同一個Set集合中,則添加操作失敗。
eg:
package cn.it.lsl; import java.util.HashSet; import java.util.Set; public class SetTest { public static void main(String[] args) { Set books = new HashSet(); books.add(new String("java")); boolean result = books.add(new String("java")); System.out.println(result + "-->" + books); } }
1)HashSet類
(1)HashSet是Set接口的實現。HashSet按Hash算法來存儲集合中的元素,具有很好的存取和查找性能。
(2)HashSet不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發生變化。
(3)當向HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據該HashCode值決定該對象在HashSet中的存儲位置。如果有兩個元素
通過equals()方法比較返回true,但它們的hashCode()方法返回值不相等,HashSet將會把它們存儲在不同的位置,依然可以添加成功。即,HashSet集合判斷兩個元素相等的標准是兩個
對象通過equals()方法比較相等,並且兩個對象的hashCode()方法返回值也相等。
eg:
package cn.it.lsl; import java.util.HashSet; class A{ public boolean equals(Object obj){ return true; } } class B{ public int hashCode(){ return 1; } } class C{ public int hashCode(){ return 2; } public boolean equals(Object obj){ return true; } } public class HashSetTest { public static void main(String[] args) { HashSet books = new HashSet(); books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); } }
注意問題:當把一個對象放入HashSet中時,如果需要重寫該對象對應類的equals()方法,則也應該重寫其hashCode()方法。其規則是:如果兩個對象通過equals()方法比較返回true,則兩個對象的hashCode值也應該相同。
重寫hashCode()方法的基本規則:
1)在程序運行過程中,同一個對象多次調用hashCode()方法應該返回相同的值。
2)當兩個對象通過equals()方法比較返回true時,這兩個對象的hashCode()方法應返回相等的值。
3)對象中用作equals()方法比較標准的Field,都應該用來計算hashCode值。
如果向HashSet中添加一個可變對象后,后面程序修改了該可變對象的Field,則可能導致它與集合中的其他元素相同,這就可能導致HashSet中包含兩個相同的對象。
eg:
package cn.it.lsl; import java.util.HashSet; import java.util.Iterator; class R{ int count; public R(int count){ this.count = count; } public String toString(){ return "R[count:" + count + "]"; } public boolean equals(Object obj){ if(this == obj) return true; if(obj != null && obj.getClass() == R.class){ R r = (R)obj; if(r.count == this.count){ return true; } } return false; } public int hashCode(){ return this.count; } } public class HashSetTest2 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new R(5)); hs.add(new R(-3)); hs.add(new R(9)); hs.add(new R(-2)); System.out.println(hs); Iterator it = hs.iterator(); R first = (R)it.next(); first.count = -3; System.out.println(hs); hs.remove(new R(-3)); System.out.println(hs); System.out.println("hs是否包含count為-3的R對象?" + hs.contains(new R(-3))); System.out.println("hs是否包含count為5的R對象?" + hs.contains(new R(5))); } }
當向HashSet中添加可變對象時,必須十分小心。如果修改HashSet集合中的對象,有可能導致該對象與集合中的其他對象相等,從而導致HashSet無法准確訪問該對象。
2)LinkedHashSet類
HashSet還有一個子類LinkedHashSet,LinkedHashSet集合也是根據元素的hashCode值來決定元素的存儲位置,但它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。也就是說,當遍歷LinkedHashSet集合里的元素時,LinkedHashSet將會按元素的添加順序來訪問集合里的元素。
package cn.it.lsl; import java.util.LinkedHashSet; public class LinkedHashSetTest { public static void main(String[] args) { LinkedHashSet books = new LinkedHashSet(); books.add("java"); books.add("Android"); System.out.println(books); books.remove("java"); books.add("java"); System.out.println(books); } }
輸出LinkedHashSet集合的元素時,元素的順序總是與添加順序一致。
雖然LinkedHashSet使用了鏈表記錄集合元素的添加順序,但LinkedHashSet依然是HashSet,因此它依然不允許集合元素重復。
3)TreeSet類
TreeSet是SortedSet接口的實現類,可以確保集合元素處於排序狀態。
TreeSet中的幾個方法:
Object first():返回集合中的第一個元素。
Object last():返回集合中的最后一個元素。
Object lower(Object e):返回集合中位於指定元素之前的元素(即小於指定元素的最大元素,參數元素不需要是TreeSet集合里的元素)。
Object higher(Object e):返回集合中位於指定元素之后的元素(即大於指定元素的最小元素,參數元素不需要是TreeSet集合里的元素)。
SortedSet subSet(formElement,toElement):返回次Set的子集合,范圍從formElement(包含)到toElement(不包含)。
SortedSet headSet(toElement):返回此Set的子集,由小於toElement的元素組成。
SortedSet tailSet(fromElement):返回此Set的子集,由大於或等於fromElement的元素組成。
package cn.it.lsl; import java.util.TreeSet; public class TreeSetTree { public static void main(String[] args) { TreeSet nums = new TreeSet(); nums.add(5); nums.add(2); nums.add(10); nums.add(-9); System.out.println(nums); System.out.println(nums.first()); System.out.println(nums.last()); System.out.println(nums.headSet(4)); //不包含4 System.out.println(nums.tailSet(5)); //包含5 System.out.println(nums.subSet(-3, 4)); } }
4)EnumSet類
EnumSet是一個專為枚舉類設計的集合類,EnumSet中的所有元素都必須是指定枚舉類型的枚舉值。
EnumSet類沒有暴露任何構造器來創建該類的實例,程序應該通過它提供的static方法來創建EnumSet對象。
static EnumSet allOf(Class elementType):創建一個包含指定枚舉類里所有枚舉值的EnumSet集合。
static EnumSet complementOf(EnumSet s):創建一個其元素類型與指定EnumSet里元素類型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚舉類剩下的枚舉值(
即新EnumSet集合和原EnumSet集合的集合元素加起來就是該枚舉類的所有枚舉值)。
static EnumSet copyOf(Collection c):使用一個普通集合來創建EnumSet集合。
static EnumSet copyOf(EnumSet s):創建一個與指定EnumSet具有相同元素類型、相同集合元素的EnumSet集合。
static EnumSet noneOf(Class elementType):創建一個元素類型為指定枚舉類型的空EnumSet。
static EnumSet of(E first, E...rest):創建一個包含一個或多個枚舉值的EnumSet集合,傳入的多個枚舉值必須屬於同一個枚舉類。
static EnumSet range(E from, E to):創建一個包含從from枚舉值到to枚舉值范圍內所有枚舉值的EnumSet集合。
eg:
package cn.it.lsl; import java.util.EnumSet; enum Season{ SPRING,SUMMER,FAIL,WINTER } public class EnumSetTest { public static void main(String[] args) { EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); es2.add(Season.WINTER); es2.add(Season.SPRING); System.out.println(es2); EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); System.out.println(es3); EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER); System.out.println(es4); EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); } }
復制另一個EnumSet集合中的所有元素來創建新的EnumSet集合,或者復制另一個Collection集合中的所有元素來創建新的EnumSet集合。當復制Collection集合中的所有元素來創建新的EnumSet集合時,要求Collection集合中的所有元素必須是同一個枚舉類的枚舉值。
package cn.it.lsl; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; public class EnumSetTest2 { public static void main(String[] args) { Collection c = new HashSet(); c.clear(); c.add(Season.FAIL); c.add(Season.SPRING); EnumSet enumSet = EnumSet.copyOf(c); System.out.println(enumSet); // c.add("java"); // c.add("Android"); // enumSet = EnumSet.copyOf(c); } }
當試圖復制一個Collection集合里的元素來創建EnumSet集合時,必須保證Collection集合里的所有元素都是同一個枚舉類的枚舉值。
總結:
HashSet的性能總是比TreeSet好(特別是最常用的添加、查詢元素等操作),因為TreeSet需要額外的紅黑樹算法來維護集合元素的次序。只有當需要一個保持排序的Set時,才應該使用TreeSet,否則都應該使用HashSet。
對於普通的插入、刪除操作,LinkedHashSet比HashSet要略微慢一點,這是由維護鏈表所帶來的額外開銷造成的;不過,因為有了鏈表,遍歷LinkedHashSet會更快。
EnumSet是所有Set實現類中性能最好的,但它只能保存同一個枚舉類的枚舉值作為集合元素。