集合框架是一個用來代表和操縱集合的統一架構。所有的集合框架都包含如下內容:
Collection 是最基本的集合接口,一個 Collection 代表一組 Object,Java不提供直接繼承自Collection的類,只提供繼承於的子接口(如List和set)。
2 ListList接口是一個有序的Collection,使用此接口能夠精確的控制每個元素插入的位置,能夠通過索引(元素在List中位置,類似於數組的小標)來訪問List中的元素,而且允許有相同的元素。
3 SetSet 具有與 Collection 完全一樣的接口,只是行為上不同,Set 不保存重復的元素。
4 SortedSet 繼承於Set保存有序的集合。 5 Map 將唯一的鍵映射到值。 6 Map.Entry 描述在一個Map中的一個元素(鍵/值對)。是一個Map的內部類。 7 SortedMap 繼承於Map,使Key保持在升序排列。 8 Enumeration 這是一個傳統的接口和定義的方法,通過它可以枚舉(一次獲得一個)對象集合中的元素。這個傳統接口已被迭代器取代。
Java提供了一套實現了Collection接口的標准集合類。其中一些是具體類,這些類可以直接拿來使用,而另外一些是抽象類,提供了接口的部分實現。
標准集合類匯總於下表:
該類實現了List接口,允許有null(空)元素。主要用於創建鏈表數據結構,該類沒有同步方法,如果多個線程同時訪問一個List,則必須自己實現訪問同步,解決方法就是在創建List時候構造一個同步的List。例如:
Listlist=Collections.synchronizedList(newLinkedList(...));
LinkedList 查找效率低。
5 ArrayList該類也是實現了List的接口,實現了可變大小的數組,隨機訪問和遍歷元素時,提供更好的性能。該類也是非同步的,在多線程的情況下不要使用。ArrayList 增長當前長度的50%,插入刪除效率低。
6 AbstractSet 繼承於AbstractCollection 並且實現了大部分Set接口。 7 HashSet該類實現了Set接口,不允許出現重復元素,不保證集合中元素的順序,允許包含值為null的元素,但最多只能一個。
8 LinkedHashSet 具有可預知迭代順序的 Set 接口的哈希表和鏈接列表實現。 9 TreeSet該類實現了Set接口,可以實現排序等功能。
10 AbstractMap 實現了大部分的Map接口。 11 HashMap HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。
在前面的教程中已經討論通過java.util包中定義的類,如下所示:
該類和ArrayList非常相似,但是該類是同步的,可以用在多線程的情況,該類允許設置默認的增長長度,默認擴容方式為原來的2倍。
2 Stack 棧是Vector的一個子類,它實現了一個標准的後進先出的棧。 3 Dictionary Dictionary 類是一個抽象類,用來存儲鍵/值對,作用和Map類相似。 4 Hashtable Hashtable 是 Dictionary(字典) 類的子類,位於 java.util 包中。 5 Properties Properties 繼承於 Hashtable,表示一個持久的屬性集,屬性列表中每個鍵及其對應值都是一個字符串。 6 BitSet 一個Bitset類創建一種特殊類型的數組來保存位值。BitSet中數組大小會隨需要增加。import java.util.Iterator; import java.util.List; import java.util.ArrayList; public class myArrayList { public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("nick"); list.add("jenny"); list.add("Maria"); //第一種遍歷方法使用foreach遍歷List for (String str : list){ System.out.println(str); } //第二種遍歷,把鏈表變為數組相關的內容進行遍歷 String[] strArray = new String[list.size()]; list.toArray(strArray); for (int i=0;i<strArray.length;i++){ System.out.println(strArray[i]); } //第三種遍歷 使用迭代器進行相關遍歷 Iterator<String> ite=list.iterator(); while (ite.hasNext()){ System.out.println(ite.next()); } } } myArrayList.java package three; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class mymap { public static void main(String[] args){ Map<String, String> map = new HashMap<String, String>(); map.put("name", "nick"); map.put("age", "18"); map.put("single", "singleDog"); //第一種:普遍使用,二次取值 for (String key : map.keySet()){ System.out.println(key + " " + map.get(key)); } //通過Map.entrySet使用iterator遍歷key和value Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); System.out.println(entry.getKey() + " " + entry.getValue()); } //第三種:推薦,尤其是容量大時 for (Map.Entry<String, String> entry : map.entrySet()){ System.out.println(entry.getKey() + " " + entry.getValue()); } //通過Map.values()遍歷所有的value,但不能遍歷key for (String v : map.values()){ System.out.println(v); } } } mymap.java
在Java中的數據結構主要包括以下幾種接口和類:
Enumeration接口中定義了一些方法,通過這些方法可以枚舉(一次獲得一個)對象集合中的元素。
這種傳統接口已被迭代器取代,雖然Enumeration 還未被遺棄,但在現代代碼中已經被很少使用了。盡管如此,它還是使用在諸如Vector和Properties這些傳統類所定義的方法中,除此之外,還用在一些API類,並且在應用程序中也廣泛被使用。 下表總結了一些Enumeration聲明的方法:
package three; import java.util.Vector; import java.util.Enumeration; public class myEnumeration { public static void main(String[] args){ Enumeration days; Vector dayNames = new Vector(); dayNames.add("Sunday"); dayNames.add("Monday"); dayNames.add("Tuesday"); dayNames.add("Wednesday"); dayNames.add("Thursday"); dayNames.add("Friday"); dayNames.add("Saturday"); days = dayNames.elements(); while (days.hasMoreElements()){ System.out.println(days.nextElement()); } } } practice
位集合類實現了一組可以單獨設置和清除的位或標志。
該類在處理一組布爾值的時候非常有用,你只需要給每個值賦值一"位",然後對位進行適當的設置或清除,就可以對布爾值進行操作了。
一個Bitset類創建一種特殊類型的數組來保存位值。BitSet中數組大小會隨需要增加。這和位向量(vector of bits)比較類似。
這是一個傳統的類,但它在Java 2中被完全重新設計。
BitSet定義了兩個構造方法。
BitSet() //第一個構造方法創建一個默認的對象 BitSet(int size) //第二個方法允許用戶指定初始大小。所有位初始化為0。
BitSet中實現了Cloneable接口中定義的方法如下表所列:
package three; import java.util.BitSet; public class myBitset { public static void main(String[] args){ BitSet bits1 = new BitSet(16); BitSet bits2 = new BitSet(16); // set some bits for(int i=0; i<16; i++) { if((i%2) == 0) bits1.set(i); if((i%5) != 0) bits2.set(i); } System.out.println("Initial pattern in bits1: "); System.out.println(bits1); // {0, 2, 4, 6, 8, 10, 12, 14} System.out.println("\nInitial pattern in bits2: "); System.out.println(bits2); // {1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14} // AND bits bits2.and(bits1); System.out.println("\nbits2 AND bits1: "); System.out.println(bits1); // {0, 2, 4, 6, 8, 10, 12, 14} System.out.println(bits2); // {2, 4, 6, 8, 12, 14} // OR bits bits2.or(bits1); System.out.println("\nbits2 OR bits1: "); System.out.println(bits1); // {0, 2, 4, 6, 8, 10, 12, 14} System.out.println(bits2); // {0, 2, 4, 6, 8, 10, 12, 14} // XOR bits bits2.xor(bits1); System.out.println("\nbits2 XOR bits1: "); System.out.println(bits1); // {0, 2, 4, 6, 8, 10, 12, 14} System.out.println(bits2); // {} } } practice
向量(Vector)類和傳統數組非常相似,但是Vector的大小能根據需要動態的變化。
和數組一樣,Vector對象的元素也能通過索引訪問。
使用Vector類最主要的好處就是在創建對象的時候不必給對象指定大小,它的大小會根據需要動態的變化。
Vector類實現了一個動態數組。和ArrayList和相似,但是兩者是不同的:
Vector主要用在事先不知道數組的大小,或者只是需要一個可以改變大小的數組的情況。
Vector類支持4種構造方法。
Vector() //第一種構造方法創建一個默認的向量,默認大小為10 Vector(int size) //第二種構造方法創建指定大小的向量 Vector(int size,int incr) //第三種構造方法創建指定大小的向量,並且增量用incr指定. 增量表示向量每次增加的元素數目 Vector(Collection c) //第四中構造方法創建一個包含集合c元素的向量
除了從父類繼承的方法外Vector還定義了以下方法:
package three; import java.util.*; public class myVector { public static void main(String[] args){ Vector v = new Vector(3, 2); System.out.println(v.size()); // 0 System.out.println(v.capacity()); // 3 v.addElement(new Integer(1)); System.out.println(v.capacity()); // 3 v.addElement(new Integer(2)); System.out.println(v.capacity()); // 3 v.addElement(new Integer(3)); System.out.println(v.capacity()); // 3 v.addElement(new Integer(4)); System.out.println(v.capacity()); // 5 v.addElement(new Double(5.45)); System.out.println(v.capacity()); // 5 v.addElement(new Double(6.08)); v.addElement(new Integer(7)); System.out.println(v.capacity()); // 7 v.addElement(new Float(9.4)); v.addElement(new Integer(10)); System.out.println(v.capacity()); // 9 v.addElement(new Integer(11)); v.addElement(new Integer(12)); System.out.println(v.capacity()); // 11 System.out.println((Integer)v.firstElement()); // 1 System.out.println((Integer)v.lastElement()); // 12 if(v.contains(new Integer(3))) System.out.println("Vector contains 3."); //true // enumerate the elements in the vector. Enumeration vEnum = v.elements(); System.out.println("\nElements in vector:"); while(vEnum.hasMoreElements()) System.out.print(vEnum.nextElement() + " "); // 1 2 3 4 5.45 6.08 7 9.4 10 11 12 System.out.println(); } } practice
棧(Stack)實現了一個後進先出(LIFO)的數據結構。
你可以把棧理解為對象的垂直分布的棧,當你添加一個新元素時,就將新元素放在其他元素的頂部。
當你從棧中取元素的時候,就從棧頂取一個元素。換句話說,最後進棧的元素最先被取出。
棧是Vector的一個子類,它實現了一個標准的後進先出的棧。
堆棧只定義了默認構造函數,用來創建一個空棧。 堆棧除了包括由Vector定義的所有方法,也定義了自己的一些方法。
Stack()
除了由Vector定義的所有方法,自己也定義了一些方法:
package three; import java.util.*; public class myStack { static void showpush(Stack st, int a) { st.push(new Integer(a)); System.out.println("push(" + a + ")"); System.out.println("stack: " + st); } static void showpop(Stack st) { System.out.print("pop -> "); Integer a = (Integer) st.pop(); System.out.println(a); System.out.println("stack: " + st); } public static void main(String args[]) { Stack st = new Stack(); System.out.println("stack: " + st); showpush(st, 42); showpush(st, 66); showpush(st, 99); showpop(st); showpop(st); showpop(st); try { showpop(st); } catch (EmptyStackException e) { System.out.println("empty stack"); } } /*輸出結果: stack: [] push(42) stack: [42] push(66) stack: [42, 66] push(99) stack: [42, 66, 99] pop -> 99 stack: [42, 66] pop -> 66 stack: [42] pop -> 42 stack: [] pop -> empty stack * */ } practice
字典(Dictionary) 類是一個抽象類,它定義了鍵映射到值的數據結構。
當你想要通過特定的鍵而不是整數索引來訪問數據的時候,這時候應該使用Dictionary。
由於Dictionary類是抽象類,所以它只提供了鍵映射到值的數據結構,而沒有提供特定的實現。
Dictionary 類是一個抽象類,用來存儲鍵/值對,作用和Map類相似。
給出鍵和值,你就可以將值存儲在Dictionary對象中。一旦該值被存儲,就可以通過它的鍵來獲取它。所以和Map一樣, Dictionary 也可以作為一個鍵/值對列表。
Dictionary定義的抽象方法如下表所示:
序號 方法描述 1 Enumeration elements( )
Hashtable類提供了一種在用戶定義鍵結構的基礎上來組織數據的手段。
例如,在地址列表的哈希表中,你可以根據郵政編碼作為鍵來存儲和排序數據,而不是通過人名。
哈希表鍵的具體含義完全取決於哈希表的使用情景和它包含的數據。
Hashtable是原始的java.util的一部分, 是一個Dictionary具體的實現 。
然而,Java 2 重構的Hashtable實現了Map接口,因此,Hashtable現在集成到了集合框架中。它和HashMap類很相似,但是它支持同步。
像HashMap一樣,Hashtable在哈希表中存儲鍵/值對。當使用一個哈希表,要指定用作鍵的對象,以及要鏈接到該鍵的值。
然後,該鍵經過哈希處理,所得到的散列碼被用作存儲在該表中值的索引。
Hashtable定義了四個構造方法。
Hashtable() Hashtable(int size) //創建指定大小的哈希表 Hashtable(int size,float fillRatio) //創建一個指定大小的哈希表,並且通過fillRatio指定填充比例。填充比例必須介於0.0和1.0之間,它決定了哈希表在重新調整大小之前的充滿程度 Hashtable(Map m) //創建一個以M中元素為初始化元素的哈希表。哈希表的容量被設置為M的兩倍。
Hashtable中除了從Map接口中定義的方法外,還定義了以下方法:
package three; import java.util.*; public class myHashTable { public static void main(String args[]) { // Create a hash map Hashtable balance = new Hashtable(); Enumeration names; String str; double bal; balance.put("Zara", new Double(3434.34)); balance.put("Mahnaz", new Double(123.22)); balance.put("Ayan", new Double(1378.00)); balance.put("Daisy", new Double(99.22)); balance.put("Qadir", new Double(-19.08)); // Show all balances in hash table. names = balance.keys(); while(names.hasMoreElements()) { str = (String) names.nextElement(); System.out.println(str + ": " + balance.get(str)); } System.out.println(); // Deposit 1,000 into Zara's account bal = ((Double)balance.get("Zara")).doubleValue(); balance.put("Zara", new Double(bal+1000)); System.out.println("Zara's new balance: " + balance.get("Zara")); } /*輸出結果 Qadir: -19.08 Zara: 3434.34 Mahnaz: 123.22 Daisy: 99.22 Ayan: 1378.0 Zara's new balance: 4434.34 * */ } practice
Properties 繼承於 Hashtable.Properties 類表示了一個持久的屬性集.屬性列表中每個鍵及其對應值都是一個字符串。
Properties 類被許多Java類使用。例如,在獲取環境變量時它就作為System.getProperties()方法的返回值。
Properties 定義如下實例變量.這個變量持有一個Properties對象相關的默認屬性列表。
Properties defaults;
Properties類定義了兩個構造方法。
Properties() //第一個構造方法沒有默認值。 Properties(Properties propDefault) //使用propDefault 作為默認值。 兩種情況下,屬性列表都為空
除了從Hashtable中所定義的方法,Properties定義了以下方法:
package three; import java.util.*; public class myProperties { public static void main(String args[]) { Properties capitals = new Properties(); Set states; String str; capitals.put("Illinois", "Springfield"); capitals.put("Missouri", "Jefferson City"); capitals.put("Washington", "Olympia"); capitals.put("California", "Sacramento"); capitals.put("Indiana", "Indianapolis"); // Show all states and capitals in hashtable. states = capitals.keySet(); // get set-view of keys Iterator itr = states.iterator(); while(itr.hasNext()) { str = (String) itr.next(); System.out.println("The capital of " + str + " is " + capitals.getProperty(str) + "."); } System.out.println(); // look for state not in list -- specify default str = capitals.getProperty("Florida", "Not Found"); System.out.println("The capital of Florida is " + str + "."); } /*輸出結果 * The capital of Missouri is Jefferson City. The capital of Illinois is Springfield. The capital of Indiana is Indianapolis. The capital of California is Sacramento. The capital of Washington is Olympia. The capital of Florida is Not Found. * */ } practice
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。
Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:
類型安全。 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼注釋中)。
Java 程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如“String 列表”或者“String 到 String 的映射”。通過在變量聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當作 ClassCastException 展示出來。將類型檢查從運行時挪到編譯時有助於您更容易找到錯誤,並可提高程序的可靠性。
消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。
盡管減少強制類型轉換可以降低使用泛型類的代碼的羅嗦程度,但是聲明泛型變量會帶來相應的啰嗦。
你可以寫一個泛型方法,該方法在調用時可以接收不同類型的參數。根據傳遞給泛型方法的參數類型,編譯器適當地處理每一個方法調用。
下面是定義泛型方法的規則:
下面的例子演示了如何使用泛型方法打印不同字符串的元素:
public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] inputArray ) { // 輸出數組元素 for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); } public static void main( String args[] ) { // 創建不同類型數組: Integer, Double 和 Character Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "整型數組元素為:" ); printArray( intArray ); // 傳遞一個整型數組 System.out.println( "\n雙精度型數組元素為:" ); printArray( doubleArray ); // 傳遞一個雙精度型數組 System.out.println( "\n字符型數組元素為:" ); printArray( charArray ); // 傳遞一個字符型數組 } } /*輸出結果 整型數組元素為: 1 2 3 4 5 雙精度型數組元素為: 1.1 2.2 3.3 4.4 字符型數組元素為: H E L L O * */ View Code
可能有時候,你會想限制那些被允許傳遞到一個類型參數的類型種類范圍。例如,一個操作數字的方法可能只希望接受Number或者Number子類的實例。這就是有界類型參數的目的。
要聲明一個有界的類型參數,首先列出類型參數的名稱,後跟extends關鍵字,最後緊跟它的上界。
下面的例子演示了"extends"如何使用在一般意義上的意思"extends"(類)或者"implements"(接口)。該例子中的泛型方法返回三個可比較對象的最大值。
public class MaximumTest { // 比較三個值並返回最大值 public static <T extends Comparable<T>> T maximum(T x, T y, T z) { T max = x; // 假設x是初始最大值 if ( y.compareTo( max ) > 0 ){ max = y; //y 更大 } if ( z.compareTo( max ) > 0 ){ max = z; // 現在 z 更大 } return max; // 返回最大對象 } public static void main( String args[] ) { System.out.printf( "%d, %d 和 %d 中最大的數為 %d\n\n", 3, 4, 5, maximum( 3, 4, 5 ) ); System.out.printf( "%.1f, %.1f 和 %.1f 中最大的數為 %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) ); System.out.printf( "%s, %s 和 %s 中最大的數為 %s\n","pear", "apple", "orange", maximum( "pear", "apple", "orange" ) ); } } /*輸出結果 3, 4 和 5 中最大的數為 5 6.6, 8.8 和 7.7 中最大的數為 8.8 pear, apple 和 orange 中最大的數為 pear */ View Code
泛型類的聲明和非泛型類的聲明類似,除了在類名後面添加了類型參數聲明部分。
和泛型方法一樣,泛型類的類型參數聲明部分也包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱為一個類型變量,是用於指定一個泛型類型名稱的標識符。因為他們接受一個或多個參數,這些類被稱為參數化的類或參數化的類型。
如下實例演示了我們如何定義一個泛型類:
public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("嗨..")); System.out.printf("整型值為 :%d\n\n", integerBox.get()); System.out.printf("字符串為 :%s\n", stringBox.get()); } } /* 整型值為 :10 字符串為 :嗨.. */ View Code
類型通配符一般是使用?代替具體的類型參數
例如 List<?> 在邏輯上是List<String>,List<Integer> 等所有List<具體類型實參>的父類。
package three; import java.util.ArrayList; import java.util.List; public class GenericTest { public static void main(String[] args) { List<String> name = new ArrayList<String>(); List<Integer> age = new ArrayList<Integer>(); List<Number> number = new ArrayList<Number>(); name.add("nick"); age.add(18); number.add(530); getData(name); getData(age); getData(number); // getUperNumber(name); //報錯 getUperNumber(age); getUperNumber(number); } public static void getData(List<?> data) { System.out.println("getData :" + data.get(0)); } public static void getUperNumber(List<? extends Number> data) { System.out.println("getUperNumber :" + data.get(0)); } } /*輸出結果 * getData :nick getData :18 getData :530 getUperNumber :18 getUperNumber :530 * */ View Code