Thinking in java 第11章 持有對象
11.1 泛型和類型安全的容器
1. 當你制定了某個類型作爲泛型參數時,你並不僅限於只能將該確切類型的對象放置到容器中。向上轉型也可一樣作用於泛型。
11.2 基本概念
1. Java容器被劃分爲兩個概念:
- Collection:一個獨立元素的序列,這些元素都服從一條或多條規則。
- Map:一組成對的“鍵值對”對象,允許你使用鍵來查找值。
11.3 添加一元素
1. Arrays.asList() 方法接收一個數組或是一個用逗號分隔的元素列表,並將其轉換爲一個List對象。
2. Collections.addAll() 方法接收一個Collection對象,以及一個數組或是一個用逗號分隔的列表。
Collections<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
Integer[] moreInts = {6,7,8,9,10};
collection.addAll(Array.asList(moreInts));
Collections.addAll(collection, 11, 12, 13, 14, 15);
Collections.addAll(collection, moreInts);
List<Integer> list = Arrays.asList(16,17,18,19,20);
list.set(1,99);
//! list.add(21); underlying array cannot be resized
3. 方法2只能接受Collection,因此沒有方法1靈活;而方法1返回的底層是數組,不能改變大小(即不能add或delete)
11.4 容器的打印
1. Set類型:
- HashSet使用相當複雜的方式來存儲元素,這種技術是最快的獲取元素方式,因此順序無實際意義。
- TreeSet按照比較結果的升序保存對象。
- LinkedHashSet按照被添加的順序保存對象。
2. Map類型(Map.put(key, value); Map.get(key))
- HashMap提供了最快的查找技術,沒有按照任何明顯順序來保存。
- TreeMap按照比較結果的升序保存鍵。
- LinkedHashMap按照插入順序保存鍵,同時還留下了HashMap的查詢速度。
11.5 List
1. 分爲ArrayList和LinkedList,區別即順序表和鏈表的區別。
2. 用contains()方法判斷某個對象是否在列表中。
3. 用remove()方法移除某個對象。
4. 用indexOf()方法查詢索引編號。
5. 比較要用到equals()方法,最好都重寫。
6. 用subList()方法獲取子集。
7. containsAll()、Collections.shuffle()、Collections.sort()、retainAll() 交集、removeAll()、set()、isEmpty()、clear()、toArray()
11.6 迭代器
1. 迭代器通常被稱爲輕量級對象:創建它的代價小。因此迭代器有很多奇怪的限制,例如Java的Iterator只能單向移動,只能用來:
- 使用iterator()要求容器返回一個Iterator。Iterator將準備好返回序列的第一個元素。
- 使用next()獲得下一個元素。
- 使用hasNext()檢查是否還有元素。
- 使用remove()將迭代器將最新的元素刪除,所以必須先調用next()再用remove()。
2. 用Iterator<T> it 就能不管T的容器類型。
3. ListIterator是Iterator的子類,可以雙向移動,還可以返回指向當前元素的前一個和後一個元素的索引,且可使用set()替換訪問過的最後一個元素。 方法爲:hasNext()、hasPrevious()、next()、previous()、nextIndex()、previousIndex()、set()
11.7 LinkedList
1. LinkedList添加了可以使其用作棧、隊列或雙端隊列的方法,這些方法中有些彼此之間只是名字差異或只有輕微差異。如:
- getFirst()和element()完全一樣,返回表頭元素,若空拋出異常,而peek()方法在空時返回null;
- removeFirst()和remove()完全一樣,一處表頭,若空拋出異常,而poll()方法在空時返回null;
- addFirst();add()和addLast()相同;removeLast();
11.8 Stack
1. push(T v)、peek()、pop()、empty()、toString()
11.9 Set
1. Set具有與Collection完全一樣的接口,因此沒有任何額外功能,不像前面有兩個不同的List。
2. HashSet使用的是散列數組;TreeSet使用的是紅黑樹;LinkedHashSet也使用了散列,並用鏈表維護插入順序。
3. 在TreeSet中如果想不分大小寫進行從小到大排序,則用 Set<String> words = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
4. 方法爲 add() 、 remove() 等。
11.10 Map
1. 添加時如下:
Integer freq = m.get(r);
m.put(r, freq == null? 1 : freq+1);
2. 可擴展到多維,即value可以使其他容器。
3. 遍歷時如下:
for(T t : map.keySet())
print(t + ": " + map.get(t));
11.11 Queue
1. 可以將LinkedList向上轉型爲Queue,因爲其實現了Queue接口。故可寫成 Queue<Integer> q = new LinkedList<Integer>();
2. 方法:offer() 插入隊尾、peek()/element()、poll()/remove() 等。
3. PriorityQueue<T>,Collections.reverseOrder()產生反序的Comparator。
4. 如果你想在優先隊列中使用自己的類,就必須包括額外的功能以產生自然排序,或者必須提供自己的Comparator。
11.12 Collection和Iterator
1. 在基於兩個接口都能實現時(例如遍歷輸出),用Collection方便一些,因爲它是Iterable的,用Foreach語句更加清晰。
2. 當要實現一個不是Colletion的外部類時,用Iterator更好。因爲實現Collection必須要實現iterator(),而且還必須要實現其他一些方法。
11.13 Foreach迭代器
1. 如果你創建了任何實現Iterable的類,都可以將它用於Foreach語句中。
class A implements Iterable<T> {
protected T[] words = ...;
public Iterable<T> iterator() {
return new Iterable<T>() {
private int index = 0;
public boolean hasNext() { return index < words.length; }
public T next() { return words[index++]; }
public void remove() {...}
}
}
}
2. Map的Foreach遍歷如下:
for(Map.Entry entry : m.entrySet()) {
print(entry.getKey(), entry.getValue());
}
3. 可以通過適配器方法,在類中添加用於不同作用的迭代方法。
習題
練習1:創建一個新類Gerbil(沙鼠),包含int gerbilNumber,在構造器中初始化它.添加一個方法hop(),用以打印沙鼠的號碼以及它正在跳躍的信息。創建一個ArrayList,並向其中添加一串Gerbil對象。使用get()遍歷List,並且對每個Gerbil調用hop()。
package Chapter11;
import java.util.ArrayList;
import java.util.Arrays;
public class E1 {
public static void main(String[] args) {
ArrayList<E1Gerbil> list = new ArrayList<E1Gerbil>(Arrays.asList(new E1Gerbil(1), new E1Gerbil(2),
new E1Gerbil(3), new E1Gerbil(4)));
for(E1Gerbil e : list) {
e.hop();
}
}
}
class E1Gerbil {
private int gerbilNumber;
public E1Gerbil(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public int getGerbilNumber() {
return gerbilNumber;
}
public void setGerbilNumber(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public void hop() {
System.out.println("" + gerbilNumber + " is jumping");
}
}
/*
1 is jumping
2 is jumping
3 is jumping
4 is jumping
*/
練習2:修改SimpleCollection.java,使用Set來表示c。
略。
練習3:修改innerclasses/Sequence.java,使你可以向其中添加任意數量的元素。
略。把Object[] 改爲ArrayList<Object> ,並用相應的方法替換即可。(P192)
練習4:創建一個生成器類,它可以在每次調用其next()方法時,產生你最喜歡的電影的名字。在電影名列表的名字用完之後,循環到該列表的開始處。使用這個生成器來填充數組、ArrayList、LinkedList、HashSet、LinkedHashSet和TreeSet,然後打印每個容器。
package Chapter11;
import java.util.*;
public class E4 {
public static void main(String[] args) {
System.out.println("ArrayList " + new E4Generator().fill(new ArrayList<String>()));
System.out.println("LinkedList " + new E4Generator().fill(new LinkedList<String>()));
System.out.println("HashSet " + new E4Generator().fill(new HashSet<String>()));
System.out.println("LinkedHashSet " + new E4Generator().fill(new LinkedHashSet<String>()));
System.out.println("TreeSet " + new E4Generator().fill(new TreeSet<String>()));
}
}
class E4Generator {
private String[] movies = {"ZZ", "BB", "CC", "AA", "FF", "EE"};
private int maxLength = 6;
private int i = 0;
public String next() { return movies[(i++)%maxLength]; }
public Collection<String> fill(Collection<String> c) {
for(int j = 0; j < 8; j++) {
c.add(next());
}
return c;
}
}
/*
ArrayList [ZZ, BB, CC, AA, FF, EE, ZZ, BB]
LinkedList [ZZ, BB, CC, AA, FF, EE, ZZ, BB]
HashSet [ZZ, BB, CC, AA, FF, EE]
LinkedHashSet [ZZ, BB, CC, AA, FF, EE]
TreeSet [AA, BB, CC, EE, FF, ZZ]
*/
練習5-練習6:修改LIstFeatures.java,讓它用Integer/String而不是Pet,並解釋在結果上有何不同。
略。
練習7:創建一個類,然後創建一個用你的類的對象進行過初始化的數組。通過使用subList()方法,創建你的List子集,然後在你的List中移除這個子集。
package Chapter11;
import java.util.*;
public class E7 {
public static void main(String[] args) {
E7A[] es = new E7A[] {new E7A(1), new E7A(2), new E7A(3), new E7A(4)};
List<E7A> list = new ArrayList<E7A>(Arrays.asList(es));
List<E7A> list2 = list.subList(1, 2);
System.out.println(list2);
list.removeAll(list2);
System.out.println(list);
}
}
class E7A {
int a;
public E7A(int a) {
this.a = a;
}
@Override
public String toString() {
return Integer.toString(a);
}
}
/*
[2]
[1, 3, 4]
*/
練習8:修改練習題1,以便調用hop()時使用Iterator遍歷List。
package Chapter11;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
public class E1 {
public static void main(String[] args) {
ArrayList<E1Gerbil> list = new ArrayList<E1Gerbil>(Arrays.asList(new E1Gerbil(1), new E1Gerbil(2),
new E1Gerbil(3), new E1Gerbil(4)));
Iterator<E1Gerbil> iter = list.iterator();
while(iter.hasNext()) {
iter.next().hop();
}
}
}
class E1Gerbil {
private int gerbilNumber;
public E1Gerbil(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public int getGerbilNumber() {
return gerbilNumber;
}
public void setGerbilNumber(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public void hop() {
System.out.println("" + gerbilNumber + " is jumping");
}
}
/*
1 is jumping
2 is jumping
3 is jumping
4 is jumping
*/
練習9:修改innerclasses/Sequence.java,使得在Sequence中,用Iterator取代Selector。
略。用個方法return collection.iterator()即可。
練習10:修改第8章中的練習9,使其使用一個ArrayList來存放Rodents,並使用一個Iterator來訪問Rodent序列。
略。
練習11:寫一個方法,使用Iterator遍歷Collection,並打印容器中每個對象的toString()。填充各種類型的Collection,然後對其使用此方法。
同P227代碼。略。
練習12:創建並組裝一個List<Integer>,然後創建第二個具有相同尺寸的List<Integer>,並使用ListIterator讀取第一個List中的元素,然後再將它們以反序插入到第二個列表中。
package Chapter11;
import java.util.*;
public class E12 {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
List<Integer> list2 = new ArrayList<>();
ListIterator<Integer> iter = list1.listIterator();
while(iter.hasNext()) iter.next();
while(iter.hasPrevious()) list2.add(iter.previous());
System.out.println(list2);
}
}
/*
[6, 5, 4, 3, 2, 1]
*/
直接寫 ListIterator<Integer> iter = list1.listIterator(list1.size()); 更好。
練習13:在innerclasses/GreenhouseController.java示例中,Controller類使用的是ArrayList,修改代碼,用LinkedList替換之,並使用Iterator來循環遍歷事件集。
略。簡單應用。
練習14:創建一個空的LinkedList<Integer>,通過使用ListIterator,將若干個Integer插入這個List中,插入時,總是將它們插入到List的中間。
package Chapter11;
import java.util.*;
public class E14 {
public static void main(String[] args) {
List<Integer> list = new LinkedList<Integer>();
for(int i = 0; i < 10; i++) {
ListIterator iter = list.listIterator(list.size()/2);
iter.add(i);
}
System.out.println(list);
}
}
/*
[1, 3, 5, 7, 9, 8, 6, 4, 2, 0]
*/
練習15:棧在編程語言中經常用來對錶達式求值。請使用net.mindview.util.Stack對下面表達式求值,其中“+”表示“將後面的字母壓進棧”,而“-”表示“彈出棧頂字母並打印它”:“+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+l+e+s---”。
package Chapter11;
import java.util.Stack;
public class E15 {
public static void main(String[] args) {
String s = "+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+l+e+s---";
Stack<Character> stack = new Stack<>();
for(int i = 0; i < s.length(); i++) {
if(s.charAt(i) == '+') stack.push(s.charAt(++i));
else System.out.print(stack.pop());
}
System.out.println();
}
}
/*
cnUtreaiytn ursel
*/
練習16:創建一個元音字母Set。對UniqueWords.java操作,計數並顯示在每一個輸入單詞中的元音字母數量,並顯示輸入文件中的所有元音字母的數量總和。
沒有文件,簡單應用。略。
練習17:使用練習1中的Gerbil類,將其放入Map中,將每個Gerbil的名字String(鍵)與每個Gerbil(值)關聯起來。爲keySet()獲取Iteratror,使用它遍歷Map,針對每個“鍵”查詢Gerbil,然後打印出“鍵”,並讓gerbil執行hop()。
package Chapter11;
import java.util.*;
import Chapter11.E1Gerbil;
public class E17 {
public static void main(String[] args) {
ArrayList<E1Gerbil> list = new ArrayList<E1Gerbil>(Arrays.asList(new E1Gerbil(1), new E1Gerbil(2),
new E1Gerbil(3), new E1Gerbil(4)));
String name = "GG";
Map<String, E1Gerbil> m = new HashMap<>();
for(int i = 1; i < 5; i++) m.put(name+i, list.get(i-1));
for(String s : m.keySet()) m.get(s).hop();
}
}
/*
1 is jumping
3 is jumping
2 is jumping
4 is jumping
*/
練習18:用鍵值對填充一個HashMap。打印結果,通過散列碼來展示其排序。抽取這些鍵值對,按照鍵進行排序,並將結果置於一個LinkedHashMap中。展示其所維護的插入排序。
package Chapter11;
import java.util.*;
public class E18 {
public static void main(String[] args) {
Map<String, Integer> m = new HashMap<>();
m.put("asd",1);
m.put("zsdf",2);
m.put("bgfd",3);
System.out.println(m);
List<String> strings = new LinkedList<>(m.keySet());
Collections.sort(strings);
Map<String, Integer> m2 = new LinkedHashMap<>();
for(String s : strings) {
m2.put(s, m.get(s));
}
System.out.println(m2);
}
}
/*
{zsdf=2, asd=1, bgfd=3}
{asd=1, bgfd=3, zsdf=2}
*/
練習19:使用HashSet和LinkedHashSet重複前一個練習。
這也能重複?略。
練習20:修改練習16,使得你可以跟蹤每一個元音字母出現的次數。
略。
練習21:通過使用Map<String,Integer>,遵循UniqueWords.java的形式來創建一個程序,它可以對一個文件中出現的單詞計數。使用帶有第二個參數**String.CASE_INSENSITIVE_OREDER的Collection.sort()方法對結果進行排序(將產生字母序),然後顯示結果。
package Chapter11;
import java.util.*;
public class E21 {
public static void main(String[] args) {
String[] s = "Can you can can a can?".split(" ");
Map<String, Integer> map = new HashMap<>();
for(String i : s) {
Integer temp = map.get(i);
map.put(i, (temp==null?1:temp+1));
}
List<String> list = new ArrayList<>(map.keySet());
Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String i : list) {
System.out.println(i + " : " + map.get(i));
}
}
}
/*
a : 1
Can : 1
can : 2
can? : 1
you : 1
*/
練習22:修改前一個練習,使其用一個包含有一個String域和一個計數域的類來存儲每一個不同的單詞,並使用一個由這些對象構成的Set來維護單詞列表。
package Chapter11;
import java.util.Objects;
import java.util.*;
public class E22 {
public static void main(String[] args) {
String[] s = "Can you can can a can?".split(" ");
Set<E22A> set = new HashSet<>();
for(String i : s) {
E22A e = new E22A(i);
if(set.contains(e)) {
Iterator<E22A> iter = set.iterator();
while(iter.hasNext()) {
E22A tempe = iter.next();
if(tempe.equals(e)) tempe.add();
}
}
else {
e.setA(1);
set.add(e);
}
}
for(E22A e : set) {
System.out.println(e.getS() + " : " + e.getA());
}
}
}
class E22A {
private String s;
private int a;
public void add() { a++; }
public E22A(String s) {
this.s = s;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
E22A e22A = (E22A) o;
return Objects.equals(s, e22A.s);
}
@Override
public int hashCode() {
return Objects.hash(s);
}
}
/*
a : 1
can? : 1
Can : 1
can : 2
you : 1
*/
要重寫equals再調用contains()。
練習23:從Statistics.java開始,寫一個程序,讓它重複做測試,觀察是否某個數字比別的數字出現的次數多。
package Chapter11;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class E23 {
public static void main(String[] args) {
Random r = new Random();
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
int temp = r.nextInt();
Integer in = map.get(temp);
map.put(temp, (in == null ? 1 : in+1));
}
for(Integer i : map.keySet()) System.out.println(""+i+" : "+map.get(i));
}
}
....
都只出現一次的樣子。
練習24:使用String“鍵”和你選擇的對象填充LinkedHashMap。然後從中提取鍵值對,以鍵排序,然後重新插入此Map。
略。和前面差不多。
練習25:創建一個Map<String,ArrayList<Integer>>,使用net.mindview.TextFile來打開一個文本文件,並一次讀入一個單詞。在讀入單詞時對它們進行計數,並且對於文件中的每一個單詞,都在ArrayList<Integer>中記錄下與這個詞相關聯的單詞計數。實際上,它記錄的是該單詞在文件中被發現的位置。
略。計數就是ArraList.size()。
練習26:拿到前一個練習中所產生的Map,並按照它們在最初的文件中出現的順序重新創建單詞順序。
略。最後再遍歷一遍用LinkedHashSet存儲。
練習27:寫一個稱爲Command的類,它包含一個String域和一個顯示該String的operation()方法。寫第二類,它具有一個使用Command對象來填充一個Queue並返回這個對象的方法。將填充後的Queue傳遞給第三個類的一個方法,該方法消耗掉Queue中的對象,並調用它們的operation()方法。
package Chapter11;
import java.util.*;
public class E27 {
public static void main(String[] args) {
E27C.func2();
}
}
class E27A {
private String s;
public void operation() {
System.out.println("this is " + s);
}
public E27A(String s) {
this.s = s;
}
}
class E27B {
public static Queue<E27A> func1() {
return new LinkedList<>(Arrays.asList(new E27A("aaa"), new E27A("bbb"), new E27A("ccc")));
}
}
class E27C {
public static void func2() {
Queue<E27A> q = E27B.func1();
while(!q.isEmpty()) {
q.poll().operation();
}
}
}
/*
this is aaa
this is bbb
this is ccc
*/
練習28:用由java.util.Random創建的Double值填充一個PriorityQueue(用offer())方法,然後使用poll()移除並顯示它們。
略。同上。
練習29:創建一個繼承自Object的簡單類,它不包含任何成員,展示你不能將這個類的多個示例成功地添加到一個PriorityQueue中。這個問題將在第17章中詳細解釋。
問號。看完17章再來看看能不能答。
練習30:修改CollectionSequeuece.java,使其不要繼承AbstractCollection,而是實現Collection。
略。直接實現Collection<Pet>接口要同時實現裏面的多餘的方法。
練習31:修改polymorphism/shape/RandomShapeGenerator.java,使其成爲一個Iterable。你需要添加一個接受元素數量爲參數的構造器,這個數量是指在停止之前,你想用迭代器生成的元素的數量。驗證這個程序可以工作。
略。
練習32:按照MultiIterableClass示例,在NonCollectionSequence.java中添加reversed()和randomized()方法,並讓NonCollectionSequence實現Iterable。然後在foreach語句中展示所有的使用方式。
略。同P244。