該文章所講內容基本涵蓋了Collection裡面的所有東西,雖然基於jdk 1.5的,但是思路很清晰
1.引言
1.1 Collection框架的介紹
雖然我們可以使用數組去存儲具有相同類型的元素集合(包括基本類型和對象類型),但是數組不支持所謂的動態內存分配,一旦分配之後,它的長度就是固定的,無法改變,另外,數組是一個簡單的線性結構,在我們的實際開發中,可能會需要更復雜的數據結構,例如linked list, stack, hash table, sets, 或者 trees.
在Java中,有統一的Collection框架來支持這種動態分配的數據結構(例如:ArrayList, LinkedList, Vector, Stack, HashSet, HashMap, Hashtable),它通過接口統一了所有繼承類的基本操作,形成了一套基本的體系。
Java集合框架包(java.util)裡包含下面內容:
1、接口集合
2、類實現
3、算法(例如排序和查找)
1.2 Colloction例子- ArrayList (Pre-JDK 1.5)
ArrayList是一個線性的數據結構,類似於數組,但是它是可伸縮的。下面使用了ArrayList來存放String集合對象。
// Pre-JDK 1.5 import java.util.List; import java.util.ArrayList; import java.util.Iterator; public class ArrayListPreJDK15Test { public static void main(String[] args) { List lst = new ArrayList(); // A List contains instances of Object. Upcast ArrayList to List lst.add("alpha"); // add() takes Object. String upcast to Object implicitly lst.add("beta"); lst.add("charlie"); System.out.println(lst); // [alpha, beta, charlie] // Get a "iterator" instance from List to iterate thru all the elements of the List Iterator iter = lst.iterator(); while (iter.hasNext()) { // any more element // Retrieve the next element, explicitly downcast from Object back to String String str = (String)iter.next(); System.out.println(str); } } }
2-4行引入了java.util包中集合框架的類和接口
上圖可以看到ArrayList的繼承關系,我們可以看到ArrayList實現了List, Collection 和 Iterable接口,Collection和Iterable接口定義了所有集合都需要實現的基本操作。Collection接口定義了如何添加和移除一個元素。Iterable接口定義了一種機制去遍歷集合中的所有元素。一般不直接使用Collection接口,而是使用它的子接口List(支持索引訪問的有序列表)、Set(不允許元素的重復)、Queue(先進先出)。
在第8行中,創建了一個ArrayList實例,並將它向上轉換為List接口,因為ArrayList實現了List接口,一般比較良好的編程操作都是在接口上面,而不是在它的實現上面。Collection框架提供了一個接口集合,這樣你可以使用這些接口而不是它的實現。
// Pre-JDK 1.5 boolean add(Object element) // adds an element boolean remove(Object element) // removes an element int size() // returns the size boolean isEmpty() // checks if empty可以看到,在pre-JDK 1.5上,add(Object)方法操作的是Object對象,Object類是所有類的超類,因此,所有類都是Object類的子類,任何Java類都可以向上轉換為Object類,然後添加到集合中,並且這個轉換是編譯器隱式操作的,它是類型安全的。 Iterable接口包含一個抽象方法去獲取集合所關聯的Iterator對象,這樣通過Iterator對象就可以遍歷整個集合。
Iterator iterator(); // returns an Iterator object to iterate thru all the elements of the collectionIterator聲明了下面的抽象方法進行集合遍歷
// Pre-JDK 1.5 boolean hasNext() // returns true if it has more elements Object next() // returns the next element void remove() // removes the last element returned by the iterator第15-20行獲取ArrayList相關聯的Iterator對象,並且使用while循環來遍歷集合。
iter.next()
方法方法返回的是Object類型的對象,這個在上面add(Object)中說過,我們在得到Object類型的對象之後,需要顯式的將類型轉換為String類型,因為編譯器不知道我們需要的具體類型。其實我們也可以使用LinkedList, Vector 和 Stack,並且只需要修改第8行的實例化代碼即可,其他不變,這也可以看出接口的統一性,雖然實現不同,但是操作的效果相同。
List lst = new LinkedList(); // use "LinkedList" implementation // or List lst = new Vector(); // use "Vector" implementation // or List lst = new Stack(); // use "Stack" implementation
// lst is designed to hold Strings lst.add(new Integer(88)); // adds an Integer, implicitly upcast to Object, okay in compile/runtime Iterator iter = lst.iterator(); while (iter.hasNext()) { String str = (String)iter.next(); // compile okay but runtime ClassCastException System.out.println(str); }
List上面我們傳遞了一個String類型,在ArrayList內部的操作使用的就是String類型,避免了類型的轉換,如果添加其他類型的元素,編譯器就會報錯。 1.5 帶有泛型的ArrayListlst = new ArrayList (); // read as List of Strings, ArrayList of Strings
// Post-JDK 1.5 with Generics import java.util.List; import java.util.ArrayList; import java.util.Iterator; public class ArrayListPostJDK15Test { public static void main(String[] args) { Listlst = new ArrayList (); // Inform compiler about the type lst.add("alpha"); // compiler checks if argument's type is String lst.add("beta"); lst.add("charlie"); System.out.println(lst); // [alpha, beta, charlie] Iterator iter = lst.iterator(); // Iterator of Strings while (iter.hasNext()) { String str = iter.next(); // compiler inserts downcast operator System.out.println(str); } // lst.add(new Integer(1234)); // ERROR: compiler can detect wrong type // Integer intObj = lst.get(0); // ERROR: compiler can detect wrong type // Enhanced for-loop (JDK 1.5) for (String str : lst) { System.out.println(str); } } }
// Pre-JDK 1.5 List lst = new ArrayList(); // No type information lst.add("alpha"); // Without generics, compiler can't check if the type is correct
// Pre-JDK 1.5 Integer intObj = new Integer(5566); // wrap int to Integer int i = intObj.intValue(); // unwrap Integer to int Double doubleObj = new Double(55.66); // wrap double to Double double d = doubleObj.doubleValue(); // unwrap Double to double
// JDK 1.5 Integer intObj = 5566; // autobox from int to Integer int i = intObj; // auto-unbox from Integer to int Double doubleObj = 55.66; // autoboxing from double to Double double d = doubleObj; // atuo-unbox from Double to double
// Pre-JDK 1.5 import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; public class PrimitiveCollectionPreJDK15 { public static void main(String[] args) { List lst = new ArrayList(); // Add 10 random primitive int into the List Random random = new Random(); for (int i = 1; i <= 10; ++i) { // Wrap the primitive int into Integer, upcast to Object lst.add(new Integer(random.nextInt(10))); } System.out.println(lst); Iterator iter = lst.iterator(); while (iter.hasNext()) { // Explicit downcast to Integer, then unwrap to int int i = ((Integer)iter.next()).intValue(); // un-safe at runtime System.out.println(i); } } }
// Post-JDK 1.5 import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; public class PrimitiveCollectionJDK15 { public static void main(String[] args) { Listlst = new ArrayList (); // Add 10 random primitive int into the List Random random = new Random(); for (int i = 1; i <= 10; ++i) { lst.add(random.nextInt(10)); // autobox to Integer, upcast to Object, type-safe } System.out.println(lst); // Transverse via iterator Iterator iter = lst.iterator(); while (iter.hasNext()) { int i = iter.next(); // downcast to Integer, auto-unbox to int, type-safe System.out.println(i); } // Transverse via enhance for-loop for (int i : lst) { // downcast to Integer, auto-unbox to int, type-safe System.out.println(i); } // Retrieve via for-loop with List's index for (int i = 0; i < lst.size(); ++i) { int j = lst.get(i); // downcast to Integer, auto-unbox to int, type-safe System.out.println(j); } } }
2.1 Iterable
Iterableiterator()
用來獲取一個Iterator
Iterator所有Collection的實現(例如:iterator(); // Returns the associated Iterator instance // that can be used to transverse thru all the elements of the collection
ArrayList
,LinkedList
,Vector
)必須實現這個方法,並且返回的對象也實現了Iterator接口。
2.2 Iterator
Iterator
boolean hasNext() // Returns true if it has more elements E next() // Returns the next element of generic type E void remove() // Removes the last element returned by the iterator
Listlst = new ArrayList (); lst.add("alpha"); lst.add("beta"); lst.add("charlie"); // Retrieve the Iterator associated with this List via the iterator() method Iterator iter = lst.iterator(); // Transverse thru this List via the Iterator while (iter.hasNext()) { // Retrieve each element and process String str = iter.next(); System.out.println(str); }
對於使用Iterator進行集合的遍歷,在JDK 1.5中引入了新的加強版的for循環,使用它進行集合的遍歷更加方便。
下面是基本的用法
for ( type item : aCollection ) { body ; }
加強版的for循環提供了一個方便的方法去遍歷集合元素,但是它將Iterator給隱藏了,因此你不能移除或者替換這個元素。循環變量接受的是一個引用的副本,因此這個加強版的for循環可以通過該引用修改對應的可變元素(例如:StringBuilder),但是不能修改不可變元素(例如:String和基本數據類型對應的對象類型)。
例子——使用加強版的for循環對於集合的可變元素(例如:StringBuilder)
import java.util.List; import java.util.ArrayList; public class ForEachMutableTest { public static void main(String[] args) { Listlst = new ArrayList (); lst.add(new StringBuilder("alpha")); lst.add(new StringBuilder("beta")); lst.add(new StringBuilder("charlie")); System.out.println(lst); // [alpha, beta, charlie] for (StringBuilder sb : lst) { sb.append("88"); // can modify "mutable" objects } System.out.println(lst); // [alpha88, beta88, charlie88] } }
import java.util.List; import java.util.ArrayList; public class ForEachImmutableTest { public static void main(String[] args) { Listlst = new ArrayList (); lst.add("alpha"); lst.add("beta"); lst.add("charlie"); System.out.println(lst); // [alpha, beta, charlie] for (String str : lst) { str += "change!"; // cannot modify "immutable" objects } System.out.println(lst); // [alpha, beta, charlie] } }
Collection
// Basic Operations int size() // Returns the number of elements of this Collection void clear() // Removes all the elements of this Collection boolean isEmpty() // Returns true if there is no element in this Collection boolean add(E element) // Ensures that this Collection contains the given element boolean remove(Object element) // Removes the given element, if present boolean contains(Object element) // Returns true if this Collection contains the given element // Bulk Operations with another Collection boolean containsAll(Collection c) // Collection of any "unknown" object boolean addAll(Collection c) // Collection of E or its sub-types boolean removeAll(Collection c) boolean retainAll(Collection c) // Comparison - Objects that are equal shall have the same hashCode boolean equals(Object o) int hashCode() // Array Operations Object[] toArray() // Convert to an Object arrayT[] toArray(T[] a) // Convert to an array of the given type T
Collection中只能包含對象類型,不能包含基本數據類型(例如:int和double),所以如果需要將基本數據類型包裝成對應的對象類型,JDK 1.5引入了自動拆箱和解箱操作來解決這個問題,這個在前面已經講過。
2.5 List
在實際開發過程中,我們一般使用Collection的子類接口——List
,Set
, 和Queue
List
:它相當於一個可擴展的線性數組,可以進行索引訪問,並且可以包含重復的元素,我們經常會使用到的一些List的實現類為:ArrayList
,LinkedList
,Vector
和Stack。
Set
:相當於一個數據集合,它不能包含重復的元素,經常使用到的Set的實現類為:HashSet和
LinkedHashSet,子接口為SortedSet
是一個有序的元素集合,TreeSet是SortedSet
的實現類。
Queue
: 相當於一個先進先出的隊列,它的子接口Deque相當於一個可在兩端操作的雙向隊列,實現類為PriorityQueue
,ArrayDeque
和LinkedList。
2.6 MapHashMap
,Hashtable
和LinkedHashMap,它的子接口SortedMap
表示一個基於鍵值key有序的鍵值對集合,實現類為TreeMap。
List
,Set
, 和Queue來代替Collection接口,這些子接口只是對Collection接口操作的重新定義。
List相當於一個可擴展的線性數組,它支持索引訪問,它可以包含重復的元素,也可以包含null元素。
在List
父接口的基礎上,List
也聲明了一些抽象方法。
// Operations at a specified index position void add(int index, E element) // add E set(int index, E element) // replace E get(int index) // retrieve without remove E remove(int index) // remove last retrieved int indexOf(Object obj) int lastIndexOf(Object obj) // Operations on a range fromIndex (inclusive) toIndex (exclusive) ListsubList(int fromIndex, int toIndex) ...... // Operations inherited from Collection int size() boolean isEmpty() boolean add(E element) boolean remove(Object obj) boolean contains(Object obj) void clear(); ......
ArrayList
和Vector。
3.1 ArrayList & Vector - List的實現類
ArrayListaddElement()
,removeElement()
,setElement()
,elementAt()
,firstElement()
,lastElement()
,insertElementAt()
),毫無疑問,這些遺留的方法是可以使用的,並且保持向後兼容。
需要注意的是,雖然ArrayListE push(E element) // pushes the specified element onto the top of the stack E pop() // removes and returns the element at the top of the stack E peek() // returns the element at the top of stack without removing boolean empty() // tests if this stack is empty int search(Object obj) // returns the distance of the specified object from the top // of stack (distance of 1 for TOS), or -1 if not found
LinkedList
也實現了Queue
和Deque,因此它可以充當FIFO 和 LIFO隊列。
3.4 轉換一個List為一個數組 - toArray()
在超類接口Collection中定義了一個toArray()方法,它可以使用該列表來創建一個數組,返回的數組可以進行自由的修改。
Object[] toArray() // Object[] versionT[] toArray(T[] a) // Generic type version
import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class TestToArray { public static void main(String[] args) { Listlst = new ArrayList (); lst.add("alpha"); lst.add("beta"); lst.add("charlie"); // Use the Object[] version Object[] strArray1 = lst.toArray(); System.out.println(Arrays.toString(strArray1)); // [alpha, beta, charlie] // Use the generic type verion - Need to specify the type in the argument String[] strArray2 = lst.toArray(new String[0]); strArray2[0] = "delta"; // modify the returned array System.out.println(Arrays.toString(strArray2)); // [delta, beta, charlie] System.out.println(lst); // [alpha, beta, charlie] - no change in the original list } }
import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class TestArrayAsList { public static void main(String[] args) { String[] strs = {"alpha", "beta", "charlie"}; System.out.println(Arrays.toString(strs)); // [alpha, beta, charlie] Listlst = Arrays.asList(strs); System.out.println(lst); // [alpha, beta, charlie] // Changes in array or list write thru strs[0] += "88"; lst.set(2, lst.get(2) + "99"); System.out.println(Arrays.toString(strs)); // [alpha88, beta, charlie99] System.out.println(lst); // [alpha88, beta, charlie99] // Initialize a list using an array List lstInt = Arrays.asList(22, 44, 11, 33); System.out.println(lstInt); // [22, 44, 11, 33] } }
使用Collections.sort()
和Arrays.sort()可進行排序
有些集合,例如SortedSet
(TreeSet
) 和SortMap
(TreeMap
)是有序的
有兩種方法進行排序:
是對象實現java.lang.Comparable,重寫compareTo()方法去指定兩個對象的排序方法。創建一個指定的java.util.Comparator對象,實現compare()方法去指定兩個對象的比較。
4.1 java.lang.Comparableint compareTo(T o) // Returns a negative integer, zero, or a positive integer // as this object is less than, equal to, or greater than the given object
equals()
和hashCode()是一致的,即:
如果compareTo()返回0,那麼equals()應該返回true。
如果equals()返回true,那麼hashCode()應該返回的是相同的int值。
所有基本數據類型的包裝類(例如:Byte
,Short
,Integer
,Long
,Float
,Double
,Character
andBoolean
)都實現了Comparable接口的compareTo()
方法。
例子:
工具類java.util.Arrays
和java.util.Collections對應不同的算法提供了很多的靜態方法,例如排序和查找。
在下面這個例子中,使用Arrays.sort()
和Collections.sort()
方法去對一個String數組和一個Integer的List進行排序,使用的就是它們默認的Comparable實現。
import java.util.Arrays; import java.util.List; import java.util.ArrayList; import java.util.Collections; public class TestComparable { public static void main(String[] args) { // Sort and search an "array" of Strings String[] array = {"Hello", "hello", "Hi", "HI"}; // Use the Comparable defined in the String class Arrays.sort(array); System.out.println(Arrays.toString(array)); // [HI, Hello, Hi, hello] // Try binary search - the array must be sorted System.out.println(Arrays.binarySearch(array, "Hello")); // 1 System.out.println(Arrays.binarySearch(array, "HELLO")); // -1 (insertion at index 0) // Sort and search a "List" of Integers List4.2 java.util.Comparatorlst = new ArrayList (); lst.add(22); // auto-box lst.add(11); lst.add(44); lst.add(33); Collections.sort(lst); // Use the Comparable of Integer class System.out.println(lst); // [11, 22, 33, 44] System.out.println(Collections.binarySearch(lst, 22)); // 1 System.out.println(Collections.binarySearch(lst, 35)); // -4 (insertion at index 3) } }
Collections.sort()
和Arrays.sort()
)來實現大小比較,如果Comparator可用,那麼Comparator將會重寫Comparable。
int compare(T o1, T o2) // Returns a negative integer, zero, or a positive integer as the // first argument is less than, equal to, or greater than the second.需要注意的是,你需要創建Comparator
compare()
方法去進行對象的比較,在Comparable中,只需要調用對象的compareTo()方法,它只需要傳遞一個參數,表示當前對象和另一個對象比較,在Comparatorimport java.util.Arrays; import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class TestComparator { // Define a Comparatorto order strings in case-insensitive manner public static class StringComparator implements Comparator { @Override public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); } } // Define a Comparator to order Integers based on the least significant digit public static class IntegerComparator implements Comparator { @Override public int compare(Integer s1, Integer s2) { return s1%10 - s2%10; } } public static void main(String[] args) { // Use a customized Comparator for Strings Comparator compStr = new StringComparator(); // Sort and search an "array" of Strings String[] array = {"Hello", "Hi", "HI", "hello"}; Arrays.sort(array, compStr); System.out.println(Arrays.toString(array)); // [Hello, hello, Hi, HI] System.out.println(Arrays.binarySearch(array, "Hello", compStr)); // 1 System.out.println(Arrays.binarySearch(array, "HELLO", compStr)); // 1 (case-insensitive) // Use a customized Comparator for Integers Comparator compInt = new IntegerComparator(); // Sort and search a "List" of Integers List lst = new ArrayList (); lst.add(42); // auto-box lst.add(21); lst.add(34); lst.add(13); Collections.sort(lst, compInt); System.out.println(lst); // [21, 42, 13, 34] System.out.println(Collections.binarySearch(lst, 22, compInt)); // 1 System.out.println(Collections.binarySearch(lst, 35, compInt)); // -5 (insertion at index 4) } }