Java 中的泛型 集合(List,Set) Map

ihav2carryon發表於2024-07-09

泛型 集合(List,Set) Map

泛型

泛型的本質是引數化型別,即允許在編譯時對集合進行型別檢查,從而避免安全問題,提高程式碼的複用性

泛型的具體定義與作用

  • 定義:泛型是一種在編譯階段進行型別檢查的機制,它允許在類,方法,介面後透過<> 來宣告型別引數.這些引數在編譯時會被具體的型別替換.java在執行時,會透過型別擦除機制,將泛型型別擦除,變為原始型別(如,String,Integer),具體的例子將在”泛型的使用”中演示
  • 作用:
  1. 型別安全:透過泛型,在編譯階段可以檢查到更多的型別錯誤,就不用再執行時丟擲ClassCastException
  2. 消除強制轉化:在使用了泛型之後,很多的型別轉換都可以自動執行,減少了程式碼中的顯性強制轉換
  3. 提高了程式碼的複用性

泛型的使用

  • 泛型類的使用:在類名後新增引數宣告部分(用<> 包括起來),如 class Box<T>
public class Box<T>
{
    private  T value;//定義泛型值
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();//例項化String類的泛型物件
        stringBox.setValue("這是一個String型別正規化");
        System.out.println(stringBox.getValue());
        Box<Integer> integerBox = new Box<>();//例項化Integer類的泛型物件
        integerBox.setValue(123);
        System.out.println(integerBox.getValue());
    }
}
  • 泛型類的使用:與泛型類類似,在介面定義後新增類似引數宣告.如interface<K,V>
public interface Pair <K,V>{  //泛型介面
    K getKey();  //Pair 介面定義了兩個抽象方法 型別分別為 K,V
    V getValue(); //K,V都是待定義的型別
}
public class SimplePair<K,V> implements Pair<K,V>{
    private K key;
    private V value;
             //SimplePair類實現了 Pair介面 必須從寫其中的抽象方法
    public SimplePair(K key, V value) {
        this.key = key;    //SimplePair的建構函式
        this.value = value;
    }
    @Override
    public K getKey() {
        return key;
    }
    @Override
    public V getValue() {
        return value;
    }
    public static void main(String[] args) {
        //例項化 一個SimplePair物件 傳入引數為 Integer,String
        SimplePair<Integer, String> integerStringSimplePair = new SimplePair<Integer, String>(1,"one");
        //則K就代表Integer V就代表String
        System.out.println(integerStringSimplePair.getKey());
        System.out.println(integerStringSimplePair.getValue());
    }
}
  • 泛型方法:在方法返回型別前宣告型別引數,如public <T> void printArray(T[] inputArray)。泛型方法可以定義在泛型類中,也可以定義在非泛型類中。
public class Method {
    public static <T> void printArray(T[] inputArray){ //定義了一個返回值為<T>的泛型函式
        for (T element : inputArray) {
            System.out.println(element);
        }
    }
    public static void main(String[] args) {
        Integer [] integers={1,2,3,4,5};
        String [] strings={"abcdefg"};
        printArray(integers);//呼叫泛型函式
        printArray(strings);
    }
}
  • 型別通配識別符號:使用? 表示型別實參,表示不確定的型別(或者是待定的型別),通常用於泛型方法,泛型類,泛型介面;通常應用於當你不確定該資料型別時
 public static void printElements(List<?> list)//假設你要寫一個列印集合元素的方法
 //當你不確定該集合的型別 則可以使用萬用字元
    {
        for (Object o : list) {
            System.out.println(o);
        }
    }
    public static void main(String[] args) {
        List<String> stringList=new ArrayList<>();
        List<Integer> integerList=new ArrayList<>();
        printElements(stringList);//可以列印String
        printElements(integerList);//也可以列印Integer
    }
  • 上限萬用字元:上限萬用字元用於知道一個型別的上限,它允許你指定一個型別及其子型別,其使用格式為<?extends Type >
List<? extends Number> listOfNum=new ArrayList<Integer>();//使用Integer是合法的
        //因為Number的子類包括 Integer Double 等等
        listOfNum.add(null);//也是合法的null屬於一切型別

在這個例子中,List<? extends Number>表示列表可以持有Number型別或其子型別(如IntegerDouble等)的物件,但你不能往這個列表中新增除了null之外的任何元素,因為編譯器不知道列表的確切型別

泛型中常見的型別引數

  • T:表示任意型別,是Type的縮寫,常用於泛型類,方法,介面中
  • K,V:分別表示鍵(key)和值(value),常用於鍵值對中,如Map<K,V>
  • E:表示元素(Element),常用於集合中如List<E>
  • N:表示數字(Number),常用於數字型別
  • S, U, V等:用於表示第二、第三、第四個泛型型別引數,這些字母在程式碼中的使用已成為一種約定俗成的規範

集合

java中,集合框架是一組介面和類的集合,他們提供了一種資料儲存和操作的方式.java的集合框架主要包括兩大介面CollectionMap

Collection介面

  • Collection是所有單列集合的根介面,其子介面包括List,Set,Queue

    java.util.Collection下的介面和繼承類關係簡易結構圖:

java.util.Map下的介面和繼承類關係簡易結構圖:

List介面

List集合也被稱為序列,其允許有重複的元素.List介面的實現類主要有ArrayList, LinkedList Vector

ArrayList

底層使用陣列實現,不是執行緒安全,查詢速度塊,但插入速度慢

 public static void main(String[] args) {
       //建立ArrayList物件
        List<String> list=new ArrayList<>();
        //使用add()方法向陣列中新增元素
        list.add("張三");
        list.add("李四");
        list.add("王五");
        //使用get(index)方法獲取陣列下標為index的元素
        System.out.println(list.get(0));
        //list的增強for迴圈
        for (String s : list) {
            System.out.println(s);
        }
    }

LinkArray

底層使用雙向連結串列實現,查詢速度慢,但其增刪速度快,使用方法與ArrayList基本一致

    public static void main(String[] args) {
        List<String> list=new LinkedList<>(); //建立LinkedList物件
        list.add("張三");//一下的使用方法與ArrayList一致
        list.add("李四");
        list.add("王五");
        System.out.println(list.get(0));
        for (String s : list) {
            System.out.println(s);
        }

Vector

底層與ArrayList一致都是使用陣列實現的,執行緒安全性高,但效率較低

public static void main(String[] args) {
        List<String> list=new Vector<>();//建立Vector物件
        list.add("張三");
        list.add("李四");
        list.add("王五");
        System.out.println(list.get(0));
        for (String s : list) {
            System.out.println(s);
        }
    }

Set介面

其特點為無序集合,不允許有重複元素,包括主要實現類HashSet,LinkedSetTreeSet

HashSet

作為較為常用的Set集合,其底層是基於雜湊表實現的,這就決定了其無法新增重複的元素和無序性

  • HashSet之所以能保證元素的唯一性是重寫了其hashCode()方法和equals()方法,具體操作為:
  1. HashSet在每次儲存元素的過程都會首先檢視其hashCode()值,看其雜湊值是否與以存入HashSet的元素的雜湊值一致,若不一致則直接存入集合,若一致則進行步驟2
  2. 如果其雜湊值相同則繼續呼叫元素的equals()方法與雜湊值相同的元素進行依次比較,若返回值為ture,則說明重複則不新增,反之則新增
  • 無序性:HashSet 是基於雜湊表實現的,因此在新增元素時,不會按照其新增的順序排放,而是根據雜湊表原理,透過hash值存放.
  • 遍歷無需性:當使用迭代器或者增強for迴圈時,HashSet的遍歷也不是按照其元素插入的順序執行的,也不是按照任何可預測的順序執行的,而是基於雜湊表的內部結構決定的,則意味著對於相同的HashSet ,在不同的JVM和實現方法下其遍歷順序都是不同的
 HashSet<Integer> integerHashSet = new HashSet<>(); //建立HashSet物件
        integerHashSet.add(1);
        integerHashSet.add(1);//使用add方法向其插入元素
        integerHashSet.add(2);
        integerHashSet.add(-1);
        for (Integer integer : integerHashSet) {
            System.out.println(integer);
        } //列印結果為 -1 1 2

LinkedHashSet

作為HashSet的子類,繼承了HashSet的所有特性,即不允許集合中有重複元素,但與HashSet不同的是LinkedHashSet內部維護了一個雙向連結串列,用於實現按元素的插入的順序實現遍歷

  • 底層資料邏輯:LinkedHashSet底層的資料結構包括一個陣列和一個雙向連結串列(或者是紅黑樹),這個陣列和雙向連結串列(或者紅黑樹)共同構成了LinkedHashMap (本文將在下文講解到),的實現基礎,LinkedHashSet就是透過封裝LinkedHashMap來實現其功能,即底層是基於LinkedHashMap實現的
  • 具體實現: LinkedHashSet,在新增元素時,都會呼叫LinkedHashMapput方法來實現.LinkedHashMap 的put方法首先會計算插入元素的雜湊值,並根據雜湊值確定元素在陣列中的位置,然後,會在雙向連結串列(或紅黑樹)新增一個節點,儲存元素值,因此每次遍歷*LinkedHashSet時實際上是遍歷其雙向連結串列(紅黑樹)*,從而保證了遍歷順序與元素插入順序一致
 LinkedHashSet<Integer> integerLinkedHashSet = new LinkedHashSet<>();
 //建立一個LinkedHashSet物件
        integerLinkedHashSet.add(1);
        integerLinkedHashSet.add(1);//新增元素
        integerLinkedHashSet.add(2);
        integerLinkedHashSet.add(-1);
        for (Integer integer : integerLinkedHashSet) {
            System.out.println(integer);
        }//列印結果與插入順序一致 1 2 -1

TreeSet

TreeSet 是Set的子類,因此也保留的Set介面的特性,特別的是TreeSet是基於紅黑樹實現的

  • 底層資料邏輯:TreeSet 的底層實際上是基於TreeMap 作為底層儲存實現的,TreeSet內部維護了一個NavigableMap (實際上就是TreeMap的一個例項化物件),用於儲存元素,在這個對映中,鍵(key)就是TreeSet中的元素,而值(value)是一個固定的關係共享的Object物件,(在TreeSet中,這個Object物件被命名為PRESENT),用於表現值的存在性,不儲存特點的值.

以下是TreeSet內部程式碼結構:

  • TreeSet的排序機制:

TreeSet元素預設是根據自然順序或根據指定的Comparator進行排序,如果沒有提供Comparator則,TreeSet會按照元素自然排序;如果提供了Comparator則使用Comparator來確定元素的順序

public class NumComparator implements Comparator<Integer> {
//NumComparator類實現了Comparator介面
    @Override//重寫了compare方法
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1,o2);
    }
} 
   TreeSet<Integer> integerTreeSet = new TreeSet<>(new NumComparator());
   //傳入NumComparator物件表明該TreeSet以該方式排序元素
        integerTreeSet.add(1);//新增元素
        integerTreeSet.add(-1);
        integerTreeSet.add(2);
        for (Integer integer : integerTreeSet) {
            System.out.println(integer);
        }列印結果為[-1,1,2]

List與Set的常用方法

返回值型別 方法和描述
boolean add(E e) 將指定的元素追加到此列表的末尾。
void add(int index, E element) 在此列表中的指定位置插入指定的元素。
boolean addAll(Collection<? extends E> c) 按指定集合的Iterator返回的順序將指定集合中的所有元素追加到此列表的末尾。
boolean addAll(int index, Collection<? extends E> c) 將指定集合中的所有元素插入到此列表中,從指定的位置開始。
void clear() 從列表中刪除所有元素。
boolean contains(Object o) 如果此列表包含指定的元素,則返回 true 。
E get(int index) 返回此列表中指定位置的元素。
int indexOf(Object o) 返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-1。
boolean isEmpty() 如果此列表不包含元素,則返回 true 。
Iterator iterator() 以正確的順序返回該列表中的元素的迭代器。
int lastIndexOf(Object o) 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。
E remove(int index) 刪除該列表中指定位置的元素。
boolean remove(Object o) 從列表中刪除指定元素的第一個出現(如果存在)。
boolean removeAll(Collection<?> c) 從此列表中刪除指定集合中包含的所有元素。
E set(int index, E element) 用指定的元素替換此列表中指定位置的元素。
int size() 返回此列表中的元素數。
Object[] toArray() 以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的陣列。

Map(字典)

Map是一種將鍵(key)對映到值(value)的物件,它提供了一種鍵值對的儲存機制,其中每個鍵都唯一對映到一個值,這種結構有利於快速查詢,插入和刪除值

Map的儲存結構:

HashMap

HashMap是基於雜湊表實現的,它允許使用null鍵和null值,HashMap不保證對映的順序,即遍歷Map時元素的順序可能與插入順序不同,HashMap底層主要維護一個陣列和一個連結串列

  • HashMap的底層原理:
  1. HashMap底層維護了一個陣列,被稱為”桶”,用來儲存多個鍵值對,沒有指定初始量時,陣列預設長度是16
  2. 當插入資料時兩個不同的鍵產生了雜湊衝突,這時就會透過HashMap底層維護的連結串列來解決雜湊衝突
 HashMap<Integer, String> integerStringHashMap = new HashMap<>();//建立HashMap物件
        integerStringHashMap.put(1,"one");//Map使用put新增元素
        integerStringHashMap.put(-1,"-one");
        integerStringHashMap.put(2,"two");
        for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }//對於Map有特殊的遍歷方式,本文將會在下文解析
        //輸出[-1 -one,1 one,2 two]

TreeMap

TreeMap是基於紅黑樹實現的Map介面,基於這種資料結構讓TreeMap 可以在log(n)時間複雜度完成containsKey、get、put和remove等操作.TreeMap是實現TreeSet的基礎

  • 有序性:由於基於紅黑樹實現儲存,則保證了TreeMap是有序的,這種有序可以是自然順序(即插入順序),或者可以根據指定Comparator實現
TreeMap<Integer, String> integerStringHashMap = new TreeMap<>();//建立TreeMap物件
        integerStringHashMap.put(1,"one");//Map使用put新增元素
        integerStringHashMap.put(-1,"-one");
        integerStringHashMap.put(2,"two");
        for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }//對於Map有特殊的遍歷方式,本文將會在下文解析
        //輸出[-1 -one,1 one,2 two]

HashTable

HashTable底層原理與HashMap十分相似,但與HashMap相比HashTable的put,get,remove 加上了同步塊,和使用了this鎖,則使得HashTable執行緒是安全的,但效能較低

  • 鍵和值的約束:HashTable是不允許使用null作為鍵和值的,否則會報出NullPointerException 異常
      HashMap<Integer, String> integerStringHashMap = new HashMap<>();//建立HashMap物件
        integerStringHashMap.put(1,"one");//Map使用put新增元素
        integerStringHashMap.put(-1,"-one");
        integerStringHashMap.put(2,"two");
        for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }//對於Map有特殊的遍歷方式,本文將會在下文解析
        //輸出[-1 -one,1 one,2 two]

LinkedHashMap

LinkedHashMap繼承了HashMap ,Linked的內部維護了一個雙向連結串列用於保證元素的順序

  • LinkedHashMap內部結構:其內部結合了雜湊表和雙向連結串列兩種資料結構,雜湊表用於快速檢索元素,雙向連結串列用於維護元素的順序
  • 插入和訪問:當元素被插入LinkedHashMap時,會在連結串列的尾部新增一個新的節點。如果設定了按訪問順序排列(透過建構函式或setAccessOrder方法),則每次訪問元素時,會將該節點移動到連結串列的尾部,以保持訪問順序

        LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件
        integerStringHashMap.put(1,"one");//Map使用put新增元素
        integerStringHashMap.put(-1,"-one");
        integerStringHashMap.put(2,"two");
        for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }//對於Map有特殊的遍歷方式,本文將會在下文解析
        //輸出[1 one,-1 -one,2 two]

Map的遍歷方式

由於Map資料結構的特性,(使用鍵值對),因此必須指定要遍歷的條件,例如按鍵或按值遍歷等等

  • 使用entrySet()和增強for迴圈:

    透過entrySet()方法,Map可以被轉換為一個包含Map.Entry物件的Set集合,其中每個Map.Entry物件都代表Map中的一個鍵值對。然後,可以使用增強for迴圈來遍歷這個Set集合

      LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件
            for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
                System.out.println(entry.getKey()+" "+entry.getValue());
            }
    
  • 使用KeySet()和增強for迴圈:

    如果只對Map的鍵感興趣,可以使用keySet()方法獲取一個包含Map中所有鍵的Set集合,然後遍歷這個集合。如果需要獲取對應的值,可以透過鍵來從Map中獲取。

     LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();
            for (Integer integer : integerStringHashMap.keySet()) {//其中integer表示Map的鍵值
            //透過Map方法的get(key)方法返回的是透過key對映的value
                System.out.println(integer+integerStringHashMap.get(integer));
            }
    
    
  • 使用values()和增強for迴圈:

    KeySet()方法同理,如果只對Map的值感興趣,可以使用values()方法獲取一個包含Map中所有值的Collection集合,然後遍歷這個集合。但請注意,這種方式無法直接獲取到對應的鍵。只能獲取其value值

     LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件
            for (String value : integerStringHashMap.values()) {
                System.out.println(value);
            }
    
  • 使用entrySet()Iterator迭代器

    使用 entrySet() 方法結合 Iterator 迭代器來遍歷 Map 中的鍵值對是一種常見的做法,尤其當需要同時訪問鍵和值時,整體是透過while迴圈實現的

    • 在使用前必須使用interator()方法構建一個interator 物件,並且需要透過 IteratorhasNext() 方法檢查是否還有下一個元素。

    • 使用 Iteratornext() 方法獲取下一個 Map.Entry 物件,從 Map.Entry 物件中使用 getKey()getValue() 方法分別獲取鍵和值。

              LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();
        Iterator<Map.Entry<Integer, String>> iterator = integerStringHashMap.entrySet().iterator();
        //使用interator()建立一個intertor物件這步其實為聯合方法可以分為一下兩步
               while (iterator.hasNext())
               {
                   Map.Entry<Integer, String> entry = iterator.next();
                   //每次透過next()方法獲取entries的下一個實體 儲存再entry中
                   Integer key=entry.getKey();//使用迭代器的getKey()方法可以獲取鍵
                   String value=entry.getValue();//getValue()方法可以獲取值
                   System.out.println(key+value);
               }
      

      Iterator<Map.Entry<Integer, String>> iterator =integerStringHashMap.entrySet().iterator();
      //使用interator()建立一個intertor物件這步其實為聯合方法可以分為一下兩步

      1. 先使用entrySet()方法建立一個Set集合:

      Set<Map.Entry<Integer, String>> entries = integerStringHashMap.entrySet();

      其中Map.Entry<>表示Map中的一個實體

      1. 再使用interator()構造一個interator物件

      Iterator<Map.Entry<Integer, String>> iterator = entries. Iterator();

相關文章