第七章 泛型與集合html
7.1 重寫hashCode()和equals()方法java
考試目標6.2 區分hashCode()和equals()方法的正確設計和錯誤設計,並解釋 == 和equals()方法的不一樣。程序員
toString()方法算法
剛沒有重寫toString方法時,顯示該對象哈希碼的無符號十六進制表示。如:數組
7.1.1 重寫equals()方法框架
使用==來判斷兩個引用變量是否引用了同一個對象。ide
使用equals()來判斷兩個對象在乎義上是否等價。函數
不重寫equals()意味着什麼?工具
若是不重寫equals(),則對象將不會是有用的哈希鍵。
若是不重寫equals(),則不一樣的對象不能認爲是等價的。
總之,若是不重寫,則equals()只使用==運算符進行比較。
public class TestEquals { public static void main(String[] args) { TestEquals t1 = new TestEquals(); TestEquals t2 = new TestEquals(); System.out.println(t1.toString()); System.out.println(t2.toString()); System.out.println(t1==t2); System.out.println(t1.equals(t2)); } } //result: //testGenerics.TestEquals@35ce36 //testGenerics.TestEquals@757aef //false //false
實現equals()方法
public class TestEquals2 { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public boolean equals(Object o){ if ((o instanceof TestEquals2)&&(this.id==((TestEquals2) o).getId())) { return true; }else{ return false; } } public static void main(String[] args) { TestEquals2 t1 = new TestEquals2(); t1.setId(1); TestEquals2 t2 = new TestEquals2(); t2.setId(1); TestEquals2 t3 = new TestEquals2(); t3.setId(2); System.out.println(t1.toString()); System.out.println(t2.toString()); System.out.println(t1==t2); System.out.println(t1.equals(t2)); System.out.println(t1.equals(t3)); } } //result: //testGenerics.TestEquals2@35ce36 //testGenerics.TestEquals2@757aef //false //true //false
equals()契約
- 自反的 對於任何引用值x,x.equals(x)都應該返回true。
- 對稱的 對於任何引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)才返回true。
- 傳遞的 對於任何引用值x,y,z,若是x.equals(y)返回true時,而且y.equals(z)返回true。
- 一致的 對於任何引用值x和y,若是在該對象上的相等性比較中所使用的信息沒有作修改,則對x.equals(y)的屢次調用要一致地返回true或者false。
- 對於變元爲null的都應返回false
7.1.2 重寫hashCode()方法
理解哈希碼
一個類的許多對象,根據同一算法生成的值分類(分桶),當查找時,先由hashCode()算出值,肯定在哪一個哈希桶裏。
不一樣的對象可能有相同的哈希值;重寫過equals方法的相同對象,應該有同樣的哈希值。
實現hashCode()方法
說白了就是怎樣的hashCode算法,都返回一個默認值,好比1932,這樣的作法是「合法的」,可是並不合適。這就至關於把全部的對象都放在了一個桶中,並無達到分類、管理對象的目的。
「適當的」方法是將如id等,能表明對象惟一性等的屬性加入到hashCode的算法中,儘量的將各個桶平衡。
hashCode()契約
- 在Java應用程序的同一個執行期間,若是沒有修改對象的equals()比較內使用的任何信息,則不管何時在相同的對象上屢次調用hashCode()方法時,它必須一致地返回同一個整數。
- 若是equals()方法兩個對象是相等的,則在這兩個對象的任意一個上調用hashCode()方法,必須產生相同的整數結果。
- 若是根據equals()方法兩個對象是不相等的,則在這兩個對象的任意一個上調用hashCode()方法,並不要求產生不一樣的整數結果。然而程序員應該知道,爲不相等的對象產生不一樣的整數結果可能提升哈希表的性能。
7.2 集合
考試目標6.1 給定一個設計場景,判斷應該使用哪些集合類和/或接口來恰當地實現該設計,包括使用Comparable接口。
7.2.1 用集合作什麼
- 將對象添加到集合。
- 從集合中刪除對象。
- 找出一個對象(或一組對象)是否位於集合內。
- 從集合中檢索對象(不刪除它)。
- 迭代遍歷集合,逐個查看每一個元素(對象)。
集合框架的重點接口和類
- collection 表示概念上的集合。
- Collection 表示java.util.Collection接口,Set、List和Queue擴展自它。Map並不擴展自它。
- Collections 表示java.util.Collections類,它擁有大量的靜態實用工具方法,用於集合。
集合的4種基本形式:
List 事物列表(實現List的類)
Set 具備惟一性的事物(實現Set的類)
Map 具備惟一ID的事物(實現Map的類)
Queue 按照被處理的順序排列的事物
ordered(有序的,有秩序的,有先來後到的)
表示該集合可以按照特定的順序(而不是隨機的順序)迭代遍歷這個集合。
sorted(已排序,有順序的,有順序規則的)
表示集合中的順序是根據某個或某些規則肯定的。
7.2.2 List接口
List關心的是索引。
ArrayList
能夠將它理解成一個可增加的數組。
它不是同步的。
它提供快速迭代和快速隨機訪問的能力。
Vector
它就是個同步的ArrayList,線程安全。
LinkedList
迭代比ArrayList慢,但適合用來快速插入和刪除。
7.2.3 Set接口
Set關心惟一性,它不容許重複
HashSet
HashSet 是一種 unsorted、unordered 的 Set 。它使用被插入對象的哈希碼,所以,hashCode()實現越有效,將獲得的訪問性能就越好。、
速度訪問,保證沒有重複,不提供任何順序。
LinkedHashSet
是HashSet的ordered版本,按照插入順序迭代
TreeSet
是sorted的,按照元素的天然順序進行升序排列。或者構造一個帶構造函數的TreeSet,它讓你經過使用Comparable或Comparator爲集合提供本身的規則。
7.2.4 Map接口
Map關心惟一的標識符
HashMap
最快速地更新鍵/值對。容許一個null鍵和多個null值。
Hashtable
HashMap的同步版本,不容許null鍵或null值。
LinkedHashMap
迭代更快,按照插入順序或者最後訪問的順序迭代。容許一個null鍵和多個null值。
TreeMap
一種排序映射。
7.2.5 Queue接口
PriortyQueue
按照元素的優先級排序的「待執行任務」的列表。
類 | Map | Set | List | ordered | sorted |
HashMap | x | 否 | 否 | ||
Hashtable | x | 否 | 否 | ||
TreeMap | x | sorted | 按照天然順序或自定義比較規則 | ||
LinkedHashMap | x | 按照插入順序或最後的訪問順序 | 否 | ||
HashSet | x | 否 | 否 | ||
TreeSet | x | sorted | 按照天然順序或自定義比較規則 | ||
LinkedHashSet | x | 按照插入順序 | 否 | ||
ArrayList | x | 按照索引 | 否 | ||
Vector | x | 按照索引 | 否 | ||
LinkedList | x | 按照索引 | 否 | ||
PriorityQueue | sorted | 按照「要執行的任務」的順序 |
7.3 使用集合框架
考試目標6.3 編寫使用NavigableSet和NavigableMap接口的代碼。
考試目標6.5 利用java.util包中的功能編寫代碼,經過排序、執行折半查找或將數組轉換成列表操做列表。利用java.util包中的功能編寫代碼,經過排序、執行折半查找或將數組轉換成列表來操做數組。利用java.util.Comparator和java.lang.Comparable接口來影響列表或數組的排序。此外,還要認識基本包裝器類和java.lang.String在排序時的「天然順序」的影響。
7.3.1 ArrayList基礎
與數組相比,ArrayList創建時不用指定長度,能夠動態增加,提供更增強大的插入和查找機制。
7.3.2 用集合進行自動裝箱
Java5中,能夠將集合中的基本類型自動裝箱爲包裝類型。
7.3.3 排序集合與數組
排序集合
import java.util.ArrayList; import java.util.Collections; public class TestSort1 { public static void main(String[] args) { ArrayList<String> stuff = new ArrayList<String>(); stuff.add("Denver"); stuff.add("Boulder"); stuff.add("Vail"); stuff.add("Aspen"); stuff.add("Telluride"); System.out.println("unsorted " + stuff); Collections.sort(stuff); //天然排序 System.out.println("sorted " + stuff); } }
unsorted [Denver, Boulder, Vail, Aspen, Telluride] sorted [Aspen, Boulder, Denver, Telluride, Vail]
Comparable接口
Comparable接口由Collections.sort()方法和java.util.Arrays.sort()方法用來分別排序List和對象數組。
要實現java.lang.Comparable,類必須實現一種方法compareTo()。下面是compareTo()的調用:
int x = thisObj.compareTo(anotherObj);
該方法返回以下int結果:
負數,若是thisObj < anotherObj
零,若是thisObj == anotherObj
正數,若是thisObj > anotherObj
總之,實現compareTo()就是制定對象排序的標準。
用Comparator排序
Collections.sort()還有一個重載:
public static <T> void sort(List<T> list,
Comparator<? super T> c)
Comparable接口和Comparator接口的比較
java.lang.Comparable | java.util.Comparator |
int objOne.compareTo(objTwo) | int compare(objOne,objTwo) |
返回: 負數,若是objOne < objTwo 零, 若是objOne==objTwo 正數,若是objOne > objTwo |
相同 |
必須修改想排序其實例的類 | 構建一個類,它不一樣於想排序其實例的類 |
只能夠建立一個排序序列 | 能夠建立多個排序序列 |
在API中常常由以下方式實現: String、包裝器類、Date、Calendar |
意味着要實現成排序第三方類的實例 |
用Arrays類排序
Arrays.sort(arrayToSort)
Arrays.sort(arrayToSort,Comparator)
查找數組與集合
static int binarySearch(Object [] ar,Object key)
方法返回兩種結果,若是在數組ar中查到了key,則返回key的索引值;不然返回插入點。
插入點與索引值:
插入點: -1 -2 -3 -4 -5 -(n+1) -(n+2)
數組: ar[0] ar[1] ar[2] ar[3] ...............ar[n]
索引值: 0 1 2 3 n
package testGenerics; import java.util.Arrays; import java.util.Comparator; public class SearchObjArray { public static void main(String[] args) { String[] sa = {"one","two","three","four"}; Arrays.sort(sa); //#1 for(String s:sa){ System.out.print(s+" "); } System.out.println("\none = "+Arrays.binarySearch(sa, "one")); //#2 System.out.println("now reverse sort"); ReSortComparator rs = new ReSortComparator(); //#3 Arrays.sort(sa,rs); for(String s : sa){ System.out.print(s + " "); } System.out.println("\none = "+Arrays.binarySearch(sa, "one")); //#4 System.out.println("one = "+Arrays.binarySearch(sa,"one",rs)); //#5 } static class ReSortComparator implements Comparator<String>{ //#6 public int compare(String a,String b){ return b.compareTo(a); //#7 } } } //result: //four one three two //one = 1 //now reverse sort //two three one four //one = -1 //one = 2 /* * #1:按字母順序(天然順序)排序sa數組。 * #2:查找元素「one」的位置,該位置爲1. * #3:建立一個Comparator實例。下一行使用comparator從新排序數組。 * #4:嘗試查找數組。咱們沒有將用於排序數組的Comparator傳遞給binarySearch()方法, * 所以,得到一個不正確的(不明確的)答案。 * #5:再次查找,將Comparator傳遞給binarySearch()。此次得到了正確答案2. * #6:定義Comparator,這裏讓它稱爲一個內部類是可行的。 * #7:經過在調用compareTo()中變元的交換使用,獲得反向的排序。 */
在數組和List之間進行轉換:
Arrays.asList()
List.toArray()
使用List
能夠經過Iterator迭代器來遍歷List,Iterator的兩個方法:
boolean hasNext()
Object next()
7.3.4 導航(查找)TreeSet與TreeMap
TreeSet和TreeMap實現了 Java6 的新接口 java.util.NavigableSet 和 java.util.NavigableMap
如今有一個需求是我想查在一個Set裏,比1600大一點的那個元素,下面是在Java5和Java6的不一樣實現
package testGenerics; import java.util.TreeSet; public class Ferry { public static void main(String[] args) { TreeSet<Integer> times = new TreeSet<Integer>(); times.add(1205); times.add(1505); times.add(1545); times.add(1830); times.add(2010); times.add(2100); TreeSet<Integer> subset = new TreeSet<Integer>(); subset = (TreeSet<Integer>) times.headSet(1600); System.out.println("J5 - last before 4pm is:"+subset.last()); TreeSet<Integer> sub2 = new TreeSet<Integer>(); sub2 = (TreeSet<Integer>) times.tailSet(2000); System.out.println("J5 - first after 8pm is:"+sub2.first()); System.out.println("J6 - last before 4pm is:"+times.lower(1600)); System.out.println("J6 - first after 8pm is:"+times.higher(2000)); } } //result: //J5 - last before 4pm is:1545 //J5 - first after 8pm is:2010 //J6 - last before 4pm is:1545 //J6 - first after 8pm is:2010
7.3.5 其餘導航方法
輪詢polling
TreeSet
E |
ceiling(E e) 返回此 set 中大於等於給定元素的最小元素;若是不存在這樣的元素,則返回 null 。 |
E |
higher(E e) 返回此 set 中嚴格大於給定元素的最小元素;若是不存在這樣的元素,則返回 null 。 |
E |
floor(E e) 返回此 set 中小於等於給定元素的最大元素;若是不存在這樣的元素,則返回 null 。 |
E |
lower(E e) 返回此 set 中嚴格小於給定元素的最大元素;若是不存在這樣的元素,則返回 null 。 |
E |
pollFirst() 獲取並移除第一個(最低)元素;若是此 set 爲空,則返回 null 。 |
E |
pollLast() 獲取並移除最後一個(最高)元素;若是此 set 爲空,則返回 null 。 |
TreeMap
K |
ceilingKey(K key) 返回大於等於給定鍵的最小鍵;若是不存在這樣的鍵,則返回 null 。 |
K |
higherKey(K key) 返回嚴格大於給定鍵的最小鍵;若是不存在這樣的鍵,則返回 null 。 |
K |
floorKey(K key) 返回小於等於給定鍵的最大鍵;若是不存在這樣的鍵,則返回 null 。 |
K |
lastKey() 返回映射中當前最後一個(最高)鍵。 |
TreeMap還能夠返回鍵值對:
TreeMap的firstEntry()方法返回一個與此映射中的最小鍵關聯的鍵-值映射關係;若是映射爲空,則返回 null
。
TreeMap的lastEntry()方法返回與此映射中的最大鍵關聯的鍵-值映射關係;若是映射爲空,則返回 null
。
降序
TreeSet
NavigableSet<E> |
descendingSet() 返回此 set 中所包含元素的逆序視圖。 |
TreeMap
NavigableMap<K,V> |
descendingMap() 返回此映射中所包含映射關係的逆序視圖。 |
7.3.6 後備集合
下面以Set爲例,好比headSet(),在Map中有相應的方法headMap()
NavigableSet<E> |
headSet(E toElement, boolean inclusive) 返回此 set 的部分視圖,其元素小於(或等於,若是 inclusive 爲 true)toElement 。 |
NavigableSet<E> |
tailSet(E fromElement, boolean inclusive) 返回此 set 的部分視圖,其元素大於(或等於,若是 inclusive 爲 true)fromElement 。 |
NavigableSet<E> |
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 返回此 set 的部分視圖,其元素範圍從 fromElement 到 toElement 。 |
使用PriortyQueue類
E |
poll() 獲取並移除此隊列的頭,若是此隊列爲空,則返回 null。 |
E |
peek() 獲取但不移除此隊列的頭;若是此隊列爲空,則返回 null。 |
boolean |
offer(E e) 將指定的元素插入此優先級隊列。 |
Arrays和Collections中的方法概述
java.util.Arrays中的主要方法:
static
|
asList(T... a) 返回一個受指定數組支持的固定大小的列表。 |
static int |
binarySearch(Object[] a, Object key) 使用二分搜索法來搜索指定數組,以得到指定對象。 |
static
|
binarySearch(T[] a, T key, Comparator<? super T> c) 使用二分搜索法來搜索指定數組,以得到指定對象。 |
static boolean |
equals(Object[] a, Object[] a2) 若是兩個指定的 Objects 數組彼此相等,則返回 true。 |
static void |
sort(Object[] a) 根據元素的天然順序對指定對象數組按升序進行排序。 |
static
|
sort(T[] a, Comparator<? super T> c) 根據指定比較器產生的順序對指定對象數組進行排序。 |
static String |
toString(Object[] a) 返回指定數組內容的字符串表示形式。 |
java.util.Collections中的主要方法:
static
|
binarySearch(List<? extends Comparable<? super T>> list, T key) 使用二分搜索法搜索指定列表,以得到指定對象。 |
|
static
|
binarySearch(List<? extends T> list, T key, Comparator<? super T> c) 使用二分搜索法搜索指定列表,以得到指定對象。 |
static void |
reverse(List<?> list) 反轉指定列表中元素的順序。 |
|
static
|
reverseOrder() 返回一個比較器,它強行逆轉實現了 Comparable 接口的對象 collection 的天然順序。 |
|
static
|
reverseOrder(Comparator<T> cmp) 返回一個比較器,它強行逆轉指定比較器的順序。 |
static
|
sort(List<T> list) 根據元素的天然順序 對指定列表按升序進行排序。 |
|
static
|
sort(List<T> list, Comparator<? super T> c) 根據指定比較器產生的順序對指定列表進行排序。 |
List、Set、Map和Queue的方法概述
主要接口方法 | List | Set | Map | 描述 |
boolean add(element) boolean add(index,element) |
X X |
X
|
添加一個元素。對於List,能夠在 索引點有選擇地添加元素 |
|
boolean contains(object) boolean containsKey(object key) boolean containsValue(object value) |
X
|
X
|
X X |
在集合中查找一個對象(或者有選 擇地在Map中查找一個鍵),將結 果做爲boolean返回 |
object get(index) object get(key) |
X
|
X |
經過索引或鍵從集合得到一個對象
|
|
int indexOf(object) | X | 得到對象在List中的位置 | ||
Iterator iterator() | X | X | 得到List或Set的迭代器 | |
Set keySet() | X | 返回包含Map的鍵的Set | ||
put(key,value) | X | 添加一個鍵/值對到Map | ||
remove(index) remove(object) remove(key) |
X X
|
X
|
X |
經過索引、元素的值或鍵刪除元素
|
int size() | X | X | X | 返回集合中元素的數量 |
Object[] toArray() T[] toArray(T[]) |
X
|
X
|
返回包含集合元素的數組
|
7.4 泛型類型
考試目標6.3 編寫代碼,使用Collections API的泛型版本,特別是Set、List和Map接口以及實現類的泛型版本。瞭解非泛型的Collection API的限制以及如何重構代碼來使用泛型版本。
考試目標6.4 編寫代碼,正確使用類/接口聲明、實例變量、方法變元以及返回類型中的類型參數。編寫泛型方法或使用通配符類型的方法,並理解這兩種方法間的類似及不一樣之處。編寫使用NavigableSet和NavigableMap接口的代碼。
處理集合的遺留方式
使用泛型以下聲明一個List:
List<String> myList = new ArrayList<String>();
使用了泛型後,myList裏的全部元素都必須是String類型的;若是咱們不使用泛型,雖然你能夠往這個myList裏插入各類類型的元素,可是你要本身保證代碼的安全性。讀出的時候要強制轉型,而且要保證確實可以轉型。
7.4.1 泛型與遺留代碼
把非泛型代碼升級成泛型代碼:
在集合類的關鍵字後面加<類型>。
7.4.2 混合泛型和非泛型集合
能夠將泛型集合傳遞到帶有非泛型集合的方法,但結果可能很是糟糕。編譯器不能阻止方法將錯誤的類型插入到之前是類型安全的集合。
若是編譯器可以認識到,非類型安全的代碼可能會危害原來聲明爲類型安全的東西,就會給出一個編譯器警告。例如,若是將一個List<String>傳遞到聲明爲
void foo(List aList){aList.add(anInteger);} 的方法,則會獲得一個警告,由於add()有多是「不安全的」。
「編譯不帶錯誤」與「編譯不帶警告」是不一樣的。編譯時的警告不被認爲是一個編譯錯誤或失敗。
泛型類型信息在運行時不存在——它只用於編譯時安全。混合泛型與遺留代碼所獲得的編譯後代碼,在運行時可能拋出異常。
7.4.3 多態與泛型
規則:變量聲明的類型必須匹配傳遞給實際對象的類型。若是聲明瞭List<Foo> foo,那麼賦予foo引用的必須是泛型類型<Foo>,而不是<Foo>的一個子類型,也不是它的一個超類型。
7.4.4 泛型方法
前面介紹了一個規則,但若是我如今須要聲明泛型爲XX的子類型時,該怎麼聲明才能讓全部子類型泛型都使用父類型泛型呢?
通配符語法容許泛型方法,接受方法變元所聲明的類型的子類型(或超類型):
void addD(List<? extends Dog> list){}
通配符關鍵字extends用於表示「擴展」或「實現」。所以,在<? extends Dog>中,Dog能夠是一個類,也能夠是一個接口。
當使用通配符時,List<? extends Dog>表示能夠訪問集合但不能修改它。
當使用通配符時,List<?>表示任何泛型類型均可以賦給引用,但只能訪問,不能修改。
7.4.5 泛型聲明
泛型的聲明約定用T表明類型,E表明元素:具體使用的時候只要記住集合用E,非集合用T就行了。
建立本身的泛型類
建立泛型方法