iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0

昨天提到了集合的基本操作,及繼承樹,那我們再來更了解每個集合的特性吧

Set

Set 是不允許重複元素的集合,不保證元素的順序,並且通常不是按照插入順序或排序順序存儲元素的。它主要關注的是元素的獨特性,確保一個元素不會出現多次。Set 集合是一個很好的選擇,當您需要確保一個集合中的元素都是唯一的,而順序並不重要時,就適合用Set。

方法:

可使用方法基本上和Collection 基本相同,沒有提供任何額外的方法,只是要注意不能包含重複元素

Set 集合的主要特點:

  1. 無序性:Set 集合中的元素沒有特定的順序,不保證元素按照特定的順序排列。這是與 List 集合不同的地方,List 保留了元素的插入順序。
  2. 獨特性:Set 集合不允許重複的元素。如果您嘗試將一個已經存在的元素添加到 Set 中,將不會成功,並且不會引發異常
  3. 高效性:Set 集合通常提供了高效的查找和插入操作。底層實現通常使用散列表或樹結構,這些結構允許在平均情況下快速查找元素。

Set 集合的實作類別

  1. **HashSet:**最常使用的實作類別,HashSet按Hash 演算法來儲存集合中的元素,因此具有很好的存取和尋找功能

    判斷兩個元素相等的標準是兩個物件通過equals()方法比較相等、並且兩個物件的hashCode()方法返回值也相等

  2. **LinkedHashSet:**LinkedHashSet 同樣也是根據元素的hashCode值來決定元素儲存位置,但它保留了元素的插入順序,因此在迭代時可以按照插入順序訪問元素,因為需要維護元素的插入順序,因此效能略低於HashSet

  3. **TreeSet:**是SortedSet介面的實作類別

    有多提供額外的方法-https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeSet.html

    TreeSet採用紅黑樹的資料結構來儲存集合元素,支援兩種排序方法:自然排序和自訂排序

    自然排序:

    TreeSet會呼叫集合元素的compareTo(Object o)來比較元素之間的大小關係,然後將集合元素按遞增排序

    自訂排序:

    通過Comparator 介面幫助,可藉由int comparator(T o1, T o2)方法,比較o1和o2大小,

    返回正整數表明o1>o2 ;

    返回0 表明o1 = o2 ;

    返回負整數表明 o1 < o2 ;

        public class HashSetExample {
        public static void main(String[] args) {
            // 創建一個 HashSet
            Set<String> set = new HashSet<>();
    
            // 添加元素
            set.add("apple");
            set.add("banana");
            set.add("cherry");
    
            // HashSet 確保元素唯一性,因此重複的元素不會被添加
            set.add("apple");
    
            // 遍歷並打印元素
            for (String fruit : set) {
                System.out.println(fruit);
            }
    
                    HashSet<PersonDemo> s3 = new HashSet<>();
                    PersonDemo p1 = new PersonDemo("A");
                    s3.add(p1);
                    s3.add(new PersonDemo("T"));
                    s3.add(new PersonDemo("W"));
                    s3.add(p1);
                    System.out.println(s3);
        }
    //--------------------------
    class PersonDemo{
        String name;
        public PersonX(String name) {
            super();
            this.name = name;
        }
        @Override
        public String toString() {
            return  name ;
        }
        @Override
        public int hashCode() {
            return 100;
        }
        @Override
        public boolean equals(Object obj) {
            return true;
        }
    }
    

List

List 適用於有次序性、但元素可能重複的集合。List 集合在使用上和陣列有些類似, 因為 List 集合中的元素, 也都可透過索引 (index) 來存取。

因此 List 介面定義了一些與索引有關的方法, 除了繼承自 Collection介面的 add()、remove() 的新增移除元素方法, 也增加了可指定索引值來新增或移除元素的方法。

  • void add(int index, Object element):將元素element插入到List集合的index處
  • boolean addAlI(int index, Collection c):將集合 c 所包含的所有元素都插人到List集合的index處
  • Object get(int index):返回集合index索引處的元素
  • int indexOf(Object o):返回物件 o 在List集合中第一次出現的位置索引。
  • int lastIndexOf(Object o):返回物件 o 在List集合中最後一次出現的位置索引。
  • Object remove(int index):刪除並返回index索引處的元素
  • Object set(int index, Object element):將index索引處的元素替換成element物件
    返回被取代的舊元素
  • List subList(int fromIndex, int tolndex):返回從索引fromIndex(包含)到索引
    toIndex (不包含)處所有集合元素組成的子集合。
  • void replaceAII(Unary Operator operator):根據operator指定的運算規則重新設置
    List集合的所有元素。
  • void sort(Comparator c):根據Comparator參數對List集合的元素排序

下面具個例子說明:

public class ListDemo01 {
	public static void main(String[] args) {
		
		ArrayList a01 = new ArrayList();
		a01.add(100);
		a01.add(78);
		a01.add("小明");
		a01.add(65);
		a01.add(33.75);

		//插入元素
		a01.add(2, 90);
		System.out.println(a01);
		
		//取代舊元素
		a01.set(2, 11);
		System.out.println(a01)

		for(int i=0; i<a01.size(); i++) {
				String value = (String)list.get(i);//轉型錯誤
				System.out.println("value:" + value);
		}
	}
}

ArrayList後面泛型如果未定義類型,是可以任意添加其他類型的值(是一個Object),放在記憶體後就會忘記他原本的型態,就不會檢查其類型,取出時預設為 Object 類型,此時強制轉型會拋出異常,ClassCastExecption(強轉型錯誤

既然提到了我們就來更認識泛型吧!

泛型(Generic)

泛型的來源、原理

  1. JDK 5 後 Java 才加入泛型,為了向下兼容,而虛擬機是不支持泛型的,所以 Java 為了實現泛型使用了 偽泛型
  2. Java 會在 編譯期間擦除所有的泛型訊息,這樣就可以讓 JVM 使用相同的字節碼而不必新增,在 JVM Runtime 時就不存在所謂的泛型訊息

優點

  1. 如果有定義泛型就不用強制轉換
  2. 對於不同類型的數據處理相同的代碼達到很好的效果 - 泛型如果未定義類型,是可以任意添加其他類型的值(是一個Object),它並不會檢查
  3. 寫程式時會檢查代碼

注意事項

  1. 不能使用基本數據類型 (byte、char、short、int、long、double),因為擦除後會轉換為 Object 類型
  2. 不能使用 instanceof 運算符號,同樣是因為類型擦除後所帶來的副作用,會導致無法判別
  3. 不能使用在static,泛型創建對象時才能確定,但靜態方法、參數,是不用加載就可以使用 (泛型方法則可以,因為泛型方法是呼叫後才加載)
  4. 泛型可能會導致多載方法衝突,同樣是因為擦除後會轉為 Object 類,導致重複定義
  5. 無法創建泛型對象,但可以使用反射創建泛型對象 (因為反射就是 Runtime 運行)
public static <E> void yo1(E e) {
    E elements = new E(); // Error
}

public static <E> void yo2(E e, Class<E> clz) {
    E elements = clz.newInstance();
}

6.Java 中沒有所謂的泛型Array,同樣也是因為擦除,無法判斷是否是同一類型

泛型類、interface、方法

  1. 透過型別參數化來達到集合元素的類型的限制
    泛型的本質是為了參數化類型,也就是指定參數類型,把類型當成引數一樣傳遞
  2. 在泛型的使用中,操作數據類型被指定為一參數,這參數可被用在 Class、interface and method
  3. 泛型允許多個變量(多個參數類型)<T, K, V>,引入一個類型變數 T (其他字母也可以 T, E, V, K 等等) 並用 <> 包住

以下範例 (類型變數用 T 代表),T 為數據類型:

public class GenericClass1<T> {
    private T data;

    private GenericClass1() {
    }

    public GenericClass1(T data) {
        this();    // 呼叫無參構造函數
        this.data = data;
    }

    public T GetData() {
        return data;
    }
}

// 兩個泛型變量 T K...
public class GenericClass2<T, K> {

    private T t;
    private K k;

    public GenericClass2() {
    }

    public void setDataT(T t, K k) {
        this.t = t;
        this.k = k;
    }

    public T getTData() {
        return t;
    }

    public K getKData() {
        return k;
    }
}

Universal Symbol 通配符 ?

有兩種拓展使用方式 extends and super 限制

泛型類繼承 Comment

  • 泛型類繼承
    1. 泛型雖然在字節碼會擦除,但是仍會在檢查的時候用到,只要源型基類相同,就符合泛型類繼承
    2. 如果與基類不同就無法實現泛型類繼承
    3. 讓普通繼承 & 泛型產生關係就要使用通配符

使用

  1. 單單 通配符 ? 不能設定也不能取值目的是為了 類型檢查;作為引數,代表全部類型都可以接受
  2. 通配符 + extends 的功能是:安全的 取得 (get) 數據作為引數,基礎使用 <? extends X>,表示類型的 上限到 X 類,包含 X 類以及其衍生子類
    1. 子類要強制轉型
  3. 通配符 + super 的功能是:安全的 設定 數據作為引數,基礎使用 <? super X>,表示類型的 下限是 X.class,包含 X 類 以及其父類(超類)
    1. 限定符不能使用 super 語法錯誤
    2. 返回 Object 類別,除非強轉否則無法使用指定的子類 Function
public static void test(ArrayList<? extends A> a) {
		System.out.println(a);
}
//?=任何
//任何A或繼承A的子類可以通通放到ArrayList
public static void test2(ArrayList<? super A> a) {
		System.out.println(a);
}
//任何A或A的父類、Object可以通通放到ArrayList

List 集合的實作類別

  1. ArrayList - 封裝了一個動態、允許再分配的Object[]陣列,使用initialCapacity 參數來設置該陣列的長度,提供了快速的隨機存取,但在插入和刪除元素時可能效能較差。是最常見的 List 類
  2. Vector- 類似於 ArrayList, 但它是線程安全的。它在多執行緒環境中使用時非常有用,但在效能上可能較差。(但建議少用)
  3. LinkedList - LinkedList 是一個雙向鏈結串列,它使用節點連接元素。它提供了快速的插入和刪除操作,但訪問元素時效能較差,同時實現了 QueueDeque 介面。這意味著它可以用作通用的 Queue 或雙端佇列(Double-Ended Queue)LinkedList 的特點是它提供了一組專門處理集合中第一個、最後一個元素的方法:
void ensureCapacity(int minCapacity); //讓集合至少可以存minCapacity 個元素

void trimToSize() //釋放未用空間

由於上述的特性, LinkedList 很適合用來實作兩種基本的『資料結構』(Data Structure):即堆疊及佇列。
所謂堆疊 (stack) 是指一種後進先出 (LIFO, Last In First Out) 的資料結構, 加入此結構 (集合) 的物件要被取出時, 必須等其它比它後加入的物件全部被拿出來後, 才能將它拿出來。

至於佇列 (queue) 則是一種『先進先出』 (FIFO) 的資料結構, 像日常生活中常見的排隊購物, 此隊伍就是個先進先出的佇列

Queue

遵循先進先出(FIFO)的原則,Queue 常用來處理需要按照特定順序處理的任務或資料。

  • void add(Object e):將指定元素加入此佇列的尾部。
  • Obiect element() :獲取佇列首部的元素,但是不刪除該元素。
  • boolean offer(Object e):將指定元素加入此佇列的尾部。當使用有容量限制的佇
    列時,此方法通常比add(Obiect e)方法更好。
  • Object peek():獲取佇列首部的元素,但是不刪除該元素。如果此佇列為空,則
    返回null。
  • Object poll():獲取佇列首部的元素,並刪除該元素。如果此佇列為空,則返回
    null
  • Object remove():獲取佇列首部的元素,並刪除該元素。

Queue 集合的實作類別

  1. PriorityQueue - 是一個優先級佇列,元素根據它們的優先順序進行處理。通常,具有更高優先級的元素會優先處理
  2. Deque - Queue的子介面,代表二端佇列的資料集合,它定義了有關操作二端元素的方法,其實作子類有: ArrayDeque。

https://ithelp.ithome.com.tw/upload/images/20231014/20163139dPnLHY5GLU.png

https://ithelp.ithome.com.tw/upload/images/20231014/20163139zKDW56uYwj.png

https://ithelp.ithome.com.tw/upload/images/20231014/20163139J1qIssbAuL.png

明天最後一天拉~今天先到這邊!


上一篇
Day 28 集合泛型 I
下一篇
Day 30 集合泛型 III
系列文
玩轉Java:從基礎打造你的程式超能力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言