JavaSE_集合

M-"發表於2020-12-12

集合概述

1、類集設定的目的(重點
物件陣列有那些問題?普通的物件陣列的最大問題在於陣列中的元素個數是固定的,不能動態的擴充大小,所以最早的時候可以通過連結串列實現一個動態物件陣列。但是這樣做畢竟太複雜了,所以在 Java 中為了方便使用者操作各個資料結構, 所以引入了類集的概念,有時候就可以把集合稱為 java 對資料結構的實現。 在整個類集中的,這個概念是從 JDK1.2(Java 2)之後才正式引入的,最早也提供了很多的操作類,但是並沒有完 整的提出類集的完整概念。 類集中最大的幾個操作介面:Collection、Map、Iterator,這三個介面為以後要使用的最重點的介面。 所有的類集操作的介面或類都在 java.util 包中。

Collection、Map基礎體系:

在這裡插入圖片描述在這裡插入圖片描述

Collection介面

1、Collection 介面是在整個 Java 類集中儲存單值的最大操作父介面,裡面每次操作的時候都只能儲存一個物件的資料。 此介面定義在 java.util 包中。
2、Collection 介面使用了泛型技術,在 JDK1.5之後為了使類集操作的更加安全,所以引入了泛型。

此介面定義如下:

	public interface Collection<E> extends Iterable<E> 

Collection 通用方法:
在這裡插入圖片描述本介面中一共定義了 15個方法,那麼此介面的全部子類或子介面就將全部繼承以上介面中的方法。

但是,在開發中不會直接使用 Collection 介面。而使用其操作的子介面:List、Set。

之所以有這樣的明文規定,也是在 JDK1.2 之後才有的。一開始在 EJB 中的最早模型中全部都是使用 Collection 操作 的,所以很早之前開發程式碼都是以 Collection 為準,但是後來為了更加清楚的區分,集合中是否允許有重複元素所以 SUN 在其開源專案 —— PetShop(寵物商店)中就開始推廣 List 和 Set 的使用。

List介面

在整個集合中 List 是 Collection 的子介面,裡面的所有內容都是允許重複的。

List 子介面的定義:

 public interface List<E> extends Collection<E> 

此介面上依然使用了泛型技術。此介面對於 Collection 介面來講有如下的擴充方法:
在這裡插入圖片描述

ArrayList

ArrayList 是 List 介面的子類,此類的定義如下:

public class ArrayList<E> 
			 extends AbstractList<E>
			 implements List<E>,RandomAccess,Cloneable,Serializable 

此類繼承了 AbstractList 類。AbstractList 是 List 介面的子類。AbstractList 是個抽象類,介面卡設計模式。

/**
		範例:增加及取得元素
*/
package org.listdemo.arraylistdemo;
import java.util.ArrayList; 
import java.util.List; 
public class ArrayListDemo01 { 
	public static void main(String[] args) {
	 	 List<String> all = new ArrayList<String>(); 
		 // 例項化List物件,並指定泛型型別 
	 	 all.add("hello "); 
	 	 // 增加內容,此方法從Collection介面繼承而來 
		 all.add(0, "LAMP ");
		 // 增加內容,此方法是List介面單獨定義的 
		 all.add("world"); 
		 // 增加內容,此方法從Collection介面繼承而來 
		 System.out.println(all); 
		 // 列印all物件呼叫toString()方法
	  }
 } 

可以發現,此時的物件陣列並沒有長度的限制,長度可以任意長,只要是記憶體夠大就行。

/**
 使用 remove()方法刪除若干個元素,並且使用迴圈的方式輸出。 
 根據指定位置取的內容的方法,只有 List 介面才有定義,
 其他的任何介面都沒有任何的定義。 
*/
package org.listdemo.arraylistdemo; 
import java.util.ArrayList; 
import java.util.List; 
	public class ArrayListDemo02 { 
		public static void main(String[] args) { 
			List<String> all = new ArrayList<String>(); 
			// 例項化List物件,並指定泛型型別 
			all.add("hello "); 
			// 增加內容,此方法從Collection介面繼承而來 
			all.add(0, "LAMP ");
			// 增加內容,此方法是List介面單獨定義的 
			all.add("world"); 
			// 增加內容,此方法從Collection介面繼承而來 
			all.remove(1); 
			// 根據索引刪除內容,此方法是List介面單獨定義的 
			all.remove("world");
			// 刪除指定的物件 
			System.out.print("集合中的內容是:"); 
			for (int x = 0; x < all.size(); x++) { 
			// size()方法從Collection介面繼承而來 
			System.out.print(all.get(x) + "、"); 
			// 此方法是List介面單獨定義的
		 } 
		} 
	}

ArrayList底層是用陣列實現的儲存,而陣列的特點就是查詢效率高,增刪效率低,執行緒不安全。所以日常需要使用List集合儲存並且儲存的資料需要頻繁的查詢時,可以選擇ArrayList進行儲存。

Vector

與 ArrayList 一樣,Vector 本身也屬於 List 介面的子類,此類的定義如下:

public class Vector<E> 
			extends AbstractList<E> 
			implements List<E>,RandomAccess,Cloneable,Serializable

此類與 ArrayList 類一樣,都是 AbstractList 的子類。所以,此時的操作只要是 List 介面的子類就都按照 List 進行操作。

package org.listdemo.vectordemo; 
import java.util.List; 
import java.util.Vector; 
	public class VectorDemo01 { 
		public static void main(String[] args) {
			 List<String> all = new Vector<String>(); 
			 // 例項化List物件,並指定泛型型別 
			 all.add("hello "); 
			 // 增加內容,此方法從Collection介面繼承而來 
			 all.add(0, "LAMP ");
			 // 增加內容,此方法是List介面單獨定義的 
			 all.add("world"); 
			 // 增加內容,此方法從Collection介面繼承而來 
			 all.remove(1); 
			 // 根據索引刪除內容,此方法是List介面單獨定義的 
			 all.remove("world");
			 // 刪除指定的物件 System.out.print("集合中的內容是:"); 
			 for (int x = 0; x < all.size(); x++) { 
			 // size()方法從Collection介面繼承而來 
			 System.out.print(all.get(x) + "、"); 
			 // 此方法是List介面單獨定義的
		     } 
		  }
	   }

  以上的操作結果與使用 ArrayList 本身並沒有任何的區別。因為操作的時候是以介面為操作的標準。
  但是 Vector 屬於 Java 元老級的操作類,是最早的提供了動態物件陣列的操作類,在 JDK1.0 的時候就已經推出了此 類的使用,只是後來在 JDK1.2 之後引入了 Java 類集合框架。但是為了照顧很多已經習慣於使用 Vector 的使用者,所以在 JDK1.2 之後將 Vector 類進行了升級了,讓其多實現了一個 List 介面,這樣才將這個類繼續保留了下來。

Vector 類和 ArrayList 類的區別
這兩個類雖然都是 List 介面的子類,但是使用起來有如下的區別,列出此內容:
在這裡插入圖片描述

LinkedList

LinkedList是 List 介面的子類,此類的定義如下:

public class LinkedList<E> 
			extends Abstract SequentialList<E> 
			implements List<E>,Deque<E>,Cloneable,Serializable

此類繼承了 AbstractList,所以是 List 的子類。但是此類也是 Queue 介面(佇列)的子類,Queue 介面定義瞭如下的方法:
在這裡插入圖片描述

import java.util.LinkedList; 
import java.util.Queue; 
		public class TestDemo { 
				public static void main(String[] args) { 
					Queue<String> queue = new LinkedList<String>();
					 queue.add("A"); 
					 queue.add("B"); 
					 queue.add("C"); 
					 int len=queue.size();
					 //把queue的大小先取出來,否則每迴圈一次,移除一個元素,就少 一個元素,那麼queue.size()在變小,就不能迴圈queue.size()次了。 
					 for (int x = 0; x <len; x++) { 
					 System.out.println(queue.poll()); 
					 } 
					 System.out.println(queue);
				  } 
		  }

LinkedLis該集合底層維護了一個雙向迴圈連結串列,連結串列中每一個元素都使用引用的方式來記住它的前一個元素和後一個元素,從而可以將所有的元素彼此連結起來。當插入一個新的元素時,只需要修改元素之間的這種引用關係即可,刪除一個節點也是如此。
正是因為這樣的儲存結構,所以LinkedList集合對於元素的增刪操作具有很高的效率。

Set介面

Set 介面也是 Collection 的子介面,與 List 介面最大的不同在於,Set 介面裡面的內容是不允許重複的。

Set 介面並沒有對 Collection 介面進行擴充,基本上還是與 Collection 介面保持一致。因為此介面沒有 List 介面中定義 的 get(intindex)方法(get()方法為List介面方法),所以無法使用迴圈進行輸出。

那麼在此介面中有兩個常用的子類:HashSet、TreeSet

HashSet

既然 Set 介面並沒有擴充任何的 Collection 介面中的內容,所以使用的方法全部都是 Collection 介面定義而來的。
HashSet 屬於雜湊的存放類集,裡面的內容是無序存放的。

package org.listdemo.hashsetdemo; 
import java.util.HashSet; 
import java.util.Set; 
		public class HashSetDemo01 { 
			public static void main(String[] args) { 
				Set<String> all = new HashSet<String>(); 
					// 例項化Set介面物件 
					all.add("A"); 
					all.add("B"); 
					all.add("C"); 
					all.add("D"); 
					all.add("E"); 
					System.out.println(all); 
			}
		 }

使用 HashSet 例項化的 Set 介面例項,本身屬於無序的存放。

那麼,現在思考一下?能不能通過迴圈的方式將 Set 介面中的內容輸出呢?
       是可以實現的,因為在 Collection 介面中定義了將集合變為物件陣列進行輸出。

package org.listdemo.hashsetdemo; 
import java.util.HashSet; 
import java.util.Set; 
		public class HashSetDemo01 { 
			public static void main(String[] args) { 
				Set<String> all = new HashSet<String>(); 
					// 例項化Set介面物件 
					all.add("A"); 
					all.add("B"); 
					all.add("C"); 
					all.add("D"); 
					all.add("E"); 
					Object obj[] = all.toArray(); 
					// 將集合變為物件陣列 
					for (int x = 0; x < obj.length; x++) { 
					System.out.print(obj[x] + "、"); 
			}
		 }

下面再進一步驗證 Set 介面中是不能有重複的內容的。

import java.util.Set; 
		public class HashSetDemo01 { 
			public static void main(String[] args) { 
				Set<String> all = new HashSet<String>(); 
					// 例項化Set介面物件 
					all.add("A");
					all.add("A");//重複的元素 
					all.add("A");//重複的元素 
					all.add("A");//重複的元素 
					all.add("A");//重複的元素 
					all.add("B"); 
					all.add("C"); 
					all.add("D"); 
					all.add("E"); 
					Object obj[] = all.toArray(); 
					// 將集合變為物件陣列 
					for (int x = 0; x < obj.length; x++) { 
					System.out.print(obj[x] + "、"); 
			}
		 }

以上字串“A”設定了很多次,因為 Set 介面中是不能有任何的重複元素的,所以其最終結果只能有一個“A”。

TreeSet

與 HashSet 不同的是,TreeSet 本身屬於排序的子類,此類的定義如下:

public class TreeSet<E> 
				extends AbstractSet<E> 
				implements NavigableSet<E>,Cloneable,Serializable 

下面通過程式碼來觀察其是如何進行排序的。

package org.listdemo.treesetdemo01; 
import java.util.Set; 
import java.util.TreeSet; 
public class TreeSetDemo01 { 
		public static void main(String[] args) { 
			Set<String> all = new TreeSet<String>(); 
			// 例項化Set介面物件\ 
			all.add("D"); 
			all.add("X");
			all.add("A");
			System.out.println(all);
		}
}
/**
輸出:A  D  X
*/

排序的說明
既然 Set 介面的 TreeSet 類本身是允許排序,那麼現在自定義一個類是否可以進行物件的排序呢?

package org.listdemo.treesetdemo02; 
		public class Person { 
				private String name; 
				private int age; 
				
				public Person() { } 
				
				public Person(String name, int age) { 
				this.name = name; this.age = age; 
				}
				 public String getName() { 
				 return name; 
				 } 
				 
			    public void setName(String name) { 
				 this.name = name; 
				 } 
				 
				public int getAge() {
				  return age; 
				  } 
				  
				public void setAge(int age) {
				   this.age = age; 
				   } 
				public String toString() { 
				   return "姓名:" + this.name + ",年齡:" + this.age;
				    } 
		    } 

下面定義一個 TreeSet 集合,向裡面增加若干個 Person 物件。

package org.listdemo.treesetdemo02; 
import java.util.Set; 
import java.util.TreeSet; 
	public class TreeSetPersonDemo01 { 
		public static void main(String[] args) { 
			Set<Person> all = new TreeSet<Person>(); 
			all.add(new Person("張三", 10)); 
			all.add(new Person("李四", 10));
			all.add(new Person("王五", 11)); 
			all.add(new Person("趙六", 12)); 
			all.add(new Person("孫七", 13)); 
			System.out.println(all);
		}
	}

執行以上的操作程式碼之後,發現出現瞭如下的錯誤提示:

Exception in thread "main" java.lang.ClassCastException: 
org.lamp.listdemo.treesetdemo02.Person cannot be cast to java.lang.Comparable 
		at java.util.TreeMap.put(Unknown Source) 
		at java.util.TreeSet.add(Unknown Source) 
		at 
org.lamp.listdemo.treesetdemo02.TreeSetPersonDemo01.main(TreeSetPersonDemo01.java:11) 

此時的提示是:Person類不能向 Comparable 介面轉型的問題? 所以,證明,如果現在要是想進行排序的話,則必須在 Person類中實現 Comparable 介面。

package org.listdemo.treesetdemo03; 
		public class Person implements Comparable<Person> { 
				private String name; 
				private int age; 
				
				public int compareTo(Person per) { 
					if (this.age > per.age) { 
						return 1; 
					} else if (this.age < per.age) { 
						return -1; 
					} else {
						 return 0; 
					 } 
				 } 
				 
				public Person() { } 
				
				public Person(String name, int age) { 
				this.name = name; this.age = age; 
				}
				 public String getName() { 
				 return name; 
				 } 
				 
			    public void setName(String name) { 
				 this.name = name; 
				 } 
				 
				public int getAge() {
				  return age; 
				  } 
				  
				public void setAge(int age) {
				   this.age = age; 
				   } 
				public String toString() { 
				   return "姓名:" + this.name + ",年齡:" + this.age;
				    } 
		    } 
//結果:[姓名:張三,年齡:10, 姓名:王五,年齡:11, 姓名:趙六,年齡:12, 姓名:孫七,年齡:13]

從以上的結果中可以發現,李四沒有了。因為李四的年齡和張三的年齡是一樣的,所以會被認為是同一個物件。則 此時必須修改 Person類,如果假設年齡相等的話,按字串進行排序。

public int compareTo(Person per) { 
		if (this.age > per.age) {
			 return 1; 
		 } else if (this.age < per.age) {
			  return -1;
		 } else { 
		      return this.name.compareTo(per.name); 
		 } 
   }

此時,可以發現李四出現了,如果加入了同一個人的資訊的話,則會認為是重複元素,所以無法繼續加入。

關於重複元素的說明
之前使用 Comparable 完成的對於重複元素的判斷,那麼 Set 介面定義的時候本身就是不允許重複元素的,那麼證明 如果現在真的是有重複元素的話,使用 HashSet 也同樣可以進行區分。

package org.listdemo.treesetdemo04; 
import java.util.HashSet; 
import java.util.Set; 
		public class HashSetPersonDemo01 { 
			public static void main(String[] args) { 
				Set<Person> all = new HashSet<Person>(); 
				all.add(new Person("張三", 10)); 
				all.add(new Person("李四", 10)); 
				all.add(new Person("李四", 10)); 
				all.add(new Person("王五", 11)); 
				all.add(new Person("趙六", 12)); 
				all.add(new Person("孫七", 13)); 
				System.out.println(all);
			 } 
		 }

此時發現,並沒有去掉所謂的重複元素,也就是說之前的操作並不是真正的重複元素的判斷,而是通過 Comparable 介面間接完成的。
如果要想判斷兩個物件是否相等,則必須使用 Object 類中的 equals()方法。

從最正規的來講,如果要想判斷兩個物件是否相等,則有兩種方法可以完成:
· 第一種判斷兩個物件的編碼是否一致,這個方法需要通過 hashCode()完成,即:每個物件有唯一的編碼
· 還需要進一步驗證物件中的每個屬性是否相等,需要通過 equals()完成。 所以此時需要覆寫 Object 類中的 hashCode()方法,此方法表示一個唯一的編碼,一般是通過公式計算出來的。

小結:
關於 TreeSet 的排序實現,如果是集合中物件是自定義的或者說其他系統定義的類沒有實現 Comparable 介面,則不能實現 TreeSet 的排序,會報型別轉換(轉向 Comparable 介面)錯誤。 換句話說要新增到 TreeSet 集合中的物件的型別必須實現了 Comparable 介面。

不過 TreeSet 的集合因為借用了 Comparable 介面,同時可以去除重複值,而 HashSet 雖然是 Set 介面子類,但是對於沒有複寫 Object 的 equals 和 hashCode 方法的物件,加入了 HashSet 集合中也是不能去掉重複值的。

Map介面

以上的 Collection 中,每次操作的都是一個物件,如果現在假設要操作一對物件,則就必須使用 Map 了,類似於以 下一種情況:
在這裡插入圖片描述
那麼儲存以上資訊的時候使用 Collection 就不那麼方便,所以要使用 Map 介面。裡面的所有內容都按照 key-value (鍵值對)的形式儲存,也稱為二元偶物件。

此介面定義如下:

public interface Map<K,V> 

此介面與 Collection 介面沒有任何的關係,是第二大的集合操作介面。此介面常用方法如下:
在這裡插入圖片描述Map 本身是一個介面,所以一般會使用以下的幾個子類:HashMap、TreeMap、Hashtable

HashMap

HashMap 是 Map 的子類,此類的定義如下:

public class HashMap<K,V> 
		extends AbstractMap<K,V> 
		implements Map<K,V>,Cloneable,Serializable 

此類繼承了 AbstractMap 類,同時可以被克隆,可以被序列化下來。

範例:向集合中增加內容

package org.listdemo.hashmapdemo; 
import java.util.HashMap; 
import java.util.Map; 
	public class HashMapDemo01 { 
		public static void main(String[] args) { 
			Map<Integer, String> map = new HashMap<Integer, String>(); 
			map.put(1, "張三A"); 
			map.put(1, "張三B"); 
			// 新的內容替換掉舊的內容 
			map.put(2, "李四"); 
			map.put(3, "王五"); 
			String val = map.get(1); 
			System.out.println(val); 
		} 
	} 

以上的操作是 Map 介面在開發中最基本的操作過程,根據指定的 key 找到內容,如果沒有找到,則返回 null,找到 了則返回具體的內容

以上的操作是 Map 介面在開發中最基本的操作過程,根據指定的 key 找到內容,如果沒有找到,則返回 null,找到 了則返回具體的內容。
範例:得到全部的 key 或 value

import java.util.Collection; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.Set; 
	public class HashMapDemo02 { 
		public static void main(String[] args) { 
		Map<Integer, String> map = new HashMap<Integer, String>(); 
		map.put(1, "張三A"); 
		map.put(2, "李四"); 
		map.put(3, "王五"); 
		Set<Integer> set = map.keySet(); 
		// 得到全部的key 
		Collection<String> value = map.values(); 
		// 得到全部的value 
		Iterator<Integer> iter1 = set.iterator(); 
		Iterator<String> iter2 = value.iterator(); 
		System.out.print("全部的key:"); 
		while (iter1.hasNext()) { 
			System.out.print(iter1.next() + "、"); 
		} 
		System.out.print("\n全部的value:"); 
		while (iter2.hasNext()) { 
			System.out.print(iter2.next() + "、"); 
			} 
		}
	 } 

Hashtable

Hashtable 是一個最早的 keyvalue 的操作類,本身是在 JDK1.0的時候推出的。其基本操作與 HashMap 是類似的。

操作的時候,可以發現與 HashMap 基本上沒有什麼區別,而且本身都是以 Map 為操作標準的,所以操作的結果形式 都一樣。但是 Hashtable 中是不能向集合中插入 null 值的。(HashMap可以儲存null鍵和null值)

HashMap 與 Hashtable 的區別
在整個集合中除了 ArrayList 和 Vector 的區別之外,另外一個最重要的區別就是 HashMap 與 Hashtable 的區別。
在這裡插入圖片描述

TreeMap

TreeMap 子類是允許 key 進行排序的操作子類,其本身在操作的時候將按照 key 進行排序,另外,key 中的內容可以 為任意的物件,但是要求物件所在的類必須實現 Comparable 介面。

package org.listdemo.treemapdemo; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.Set; 
import java.util.TreeMap; 
		public class TreeMapDemo01 {
		public static void main(String[] args) { 
		Map<String, String> map = new TreeMap<String, String>();
		 map.put("ZS", "張三"); 
		 map.put("LS", "李四"); 
		 map.put("WW", "王五"); 
		 map.put("ZL", "趙六"); 
		 map.put("SQ", "孫七"); 
		 Set<String> set = map.keySet(); 
		 // 得到全部的key 
		 Iterator<String> iter = set.iterator(); 
		 while (iter.hasNext()) { 
			 String i = iter.next(); 
		 // 得到key 
			 System.out.println(i + " --:> " + map.get(i));
		  } 
	}

Iterator介面

已經學習過了基本的集合操作,那麼對於集合的輸出本身也是有多種形式的,可以使用如下的幾種方式:
· Iterator 迭代輸出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)

但是在講解輸出的時候一定要記住以下的原則:“只要是碰到了集合,則輸出的時候想都不想就使用 Iterator 進行輸出。

Iterator 屬於迭代輸出,基本的操作原理:是不斷的判斷是否有下一個元素,有的話,則直接輸出。
此介面定義如下:

public interface Iterator<E> 

要想使用此介面,則必須使用 Collection 介面,在 Collection 介面中規定了一個 iterator()方法,可以用於為 Iterator 介面進行例項化操作。
此介面規定了以下的三個方法:
在這裡插入圖片描述通過 Collection 介面為其進行例項化之後,一定要記住,Iterator 中的操作指標是在第一條元素之上,當呼叫 next()方 法的時候,獲取當前指標指向的值並向下移動,使用 hasNext()可以檢查序列中是否還有元素。
在這裡插入圖片描述

package org.listdemo.iteratordemo; 
import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 

public class IteratorDemo01 { 
		public static void main(String[] args) { 
			Collection<String> all = new ArrayList<String>();
			 all.add("A"); all.add("B"); 
			 all.add("C"); all.add("D"); 
			 all.add("E"); 
			 
			 Iterator<String> iter = all.iterator(); 
			 while (iter.hasNext()) {
				 // 判斷是否有下一個元素 
				 String str = iter.next(); 
				 // 取出當前元素 
				 System.out.print(str + "、"); 
			 } 
		 } 
 }

以上的操作是 Iterator 介面使用最多的形式,也是一個標準的輸出形式。

但是在使用 Iterator 輸出的時候有一點必須注意,在進行迭代輸出的時候如果要想刪除當前元素,則只能使用 Iterator 介面中的 remove()方法,而不能使用集合中的 remove()方法。否則將出現未知的錯誤。

package org.listdemo.iteratordemo; 
import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 

	public class IteratorDemo02 { 
			public static void main(String[] args) {
				 Collection<String> all = new ArrayList<String>();
				 all.add("A"); 
				 all.add("B"); 
				 all.add("C"); 
				 all.add("D"); 
				 all.add("E"); 
				 Iterator<String> iter = all.iterator(); 
				 while (iter.hasNext()) {
				 // 判斷是否有下一個元素 
				 String str = iter.next(); 
				 // 取出當前元素 
				 if (str.equals("C")) { 
					 all.remove(str); 
				 // 錯誤的,呼叫了集合中的刪除 
				 } else { 
					 System.out.print(str + "、"); 
				 } 
	 }


此時出現了錯誤,因為原本的要輸出的集合的內容被破壞掉了。

		Iterator<String> iter = all.iterator(); 
			while (iter.hasNext()) {
			// 判斷是否有下一個元素 
			String str = iter.next(); 
			// 取出當前元素 
			if (str.equals("C")) { 
				iter.remove(); 
			// 注意:呼叫迭代器的刪除方法
			} else { 
				System.out.print(str + "、"); 
			} 

相關文章