天天看點

Java程式設計思想讀書筆記——第十一章:持有對象

第十一章 持有對象

Java類庫提供了一套相當完整的容器來解決這個問題,基本類型是List、Set、Queue、Map

11.1 泛型和類型安全的容器

我們常用的ArrayList,一般使用泛型指定類型,如果不指定,那麼就都是Object類,直接上練習

練習1:建立一個新類Gerbil(沙鼠),包含int gerbilNumber,在構造器中初始化它。添加一個方法hop(),用以列印沙鼠的号碼以及它正在跳躍的資訊。建立一個ArrayList,并向其中添加一串Gerbil對象。使用get()周遊List,并且對每個Gerbil調用hop()。這個練習太簡單了,都不想貼上來了。

public class Gerbil {
	
	private int gerbilNumber;
	
	public Gerbil(int number) {
		gerbilNumber = number;
	}
	
	public void hop() {
		System.out.println(gerbilNumber + "跳起來啦");
	}
	
	public static class Inner {
		public static void main(String[] args) {
			ArrayList<Gerbil> list = new ArrayList<Gerbil>();
			for (int i = 0; i < 10; i++) {
				list.add(new Gerbil(i));
			}
			for (int i = 0; i < list.size(); i++) {
				list.get(i).hop();
			}
		}
	}
	
}

輸出
0跳起來啦
1跳起來啦
2跳起來啦
3跳起來啦
4跳起來啦
5跳起來啦
6跳起來啦
7跳起來啦
8跳起來啦
9跳起來啦
           

11.2 基本概念

1、Collection。一個獨立的元素序列,必須按照插入的順序儲存元素,Set不能有重複元素,Queue按照排隊規則來确定順序

2、Map。一組成對的"鍵值對"對象,ArrayList允許通過數字查值,某種意義上來說,它也将數字和值關聯在了一起。通過一個對象去查找另一個對象,我們通俗的可以稱"關聯數組",或者"字典",Map是很強大的程式設計工具。

練習2:略

練習3:略

11.3 添加一組元素

java.util包下Arrays和Collections類中有很多實用方法,比如Arrays.asList(),Collection.addAll(),它們都接受一個數組或者用逗号分割的清單。

Collection.addAll()運作起來要快的多,首選這種方式

Arrays.asList()的要點:

  1. 底層是數組,長度不可變
  2. 傳回的ArrayList不是真的java.util包下的ArrayList,而是Arrays的内部類,不支援添加和删除元素
  3. 這個方法将數組和清單連接配接了起來,同生共死的關系
  4. 對基本類型的數組支援不友好,有時候識别不出來基本類型,要想用,還必須使用封裝類,比如int型的得用Integer
其實想要避免這些問題可以考慮這種騷操作:
// ArrayList的構造方法并不高效
List<String> list = new ArrayList<String>(Arrays.asList(arr));
// 還是像上面說的,首先建議使用Collection.addAll()這種方法更高效
List<String> list = new ArrayList<String>();
Collections.addAll(list, arr);
           

11.4 容器的列印

在每個槽中隻保留一個元素,此類容器包括:

  1. List,它以特定的順序儲存一組元素(ArrayList,LinkedList)
  2. Set,元素不能重複(HashSet,TreeSet,LinkedHashSet)
  3. Queue,隻允許在容易的一端插入對象,并在另外一端移除對象
在每個槽中保留了兩個對象
  1. Map,鍵和與之相關聯的值(HashMap,TreeMap,LinkedHashMap)
練習4:建立一個生成器類,它可以在每次調用其next方法時,産生你由你最喜歡的電影的字元構成的名字(作為String對象)。在字元清單中的電影名用完之後,循環到這個字元清單的開始處。使用這個生成器來填充數組、ArrayList、LinkedList、HashSet、LinkedHashSet和TreeSet、然後列印每一個容器。
public class Player {
	
	String[] player = {"哈登", "庫裡", "保羅", "科比", "艾弗森", "麥迪"};
	
	int next;
	
	public String next() {
		String s = player[next];
		next = (next + 1) % player.length;
		return s;
	}
	
	
	public static class Main {
		
		private static final Player PLAYERS = new Player();
		
		static String[] fill(String[] array) {
			for (int i = 0; i < array.length; i++) {
				array[i] = PLAYERS.next();
			}
			return array;
		}
		
		static Collection<String> fill(Collection<String> collection) {
			for (int i = 0; i < 5; i++) {
				collection.add(PLAYERS.next());
			}
			return collection;
		}
		
		public static void main(String[] args) {
			System.out.println(Arrays.toString(fill(new String[5])));
			System.out.println(fill(new ArrayList<String>()));
			System.out.println(fill(new LinkedList<String>()));
			System.out.println(fill(new HashSet<String>()));
			System.out.println(fill(new LinkedHashSet<String>()));
			System.out.println(fill(new TreeSet<String>()));
			
		}
	}
}

輸出:
[哈登, 庫裡, 保羅, 科比, 艾弗森]
[麥迪, 哈登, 庫裡, 保羅, 科比]
[艾弗森, 麥迪, 哈登, 庫裡, 保羅]
[艾弗森, 庫裡, 麥迪, 哈登, 科比]
[保羅, 科比, 艾弗森, 麥迪, 哈登]
[保羅, 庫裡, 科比, 艾弗森, 麥迪]
           

11.5 List

List有兩種:

  • ArrayList,我們常用的用于通路随機元素,在插入和移除的時候較慢
  • LinkedList,插入和移除操作較快,随機通路較慢

List允許建立後添加移除,并且調整自身的尺寸,一種可修改的序列。這些是它的重要價值所在。

常用方法:

contains():确定某個對象是否在清單中

remove():将對象的引用傳遞給此方法可以移除對象

indexOf():發現某一個對象所在的索引

subList():從一個list中截取一個片段,此片段就是基于截取的那個list,是以此片段的變化會通知到初始清單

retainAll():取交集的方法,也同樣依賴于equals()方法,這個不常用

removeAll():移除所有元素

set():作者也說了這個方法應該命名為replace更合适些,哈哈,取代的意思

addAll():添加一個list,這個方法還是很常用的

isEmpty()和clear():這倆方法就不用說了吧

toArray():轉換為一個數組

練習5:略

練習6:略

練習7:建立一個類,然後建立一個用你的類的對象進行初始化過的數組。通過subList()方法,建立你的List的子集,然後在你的List中移除這個子集

// 這個例子很簡單,但是非要寫一下,subList得到的這個list,進行何種操作都會影響到初始清單
// 想要正常使用Arrays.asList()和subList()這兩個方法傳回的list,我們可以建立它們的副本
public class Apple {
	
	int id;
	
	public Apple(int i) {
		id = i;
	}
	
	public static class Main{
	
		static List<Apple> list = new ArrayList<>();
		
		public static void main(String[] args) {
			for (int i = 0; i < 10; i++) {
				list.add(new Apple(i));
			}
			
			List<Apple> list2 = list.subList(0, 3);
			// 移除list2中的第二項
			list2.remove(1);
			for (int i = 0; i < list.size(); i++) {
				System.out.println(list.get(i).id);
			}
		}
	}
	
}

輸出:
0
2
3
4
5
6
7
8
9
           

11.6 疊代器

任何容器都可以插入元素并取出元素,比如list的add()和get()方法

對于list我們肯定會涉及到周遊操作

// 一個list的三種周遊方式
List<Apple> list = new ArrayList<>();
// 1、普通方式
for (int i = 0; i < list.size(); i++) {
	System.out.print(list.get(i).id);
				
}
// 2、foreach, 寫起來挺簡潔,但是無法獲得下标
for(Apple apple : list) {
	System.out.print(apple.id);
}
// 3、疊代器方式
Iterator<Apple> iterator = list.iterator();
while(iterator.hasNext()) {
	System.out.print(iterator.next().id);
}
           
通過list.iterator()方法傳回疊代器,注意Iterator隻能向一個方向周遊疊代器有三個方法:
  • next()
  • hasNext()
  • remove()

挺簡單的,這幾個練習不寫了,略了

練習8:略

練習9:略

練習10:略

練習11:略

11.6.1 ListIterator

ListIterator是一種更強大的子類型,它的優點有:

  • 可以雙向移動
  • 可以擷取目前位置的前一個和後一個的元素的索引
  • set()方法替換它通路過的最後一個元素
  • 可以建立一個一開始就指向清單索引為n的元素處的ListIterator

練習12:略

11.7 LinkedList

LinkedList實作了List的接口,同樣也實作了使其作用于棧和隊列的一些方法,這些方法隻存在些許差異:

getFirst()和element()都是傳回了清單的頭(第一個元素),并且不移除它,List為空,抛出異常,而peek()方法list為空傳回null

removeFirst()和remove()也是一樣的,移除并傳回清單的頭,List為空,抛出異常,而poll()方法在清單為空時傳回null

addFirst()與add()和addFirst()相同,都是将某個元素插入到清單的尾部

removeLast()移除并傳回清單的最後一個元素

練習13:略

練習14:略

11.8 Stack

LinkedList擁有能夠直接實作棧的所有的方法,因為可以直接将LinkedList作為棧使用,不過一個真正的棧更能把事情講清楚
public class Stack<T> {
	
	private LinkedList<T> storage = new LinkedList<>();
	
	public void push(T v) {
		storage.addFirst(v);
	}
	
	public T peek() {
		return storage.getFirst();
	}
	
	public T pop() {
		return storage.removeFirst();
	}
	
	public boolean empty() {
		return storage.isEmpty();
	}
	
}
           
練習15:對表達式求值,“+”表示将後面的字母壓進棧,而“-”表示彈出棧頂字母并列印它
// 你還别說,通過這個例子
// for循環不自增的用法确實挺騷
// 再一個就是思維的問題,如果按正常考慮肯定要一位一位的判斷是+還是-,
// 然後用一個變量來記錄上一位是個符号還是字母,
// 因為存在符号也算字元的問題,甚至得記錄到上一位的上一位,那這樣解決這個問題就太複雜了
// 通過自增來解決這個問題,那麼就容易多了
public class Pratice15 {
	
	private static Stack<Character> stack = new Stack<>();

	public static void main(String[] args) {
		String string = "+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+l+e+s---";
		char[] array = string.toCharArray();
		for (int i = 0; i < array.length;) {
			switch (array[i++]) {
			case '+':
				stack.push(array[i++]);
				break;
			case '-':
				System.out.print(stack.pop());
			default:
				break;
			}
		}
		
	}
}

輸出:
cnUtreaiytn ursel
           

11.9 Set

Set不儲存重複元素,比如add相同元素,是不會儲存的。

HashSet輸出的時候是散列的

TreeSet輸出的時候是有序的

使用contain()方法來确定Set中有沒有此元素

練習16:略

11.10 Map

map是很牛逼的,解決程式設計問題的殺手锏,哈哈哈

比如此時Random類的随機性,随機生成的數出現幾次的問題。那麼使用Map簡直不要太爽,鍵(key)是生成的數字,值(value)是出現的次數

鍵不在容器中,get()方法傳回null,否則将産生對應的關聯值,containsKey()和containsValue()用來檢視map中是否包含某個值

  • Map可以傳回它鍵的Set,keySet()方法
  • 值的Collection,values()方法
  • 或者鍵值對的Set,entrySet()方法

以後寫練習不寫題目了,代碼也寫主要代碼了。這樣能節省好多時間

練習17:

// 主要考察:
// Map的entrySet()方法傳回鍵值對的集合
// 容器使用Iterator來周遊
Map<String, Gerbil> map = new HashMap<>();
Iterator<Entry<String, Gerbil>> iterator = map.entrySet().iterator(); 
while(iterator.hasNext()) {
    Entry<String, Gerbil> entry = iterator.next();
	System.out.println(entry.getKey() + ":");
	entry.getValue().hop();
}
           
練習18:
// 主要考察:
// map.ketSet()方法
// 排序
// foreach循環
String[] keys = map.keySet().toArray(new String[0]);
Arrays.sort(keys);
Map<String, String> map2 = new LinkedHashMap<String, String>();
for (String key : keys) {
	map2.put(key, map.get(key));
}
           

練習19:略

練習20:

// 考察:
// 使用map來解決記錄次數的問題
void updateStatus(Map<Character, Integer> map, char letter) {
	Integer integer = map.get(letter);
	map.put(letter, integer == null ? 1 : integer + 1);
}
           

練習21:略

練習22:略

練習23:略

練習24:略

練習25:略

練習26:略

11.11 Queue

隊列是先進先出的容器,常被當做是一種可靠的将對象從程式的某個區域傳輸到另一個區域的途徑。隊列在并發程式設計中非常重要

LinkedList實作了Queue的接口,可以用作queue的一種實作

插入隊列,offer方法():将一個元素插入隊尾或者傳回false

傳回隊頭,peek()在隊列為空傳回null,element()在隊列為空抛出異常

移除并傳回隊頭poll()在隊列為空傳回null,remove()抛出異常

練習27:

// 主要考察:
// 隊列的這三個方法
// offer()入隊
// peek()擷取隊首元素
// remove()擷取并移除隊首元素,如果隊列為空,抛出異常
Queue<String> queue = new LinkedList<>();
queue.offer("1");
if (queue.peek() != null) {
	queue.remove();
}
           

11.11.1 PriorityQueue

一種優先級隊列,不一定是先進先出,可以通過Comparator來修改這個順序,有它一定的優勢

練習28:略

練習29:略

11.12 Collection和Iterator

使用接口描述的理由是:它可以使我們能夠建立更通用的代碼

Collection是所有序列容器的共性接口

繼承Collection必須首先實作很多的方法,如果繼承AbstractCollection隻需實作iterator()方法

11.13 foreach與疊代器

foreach适用于數組和所有Collection對象

是因為在Java SE5引入了新的被稱為Iterable的接口,包含一個iterator()的方法用來在序列中移動,如果我們建立了任何實作Iterable的類,都可以使用foreach語句。

foreach語句可以用于數組,但是并不意味着數組也是一個Iterable

練習31:略

11.13.1 擴充卡方法慣用法

挺重要的一點:

Arrays.asList()傳回的List并不是ArrayList,隻能通路不能修改,是以正确方法是在ArrayList的構造方法中傳入Arrays.asList傳回的list

練習32:略

11.14 總結

  • 數組儲存類型明确的對象,數組一旦生成,其容量無法改變
  • Collection儲存單一的對象,Map儲存相關聯的鍵值對,泛型可以指定存放的類型,Collection和Map都可以在添加元素時自動調整尺寸。不能持有基本類型,但是有自動包裝機制可以雙向轉換。
  • 要進行大量的随機通路,就使用ArrayList,如果經常要從中插入或删除元素,就使用LinkedList
  • 各種Queue和棧行為,有LinkedList支援
  • HashMap用來快速通路,TreeMap保持鍵始終處于排序狀态,LinkedHashMap保持元素插入順序
  • Set不接受重複的元素,HashSet用來快速通路,TreeSet保持元素處于排序狀态,LinkedHashSet以插入順序儲存元素
  • 新程式中不應該使用過時的Vetor、HashTable、Stack
這個圖腦子裡應該大體有個印象
Java程式設計思想讀書筆記——第十一章:持有對象
其實這張也就是容器類的一個簡述而已,大體的功能什麼的了解一下,再後面會學到更多詳細的有關容器類的内容

繼續閱讀