《Java程式設計思想》筆記08——持有物件

CosmosLeung發表於2019-02-24

如果一個程式包含固定數量的且其生命週期都是已知的物件,那麼這是一個非常簡單的程式。然而事與願違,通常情況下,程式在執行之前並不知道物件的數量,甚至不知道物件的型別。因此,需要引入一項新的技術,可以在任意時刻和任意位置建立任意數量的物件。
Java類庫提供了一套相當完整的容器類來解決這個問題,其中基本的型別包括:List、Set、Map等,這些物件的型別也稱為集合類。不同的容器有不同的特性,但它們的共性就是:都可以自動的調整自身的大小。因此,與陣列不同,在程式設計時我們可以將任意數量的物件放置在容器中。

一、泛型與型別安全的容器

為了便於理解下面的這個例子,首先介紹一個簡單容器 ArrayList,我們可以把ArrayList當做是一個可以自動擴充自身大小的陣列。

我們可以通過add()方法插入物件,可以通過get()方法訪問物件,還可以通過size()方法來獲取當前容器的大小。


在Java SE5之前的版本,編譯器允許向容器中插入不正確的型別。

例如:要建立一個ArrayList用於放置Apple物件。如果把Orange物件也放入容器中,編譯器是不會報錯的。

/**
 * 泛型的使用
 * @author LiangYu
 * 2018-01-11
 */
public class GenericityTest {
	@Test
	public void test() throws Exception {
		ArrayList apples = new ArrayList();
		for(int i = 0 ; i < 4 ; i++){
			Apple apple = new Apple();
			apple.setId(i);
			apples.add(apple);
		}
		//新來的同事手誤,將Orange物件也放入了apples容器中
		Orange orange = new Orange();
		//編譯器並沒有報錯
		apples.add(orange);
		//遍歷容器的時候,悲劇了。。
		for(int i = 0 ; i < apples.size() ; i++){
			Apple apple = (Apple) apples.get(i);
			System.out.println(apple.getId());
		}
	}
}

class Apple{
	private int id;
	public void setId(int id){
		this.id = id;
	}
	public int getId(){
		return id;
	}
}

class Orange{}
複製程式碼

上面的例子可以看到,當我們把Orange物件放入盛有Apple物件的容器中的時候,編譯器並沒有報錯,而是在執行的時候,發現問題,並丟擲異常。

為了將類似的錯誤扼殺在搖籃中,我們可以宣告ArrayList<Apple>,用於告訴編譯器:這個容器只能盛放Apple物件,如果有人錯把其他物件放入容器,那麼請編譯器報錯提示。

@Test
public void test() throws Exception {
	ArrayList<Apple> apples = new ArrayList<Apple>();
	for(int i = 0 ; i < 4 ; i++){
		Apple apple = new Apple();
		apple.setId(i);
		apples.add(apple);
	}
	//新來的同事手誤,準備將Orange物件也放入了apples容器中
	Orange orange = new Orange();
	//此時編譯器報錯,提示他不能將Orange物件放入容器中
//  apples.add(orange);
	//遍歷容器,執行正常
	for(int i = 0 ; i < apples.size() ; i++){
		Apple apple =  apples.get(i);
		System.out.println(apple.getId());
	}
}
複製程式碼

通過泛型,編譯器可以阻止Orange物件放入apples容器中,將執行時錯誤,變成了編譯時錯誤

二、基本概念

Java容器類的主要用途是“儲存物件”,並將其劃分為兩個概念:

1.Collection:一個獨立元素的序列,這些元素都服從一條或多條規則。List必須按照插入的順序儲存元素;Set不能有重複元素,Queue按照排隊規則來確定物件產生的順序(通常與它們被插入的順序相同)。

2.Map:一組成對的“鍵值對”物件,允許使用鍵來查詢值。ArrayList允許使用數字來查詢值。因此,在某種意義上講,它將數字和物件關聯在一起。對映表允許我們使用另一個對映來查詢某個物件,它也稱為“關聯陣列”。因為它將某些物件和另一些物件關聯在了一起。或者成為“字典”,因為可以使用鍵物件來查詢值物件。

在建立的時候就要指定所使用的精確型別,例如:

List<String> texts = new ArrayList<String>();
複製程式碼

使用介面的目的在於如果決定去修改你的實現,只需要在建立時修改它,例如:

List<String> texts = new LinkedList<String>();
複製程式碼

Collection介面概括了序列的概念——————一種存放物件的方式。

例如:用Integer物件填充一個Collection,然後列印容器中所有元素。

/**
 * 序列的概念
 * @author LiangYu
 * 2018-01-16
 */
public class CollectionTest {
	@Test
	public void test() throws Exception {
		Collection<Integer> numbers = new ArrayList<Integer>();
		for(int i = 0 ; i < 10 ; i++){
			numbers.add(i);
		}
		for(int i : numbers){
			System.out.println(i);
		}
	}
}
複製程式碼

add()方法的名稱就比表明它要將一個新元素加入到Collection中。
所有的Collection都可以用foreach遍歷。

三、新增一組元素

在java.util包下的Arrays和Collections類中的很多實用方法可以在一個Collection中新增一組元素。

Arrays.asList()方法接受一個陣列或是一個用逗號分隔的元素列表(可變引數),並將其轉換為一個List物件。

Collections.addAll()方法接受一個Collection物件,以及一個陣列或是一個用逗號分隔的列表,將元素新增到Collection中。

/**
 * 新增一組元素
 * @author LiangYu
 * 2018-01-16
 */
public class AddCollectionTest {
	@Test
	public void test() throws Exception {
		//Collection的構造器可以接受另一個Collection
		Collection<Integer> numbers = new ArrayList<Integer>(Arrays.asList(2,4,6,8,10));
		Integer[] moreInts = {1,3,5,7,9};
		//Collection.addAll()執行起來要快很多,但是它只能接受另一個Collection作為引數,不夠靈活
		numbers.addAll(Arrays.asList(moreInts));
		//Collections.addAll()是首選方式
		Collections.addAll(numbers, 11,12,13,14);
		Collections.addAll(numbers, moreInts);
		List<Integer> numberList = Arrays.asList(15,16,17,18,19,20);
		numberList.set(2, 21);
		//通過Arrays.asList()建立的Collection物件底層依舊是陣列,因此,不能調整尺寸。
//		numberList.add(98);  執行時報錯:UnsupportedOperationException
		System.out.println(numbers);
		System.out.println(numberList);
	}
}

複製程式碼

總結:

  1. Collection的構造器可以接受另一個Collection
  2. Collection物件的addAll()執行起來要快很多,但是它只能接受另一個Collection作為引數,不夠靈活
  3. Collections.addAll()是首選方式
  4. 通過Arrays.asList()建立的Collection物件底層依舊是陣列,因此,不能調整尺寸。

Arrays.asList()方法的限制是它對所產生的List的型別做出最理想的假設,而並沒有注意你會給它賦予什麼型別。有時會引發問題。

四、容器的列印

必須使用Arrays.toString()方法來產生陣列的可列印表示。列印容器不需要任何幫助。

eg:

/**
 * Collection的列印
 * @author LiangYu
 * 2018-01-16
 */
public class CollectionPrintTest {
	@Test
	public void test() throws Exception {
		System.out.println("ArrayList:"+fill(new ArrayList<String>()));
		System.out.println("LinkedList:"+fill(new LinkedList<String>()));
		System.out.println("HashSet:"+fill(new HashSet<String>()));
		System.out.println("TreeSet:"+fill(new TreeSet<String>()));
		System.out.println("LinkedHashSet:"+fill(new LinkedHashSet<String>()));
		System.out.println("HashMap:"+fill(new HashMap<String,String>()));
		System.out.println("TreeMap:"+fill(new TreeMap<String,String>()));
		System.out.println("LinkedHashMap:"+fill(new LinkedHashMap<String,String>()));
	}
	
	Collection fill(Collection<String> collection){
		collection.add("dog");
		collection.add("cat");
		collection.add("fish");
		collection.add("dog");
		return collection;
	}
	
	Map fill(Map<String,String> map){
		map.put("cat", "Apache");
		map.put("dog", "Cookie");
		map.put("fish", "Tom");
		map.put("dog", "July");
		return map;
	}
}
複製程式碼

結論:

1.Collection在每一個“槽”裡面只能儲存一個元素,此類容器包括:List(以特定的順序儲存一組元素)、Set(不能重複)、Queue(只允許容器的一端插入元素,另一端移除元素); Map在每一個“槽”裡面儲存兩個元素,分別是元素的鍵和它對應的值。

2.ArrayList和LinkedList都是List型別,按照被插入的順序儲存元素。不同之處:

(1)在執行某些型別的操作時效能不同

(2)LinkedList包含的操作多於ArrayList

3.HashSet和TreeSet、LinkedHashSet都是Set型別,輸出顯示在Set中。每一個相同的項只能儲存一次。HashSet擁有最快的獲取元素方式;TreeSet按照比較結果升序儲存物件;LinkedHashSet按照被新增的順序儲存物件。

4.HashMap和TreeMap、LinkedHashMap都是Map型別。Map是通過鍵來查詢物件。對於每一個鍵,Map只儲存一次。HashMap提供了最快的查詢技術;TreeMap按照比較結果升序儲存鍵;LinkedHashMap根據插入順序儲存鍵,同時保留了HashMap的查詢速度。

五、List

List可以將元素維護在特定的序列中。List介面在Collection的基礎上新增了大量的方法,使得可以在List中間插入和移除元素。

有兩種型別的List:

  • 基本的ArrayList,隨機訪問元素比較快,但是在List的中間插入和移除元素時較慢。
  • LinkedList:可以用較低代價進行插入和移除操作,優化了順序訪問,在隨機訪問方面比較慢。
/**
 * ArrayList和LinkedList的使用
 * @author LiangYu
 * 2018-01-16
 */
public class ListTest {
	@Test
	public void test() throws Exception {
		List<Pet> pets = new ArrayList<Pet>(Arrays.asList(new Pet(),new Dog(),new OrangeCat(),new Mouse(),new BlueCat(),new Dog(),new WhiteMouse()));
  		System.out.println("1:"+pets);
		BlueMouse blueMouse = new BlueMouse();
  		//新增元素
		pets.add(blueMouse);
  		System.out.println("2:"+pets);
  		System.out.println("3:"+pets.contains(blueMouse)); //判斷元素是否存在
  		pets.remove(blueMouse); //移除元素
  		Pet p = pets.get(2); //根據索引獲取元素
  		System.out.println("4:"+p+" "+pets.indexOf(p)); //獲取元素的索引
  		Pet orangeCat = new OrangeCat(); 
  		System.out.println("5:"+pets.indexOf(orangeCat)); //如果元素不存在,索引返回-1
  		System.out.println("6:"+pets.remove(orangeCat)); //如果元素不存在,remove方法返回false
  		System.out.println("7:"+pets.remove(p));  //移除元素成功,remove方法返回true
  		System.out.println("8:"+pets);
  		pets.add(1,new Mouse()); //在指定位置新增物件
  		System.out.println("9:"+pets);
  		List<Pet> sub = pets.subList(1, 5); //擷取集合
  		System.out.println("subList:"+sub);
  		System.out.println("10:"+pets.containsAll(sub)); //判斷某集合是不是該集合的子集
  		Collections.shuffle(sub,new Random(47)); //打亂排序
  		System.out.println("shuffle:"+sub);
  		List<Pet> petsCopy = new ArrayList<Pet>(pets);  //拷貝一個元素內容相同的容器
  		sub = Arrays.asList(pets.get(2),pets.get(4)); 
  		System.out.println("sub:"+sub);
  		petsCopy.retainAll(sub); // 獲取兩個集合的交集部分
  		System.out.println("11:"+petsCopy);
  		petsCopy = new ArrayList<Pet>(pets); 
  		petsCopy.remove(2);
  		System.out.println("12:"+petsCopy);
  		petsCopy.removeAll(sub); //移除和sub交集的部分
  		System.out.println("13:"+petsCopy);
  		petsCopy.set(1, new BlackMouse()); //將指定位置的元素進行修改
  		System.out.println("14:"+petsCopy);
  		petsCopy.addAll(2,sub); //在指定位置插入集合
  		System.out.println("15:"+petsCopy);
  		System.out.println("16:"+pets.isEmpty()); //isEmpty方法判斷集合是否為空
   		pets.clear(); //清空集合
  		System.out.println("17:"+pets);
  		System.out.println("18:"+pets.isEmpty());
  		Object[] objects = petsCopy.toArray(); //list轉換為陣列
  		System.out.println("19:"+objects[2]);
  		
	}
}


class Pet{
	@Override
	//列印時輸出類名
	public String toString() {
		return getClass().getSimpleName();
	}
}

class Cat extends Pet{}

class OrangeCat extends Cat{}

class BlueCat extends Cat{}

class Dog extends Pet{}

class Mutt extends Dog{}

class Pug extends Dog{}

class SmallOrangeCat extends OrangeCat{}

class Mouse extends Pet{}

class WhiteMouse extends Mouse{}

class BlackMouse extends Mouse{}

class BlueMouse extends Mouse{}
複製程式碼

結論:

  1. List允許在它被建立以後新增元素、移除元素,或者自我調節尺寸
  2. contains()方法用於判斷某個物件是否存在於容器中
  3. remove()移除、add()新增
  4. 通過indexOf()方法用於獲取指定元素的索引
  5. subList()可以獲取列表的片段
  6. removeAll(List list) 從List中移除引數List中的所有元素
  7. retainAll(List list) 獲取交集
  8. set(int index,Object object) 將指定位置的元素替換
  9. isEmpty() 判斷列表是否為空; clear() 清空列表

六、迭代器

任何容器類都必須有某種方式可以插入元素並將它們再次取回。對於List而言,add()是插入元素的方法之一,get()是取出元素的方法之一。

如果原本對著List編碼,但是後來發現能夠把相同的程式碼應用於Set,此時如果程式碼使用了add()和get()將會影響可移植性。

迭代器的概念則可以用於達成此目的。迭代器是一個物件,它的工作是遍歷並選擇序列中的物件。開發者不需要關注該序列底層的結構。無論是List還是Set都可以直接使用迭代器。此外,迭代器是輕量級物件,建立它的代價非常小。因此,它的功能受到了一定的限制。

  1. 迭代器只能單向移動
  2. 通過iterator()要求容器返回一次Iterator物件。Iterator將準備好返回序列的第一個元素。
  3. 使用next()方法可以獲取序列中的下一個元素
  4. 使用hasNext()判斷序列中是否還有元素
  5. 使用remove() 將迭代器新近返回的元素刪除
/**
 * 迭代器的使用
 * @author LiangYu
 * 2018-01-16
 */
public class IteratorTest {
	@Test
	public void test() throws Exception {
		List<Integer> numbers = new ArrayList<Integer>(Arrays.asList(0,1,2,3,4,5,6,7,8,9));
		//獲取迭代器
		Iterator<Integer> iterator = numbers.iterator();
		//迭代
		while(iterator.hasNext()){
			int num = iterator.next();
			System.out.print(num);
		}
		System.out.println();
		for(int i : numbers){
			System.out.print(i);
		}
		System.out.println();
		//重置迭代器
		iterator = numbers.iterator();
		for(int i = 0 ; i < 4 ; i++){
			iterator.next();
			iterator.remove();
		}
		System.out.print(numbers);
	}
}
複製程式碼

有了迭代器,我們就不需要關心容器中元素的數量。那是hasNext()和next()關心的事。

Iterator中,remove()方法可以移除next()產生的最後一個元素,因此,呼叫remove()之前,首先需要呼叫next()

如果只是遍歷List,而不修改List本身,那麼foreach語法更簡潔。

例:建立一個display方法,用於遍歷所有內容,而不需要關注容器的確切型別

/**
 * 通過迭代器展示所有元素
 * @author LiangYu
 * 2018-01-16
 */
public class DisplayCollectionTest {
	@Test
	public void test() throws Exception {
		ArrayList<String> textList = new ArrayList<String>(
				Arrays.asList("Qwe","ert","yui","opasdf","ghj","kl","zx","Qwe","xcvb"));
		LinkedList<String> textLinkedList = new LinkedList<String>(textList);
		TreeSet<String> textTreeSet = new TreeSet<String>(textList);
		HashSet<String> textHashSet = new HashSet<String>(textList);
		DisplayUtil.display(textList.iterator());
		DisplayUtil.display(textLinkedList.iterator());
		DisplayUtil.display(textTreeSet.iterator());
		DisplayUtil.display(textHashSet.iterator());
	}
}

class DisplayUtil{
	//通用方法,可以在不考慮容器型別的前提下,遍歷展示所有元素
	static void display(Iterator<String> texts){
		while(texts.hasNext()){
			String msg = texts.next();
			System.out.print(msg+" ");
		}
		System.out.println();
	}
}
複製程式碼

Iterator可以將遍歷序列的操作與序列底層的結構分離。

ListIterator

ListIterator是Iterator的子型別,只能用於List類的訪問。ListIterator可以雙向移動。它還可以產生相對於迭代器在列表中指向當前位置的前一個元素和後一個元素的索引。並且可以使用set()方法替換它訪問過的最後一個元素。同時,還可以呼叫listIterator(n)方法建立一個一開始就指向列表索引為n的元素。


/**
 * ListIterator的使用
 * @author LiangYu
 * 2018-01-16
 */
public class ListIteratorTest 
{
	@Test
	public void test() throws Exception {
		List<Integer> numbers = new ArrayList<Integer>(
				Arrays.asList(1,3,5,7,9,11,13,15,17,19,21,23,25,27,29));
		//建立一個ListIterator
		ListIterator<Integer> listIterator = numbers.listIterator();
		//獲取元素為17的前一個元素索引和後一個元素索引
		while(listIterator.hasNext()){
			System.out.print(listIterator.next()+","+listIterator.previousIndex()+","+listIterator.nextIndex());
			System.out.println();
		}
		//建立一個ListIterator,並且,從第10個索引開始
		listIterator = numbers.listIterator(10);
		//反向遍歷
		while(listIterator.hasPrevious()){
			System.out.print(listIterator.previous()+" ");
		}
		System.out.println();
		//修改元素的值
		listIterator = numbers.listIterator();
		while(listIterator.hasNext()){
			listIterator.set(listIterator.next()-1);
		}
		System.out.println(numbers);
	}
}
複製程式碼

七、LinkedList

LinkedList也像ArrayList一樣,實現了基本的List介面。但是它在執行插入和移除時,比ArrayList高效。

LinkedList還新增了可以使其用作棧、佇列、雙端佇列的方法。這些方法中有些彼此之間只是名字不同或者只存在些許差異。例如:

getFirst()方法和element()方法完全一樣,返回列表的第一個元素,而並不移除它,如果List為空,則拋異常。

peek()方法也是獲取列表的第一個元素,如果List為空,則返回null。

removeFirst()和remove()完全一樣。它們移除並返回列表的頭,而在List為空時丟擲異常。

poll()方法也是移除並返回頭,如果List為空,則返回null。

addFirst()add()addLast() 都是在指定位置插入元素。

removeLast() 移除並返回最後一個元素。

/**
 * LinkedList的使用
 * @author LiangYu
 * 2018-01-16
 */
public class LinkedListTest {
	@Test
	public void test() throws Exception {
		LinkedList<String> texts = new LinkedList<String>(Arrays.asList("A","B","C","D","E","F","G","H"));
		System.out.println(texts);
		//獲取列表的第一個元素
		System.out.println("texts.getFirst()--->"+texts.getFirst());
		System.out.println("texts.element()--->"+texts.element());
		System.out.println("texts.peek()--->"+texts.peek());
		//獲取並移除列表的第一個元素
		System.out.println("texts.remove()--->"+texts.remove());
		System.out.println("texts.removeFirst()--->"+texts.removeFirst());
		System.out.println("texts.poll()--->"+texts.poll());
		//新增元素
		texts.offer("I");
		System.out.println("texts.offer()--->"+texts);
		texts.addFirst("J");
		System.out.println("texts.addFirst()--->"+texts);
		texts.add("K");
		System.out.println("texts.add()--->"+texts);
		texts.removeLast();
		System.out.println("texts.removeLast()--->"+texts);
	}
}
複製程式碼

八、Stack

棧:後進先出。最後一個進棧的元素第一個出來。

LinkedList具備直接實現棧功能的所有方法。不過,為了能夠看清楚棧的功能,在這裡建立一個Stack類:

/**
 * 棧
 * @author LiangYu
 * 2018-01-16
 */
public class StackTest {
	@Test
	public void test() throws Exception {
		Stack<String> texts = new Stack<String>();
		texts.push("A");
		texts.push("B");
		texts.push("C");
		while(!texts.isEmpty()){
			System.out.print(texts.pop());
		}
		System.out.println();
		
	}
}

class Stack<T>{
	private LinkedList<T> storage = new LinkedList<T>();
	//新增資料到棧頂
	public void push(T v){
		storage.addFirst(v);
	}
	//獲取棧頂的元素
	public T peek(){
		return storage.getFirst();
	}
	//移除棧頂
	public T pop(){
		return storage.removeFirst();
	}
	//判斷是否為空
	public boolean isEmpty(){
		return storage.isEmpty();
	}
	//過載toString
	@Override
	public String toString() {
		return storage.toString();
	}
}
複製程式碼

九、Set

Set不儲存重複的元素,如果試圖將相同元素的多個例項新增到Set中,那麼它會阻止這種重複現象。Set最常被使用的是測試歸屬性,你可以輕易的詢問某個物件是否在Set中,對於Set而言,查詢是最重要的操作。

Set和Collection一樣的介面,沒有任何額外的功能,其實Set就是Collection,只是行為不同。Set是基於物件值來確定歸屬性。

/**
 * Set的使用
 * @author LiangYu
 * 2018-01-16
 */
public class SetTest {
	@Test
	public void test() throws Exception {
		Random random = new Random(47);
		Set<Integer> numbers = new HashSet<Integer>();
		for(int i = 0 ; i < 10000;i++){
			numbers.add(random.nextInt(20));
		}
		System.out.println(numbers);
	}
}
複製程式碼

輸出的順序是從0~29。(Java8版本之後,HashSet的底層使用演算法已經作了修改。後面會專門寫幾篇關於utils包下一些類的原始碼解析,此處不再贅述

十、Map

用於將物件對映到其他物件。

例如:檢查Random的隨機性。(鍵為隨機數,值為該數字出現的次數)

/**
 * 使用Map容器檢查Random
 * @author LiangYu
 * 2018-01-16
 */
public class RandomTest {
	@Test
	public void test() throws Exception {
		Random random = new Random(47);
		Map<Integer,Integer> randomMap = new HashMap<Integer,Integer>();
		for(int i = 0 ; i < 40000 ; i++){
			int r = random.nextInt(20);
			Integer value = randomMap.get(r);
			randomMap.put(r, value==null?1:value+1);
		}
		System.out.println(randomMap);
	}
}
複製程式碼

結論:

1.get(Object key) 通過鍵獲取值

2.put(Object key,Object value) 插入資料

eg:建立一個Map容器,鍵:寵物名稱(String),值:寵物型別(Pet)

/**
 * 建立一個Map容器,鍵:寵物名稱(String),值:寵物型別(Pet)
 * @author LiangYu
 * 2018-01-16
 */
public class PetMapTest {
	@Test
	public void test() throws Exception {
		Map<String,Pet> petMap = new HashMap<String,Pet>();
		petMap.put("Apache",new Cat());
		petMap.put("Cookie", new Dog());
		petMap.put("Jerry", new Mouse());
		System.out.println(petMap);
		System.out.println(petMap.get("Cookie"));
		System.out.println(petMap.containsKey("Apache"));
		System.out.println(petMap.containsValue(new Mouse()));
		System.out.println(petMap.containsValue(petMap.get("Jerry")));
	}
}
複製程式碼

Map甚至可以是多維的。
例如:建立一個容器,鍵為Person物件,值為Pet的List容器,代表一個人擁有多個寵物。

/**
 * 一個人擁有多個寵物的情況
 * @author LiangYu
 * 2018-01-16
 */
public class PersonPetListTest {
	@Test
	public void test() throws Exception {
		Map<Person, List<? extends Pet>> personPetListMap = new HashMap<Person,List<? extends Pet>>();
		personPetListMap.put(new Person(0,"KosmoLeung"),Arrays.asList(new Dog(),new Cat()));
		personPetListMap.put(new Person(1,"AirSu"),Arrays.asList(new BlackMouse(),new BlueCat()));
		personPetListMap.put(new Person(2,"LindaWu"), Arrays.asList(new BlueMouse(),new WhiteMouse()));
		System.out.println("People:"+personPetListMap.keySet());
		System.out.println("Pets:"+personPetListMap.values());
		//遍歷Map(同時需要key和value時)
		for(Map.Entry<Person, List<? extends Pet>> entry : personPetListMap.entrySet()){
			System.out.println(entry.getKey()+"---"+entry.getValue());
		}
		//遍歷Map(僅需要輸出key或value二者之一)
		for(Person person : personPetListMap.keySet()){
			System.out.println(person);
		}
		for(List<? extends Pet> petValue : personPetListMap.values()){
			System.out.println(petValue);
		}
		//遍歷Map(可以遍歷的同時刪除某個元素,用remove方法)
		Iterator<Map.Entry<Person, List<? extends Pet>>> iterator = personPetListMap.entrySet().iterator();
		while(iterator.hasNext()){
			Map.Entry<Person,List<? extends Pet>> entry = iterator.next();
			System.out.println(entry.getKey()+"---"+entry.getValue());
		}
	}
}
複製程式碼

十一、Queue

佇列:先進先出。從容器的一端放入資料,另一端取出資料。

佇列在併發程式設計中非常重要。(以後寫)

LinkedList提供了方法以支援佇列的行為。並且它實現了Queue介面。因此LinkedList可以用作Queue的實現。通過將LinkedList向上轉型為Queue。

eg:

/**
 * 佇列的使用
 * @author LiangYu
 * 2018-01-16
 */
public class QueueTest {
	void printQueue(Queue queue){
		while(queue.peek() != null){
			System.out.print(queue.remove()+" ");
		}
		System.out.println();
	}
	
	@Test
	public void test() throws Exception {
		Queue<Integer> queue = new LinkedList<Integer>();
		Random random = new Random(47);
		for(int i = 0 ; i < 10 ; i++){
			queue.offer(random.nextInt(i+10));
		}
		printQueue(queue);
		Queue<Character> text = new LinkedList<Character>();
		for(char c : "Hello World!".toCharArray()){
			text.offer(c);
		}
		printQueue(text);
	}
}
複製程式碼

結論:

  1. 新增元素 : offer()
  2. 獲取第一個元素:peek() element()
  3. 刪除元素: poll() remove()

PriorityQueue 優先順序佇列

物件會在容器內部被排序,預設的排序是佇列的自然排序,也可以通過提供自己的Comparator來修改這個順序。

/**
 * 優先順序佇列
 * @author LiangYu
 * 2018-01-16
 */
public class PriorityQueueTest {
	@Test
	public void test() throws Exception {
		PriorityQueue<Character> pStrings = new PriorityQueue<Character>();
		for(char c : "Hello World!".toCharArray()){
			pStrings.add(c);
		}
		printPriorityQueue(pStrings);
	}
	void printPriorityQueue(PriorityQueue<Character> pStrings){
		while(pStrings.peek() != null){
			System.out.print(pStrings.remove()+" ");
		}
		System.out.println();
	}
}
複製程式碼

十二、Collection和Iterator

/**
 * Collection與Iterator
 * @author LiangYu
 * 2018-01-16
 */
public class CollectionVSIteratorTest {
	void display(Iterator<Student> it){
		while(it.hasNext()){
			Student student = it.next();
			System.out.println(student);
		}
		System.out.println("*************************************");
	}
	
	void display(Collection<Student> students){
		for(Student student : students){
			System.out.println(student);
		}
		System.out.println("*************************************");
	}
	
	@Test
	public void test() throws Exception {
		List<Student> students = new ArrayList<Student>(Arrays.asList(
				new Student(1, "KosmoLeung"),
				new Student(2, "AirSu"),
				new Student(3, "LindaWu"),
				new Student(4, "NancyLee")));
		Set<Student> studentSet = new HashSet<Student>(students);
		Map<String, Student> studentMap = new LinkedHashMap<String,Student>();
		String[] ids = "s001,s002,s003,s004".split(",");
		for(int i = 0 ; i < ids.length ; i++){
			studentMap.put(ids[i], students.get(i));
		}
		display(students);
		display(studentSet);
		display(students.iterator());
		display(studentSet.iterator());
		System.out.println(studentMap);
		System.out.println(studentMap.keySet());
		display(studentMap.values());
		display(studentMap.values().iterator());
		
	}
}

class Student{
	private int id;
	private String name;
	public Student(int id,String name) {
		this.id = id;
		this.name = name;
	}
	@Override
	public String toString() {
		return id+":"+name;
	}
}
複製程式碼

結論:

1.Collection介面和Iterator都可以將display方法與底層容器解耦

2.Collection更方便一些,可以使用foreach

Foreach與迭代器

foreach可以應用於任何Collection物件。

因為Java SE5引入了新的被稱為Iterable的介面,該介面包含iterator方法,並且Iterable介面被foreach用來在序列中移動。因此,只要你建立了任何實現Iterable的類,都可以將它用於foreach語句。

/**
 * Iterable的使用
 * @author LiangYu
 * 2018-01-16
 */
public class IterableTest {
	@Test
	public void test() throws Exception {
		IterableClass iterableClass = new IterableClass("Hello,World,I,am,Kosmo,Leung,!".split(","));
		for(String msg : iterableClass){
			System.out.print(msg+" ");
		}
	}
}

class IterableClass implements Iterable<String>{

	private String[] word;
	public IterableClass(String[] word){
		this.word = word;
	}
	@Override
	public Iterator<String> iterator() {
		return new Iterator<String>() {
			private int index = 0;
			@Override
			public boolean hasNext() {
				return index<word.length;
			}

			@Override
			public String next() {
				return word[index++];
			}
			
		};
	}
	
}
複製程式碼

注意:foreach可以用於陣列或其他任何Iterable,但並不意味著陣列肯定是Iterable,而自動包裝也不會發生

例如:

/**
 * 陣列並不一定是Iterable
 * @author LiangYu
 * 2018-01-16
 */
public class ArrayIsNotIterableTest {
	@Test
	public void test() throws Exception {
		String[] textArray = {"A","B","C"};
//		iterable(textArray); //編譯不通過
		iterable(Arrays.asList(textArray));
	}
	
	<T> void  iterable(Iterable<T> iterable){
		for(T t : iterable){
			System.out.print(t+" ");
		}
	}
}
複製程式碼

相關文章