一文看懂Java集合(詳細)

zhkey123發表於2020-12-09

一、陣列與集合

  1. 集合、陣列都是對多個資料進行儲存操作的結構,簡稱Java容器
    說明:此時的儲存,主要指的是記憶體層面的儲存,不涉及到持久化的儲存(.txt,.jpg,.avi,資料庫中)

  2. 陣列在儲存多個資料方面

    1. 特點:
      一旦初始化以後,其長度就確定了。
      陣列一旦定義好,其元素的型別也就確定了。我們也就只能操作指定型別的資料了。比如 :String[] arr;int[] arr1;Object[] arr2;

    2. 缺點
      一旦初始化以後,其長度就不可修改
      陣列中提供的方法非常有限,對於新增、刪除、插入資料等操作,非常不便,同時效率不高。
      獲取陣列中實際元素的個數的需求,陣列沒有現成的屬性或方法可用
      陣列儲存資料的特點:有序、可重複。對於無序、不可重複的需求,不能滿足。

  3. 集合儲存的優點:解決陣列儲存資料方面的弊端。

二、Collection介面

  1. 單例集合框架
    Collection介面:單列集合,用來儲存一個一個的物件

    1. List介面:儲存有序的、可重複的資料。---->“動態”陣列
      常用實現類:ArrayList、LinkedList、Vector

    2. Set介面:儲存無序的、不可重複的資料。 ---->數學中的“集合”
      常用實現類:HashSet、LinkedHashSet、TreeSet

    對應圖示:
    在這裡插入圖片描述

  2. Collection介面中常用方法

    1. add(Object e):將元素e新增到集合coll中

    2. addAll(Collection coll1):將coll1集合中的元素新增到當前的集合中

    3. size():獲取新增的元素的個數

    4. clear():清空集合元素

    5. isEmpty():判斷當前集合是否為空

      
      @Test
      public void test1() {
          Collection coll = new ArrayList();
      
          //1. add(Object e):將元素e新增到集合coll中
          coll.add("AA");
          coll.add("BB");
          coll.add(123);//自動裝箱
          coll.add(new Date());
      
          //2. size():獲取新增的元素的個數
          System.out.println(coll.size());//4
      
          //3. addAll(Collection coll1):將coll1集合中的元素新增到當前的集合中
          Collection coll1 = new ArrayList();
          coll1.add(456);
          coll1.add("CC");
          coll.addAll(coll1);
      
          System.out.println(coll.size());//6
          System.out.println(coll);//[AA, BB, 123, Fri Dec 04 21:10:12 CST 2020, 456, CC]
      
          //4. clear():清空集合元素
          coll.clear();
      
          //5. isEmpty():判斷當前集合是否為空
          System.out.println(coll.isEmpty());//true
      
      }
      
    6. contains(Object obj):判斷當前集合中是否包含obj,我們在判斷是會呼叫obj物件所在類的equals()

    7. containsAll(Collection coll1):判斷形參coll1中的所有元素是否都存在於當前集合中

      @Test
      public void test2(){
          Collection coll = new ArrayList();
          coll.add(123);
          coll.add(456);
          coll.add(new String("Tom"));
          coll.add(false);
          coll.add(new Person("Jerry",20));
      
          //6. contains(Object obj):判斷當前集合中是否包含obj,我們在判斷是會呼叫obj物件所在類的equals()
          boolean contains = coll.contains(123);
          System.out.println(contains);//true
          System.out.println(coll.contains(new String("Tom")));//true
          System.out.println(coll.contains(new Person("Jerry",20)));//false,要使它變成true,在Person中重寫equals()方法
      
          //7. containsAll(Collection coll1):判斷形參coll1中的所有元素是否都存在於當前集合中
          Collection coll1 = Arrays.asList(123,456);
          System.out.println(coll.containsAll(coll1));//true
      
      }
      
    8. remove(Object obj):從當前集合中移除obj元素。移除成功返回true,移除失敗返回false。

    9. removeAll(Collection coll1):差集:從當前集合中移除coll1中所有的元素

      @Test
      public void test3(){
      	//8. remove(Object obj):從當前集合中移除obj元素。移除成功返回true,移除失敗返回false。
      	Collection coll = new ArrayList();
      	coll.add(123);
      	coll.add(456);
      	coll.add(new String("Tom"));
      	coll.add(false);
      	coll.add(new Person("Jerry",20));
      	
      	coll.remove(1234);
      	System.out.println(coll);//[123, 456, Tom, false, Person{name='Jerry', age=20}]
      	
      	coll.remove(new Person("Jerry",20));
      	System.out.println(coll);//[123, 456, Tom, false]
      	
      	//9. removeAll(Collection coll1):差集:從當前集合中移除coll1中所有的元素
      	Collection coll1 = Arrays.asList(123,4567);
      	coll.removeAll(coll1);
      	System.out.println(coll);//[456, Tom, false]
      
      }
      
    10. retainAll(Collection coll1):交集:獲取當前集合和coll1集合的交集,並返回給當前集合

    11. equals(Object obj):要想返回true,需要當前集合和形參集合的元素都相同

      @Test
      public void test4(){
          Collection coll = new ArrayList();
          coll.add(123);
          coll.add(456);
          coll.add(new String("Tom"));
          coll.add(false);
          coll.add(new Person("Jerry",20));
      
          //10. retainAll(Collection coll1):交集:獲取當前集合和coll1集合的交集,並返回給當前集合
      //        Collection coll1 = Arrays.asList(123,456,789);
      //        coll.retainAll(coll1);
      //        System.out.println(coll);//[123, 456]
      
          //11. equals(Object obj):要想返回true,需要當前集合和形參集合的元素都相同
          Collection coll1 = new ArrayList();
          coll1.add(123);
          coll1.add(456);
          coll1.add(new String("Tom"));
          coll1.add(false);
          coll1.add(new Person("Jerry",20));
      
          System.out.println(coll.equals(coll1));//true
      
      }
      
    12. hashCode():返回當前物件的雜湊值

    13. 集合 —> 陣列:toArray()
      擴充:陣列 —>集合:呼叫Arrays類的靜態方法asList(T … t),程式碼如下

    14. iterator():返回Iterator介面的例項,用於遍歷集合元素

      @Test
      public void test5(){
          Collection coll = new ArrayList();
          coll.add(123);
          coll.add(456);
          coll.add(new String("Tom"));
          coll.add(false);
          coll.add(new Person("Jerry",20));
      
          //12. hashCode():返回當前物件的雜湊值
          System.out.println(coll.hashCode());//1560300821
      
          //13. 集合 ---> 陣列:toArray()
          Object[] arr = coll.toArray();
          for (int i = 0; i < arr.length; i++) {
              System.out.println(arr[i]);
      //            123
      //            456
      //            Tom
      //            false
      //            Person{name='Jerry', age=20}
          }
          //擴充:陣列 ---> 集合:呼叫Arrays類的靜態方法asList(T ... t)
          List<String> strings = Arrays.asList("AA", "BB", "CC");
          System.out.println(strings);//[AA, BB, CC]
          //注意細節
          List arr1 = Arrays.asList(new int[]{123, 456});
          System.out.println(arr1.size());//1
          List arr2 = Arrays.asList(new Integer[]{123, 456});
          System.out.println(arr2.size());//2
      
          //14. iterator():返回Iterator介面的例項,用於遍歷集合元素。放在IteratorTest中測試
      }
      

注: 使用Collection集合儲存物件,要求物件所屬的類滿足:向Collection介面的實現類的物件中新增資料obj時,要求obj所在類要重寫equals()

三、Iterator介面和foreach迴圈

  1. 遍歷Collection的兩種方式:
    ① 使用迭代器Iterator
    ② foreach迴圈(或增強for迴圈)

  2. java.utils包下定義的迭代器介面:Iterator

    1. 作用:遍歷集合Collectiton元素

    2. 內部的方法:hasNext() 和 nest()

    3. 如何讓獲取例項:coll.iterator()返回一個迭代器例項

    4. 遍歷程式碼實現:

      Iterator iterator = coll.iterator();
      //hasNext():判斷是否還有下一個元素
      while (iter.hasNext()) {
          //next():①指標下移 ②將下移以後集合位置上的元素返回
          System.out.println(iter.next());
      }
      
    5. 集合物件每次呼叫iterator()方法都得到一個全新的迭代器物件,預設遊標都在集合的第一個元素之前,圖示如下
      在這裡插入圖片描述

    6. 內部定義了remove(),可以在遍歷時,刪除集合中的元素。此方法不同於集合直接呼叫remove()

      //測試Iterator中的remove()
      //如果還未呼叫next()或在上一次呼叫 next 方法之後已經呼叫了 remove 方法,再呼叫remove都會報IllegalStateException。
      //內部定義了remove(),可以在遍歷的時候,刪除集合中的元素。此方法不同於集合直接呼叫remove()
      @Test
      public void test3(){
          Collection coll = new ArrayList();
          coll.add(123);
          coll.add(456);
          coll.add(new Person("Jerry",20));
          coll.add(new String("Tom"));
          coll.add(false);
      
          //刪除集合中"Tom"
          Iterator iterator = coll.iterator();
          while (iterator.hasNext()){
      //            iterator.remove();
              Object obj = iterator.next();
              if("Tom".equals(obj)){
                  iterator.remove();
      //                iterator.remove();
              }
      
          }
          //遍歷集合
          iterator = coll.iterator();
          while (iterator.hasNext()){
              System.out.println(iterator.next());
          }
      }
      
  3. jdk5.0新特性–增強for迴圈:(foreach迴圈)

    1. 遍歷集合舉例:內部仍然使用了迭代器
      @Test
      public void test1(){
          Collection coll = new ArrayList();
          coll.add(123);
          coll.add(456);
          coll.add(new Person("Jerry",20));
          coll.add(new String("Tom"));
          coll.add(false);
      
          //for(集合元素的型別 區域性變數 : 集合物件)
          
          for(Object obj : coll){
              System.out.println(obj);
          }
      }
      
    2. 遍歷陣列舉例:
      @Test
      public void test2(){
          int[] arr = new int[]{1,2,3,4,5,6};
          //for(陣列元素的型別 區域性變數 : 陣列物件)
          for(int i : arr){
              System.out.println(i);
          }
      }
      

四、Collection子介面:List介面

  1. 儲存的資料特點:儲存有序的、可重複的資料。

  2. 常用實現類:

    1. ArrayList:作為List介面的主要實現類;執行緒不安全的,效率高;底層使用Object[] elementData儲存
    2. LinkedList:對於頻繁的插入刪除操作,使用此類效率比ArrayList高;底層使用雙向連結串列儲存
    3. Vector:作為List介面的古老實現類;執行緒安全的,效率低;底層使用Object[] elementData儲存
  3. 原始碼分析(難點)

    1. ArrayList的原始碼分析:

      1. jdk 7情況下
        ArrayList list = new ArrayList();//底層建立了長度是10的Object[]陣列elementData
        list.add(123);//elementData[0] = new Integer(123);

        list.add(11);//如果此次的新增導致底層elementData陣列容量不夠,則擴容。
        預設情況下,擴容為原來容量的1.5倍,同時需要將原有陣列中的資料複製到新陣列中
        結論:建議開發中使用帶參的構造器:ArrayList list = new ArrayList(int capacity)

      2. jdk 8中ArrayList的變化:
        ArrayList list = new ArrayList();//底層Object[] elementData初始化為{}.並沒建立長度為 10的陣列
        list.add(123);//第一次呼叫add()時,底層才建立了長度10的陣列,並將資料123新增到elementData[0]

        後續的新增和擴容操作與jdk 7 無異。

      3. 小結:jdk7中的ArrayList的物件的建立類似於單例的餓漢式,而jdk8中的ArrayList的物件的建立類似於單例的懶漢式,延遲了陣列的建立,節省記憶體。

    2. LinkedList的原始碼分析:
      LinkedList list = new LinkedList();//內部宣告瞭Node型別的first和last屬性,預設值為null
      list.add(123);//將123封裝到Node中,建立了Node物件
      其中,Node定義為:體現了LinkedList雙向連結串列的說法

      private static class Node<E> {
      	E item;
      	Node<E> next;
      	Node<E> prev;
      	
      	Node(Node<E> prev, E element, Node<E> next) {
      		this.item = element;
      		this.next = next;
      		this.prev = prev;
      	}
      }
      
    3. Vector的原始碼分析:
      jdk7和jdk8中通過Vector()構造器建立物件時,底層都建立了長度為10的陣列。
      在擴容方面,預設擴容為原來的陣列長度的2倍。

  4. List介面中的常用方法

    1. void add(int index, Object ele):在index位置插入ele元素

      	ArrayList list = new ArrayList();
          list.add(123);
          list.add(456);
          list.add("AA");
          list.add(new Person("Tom",12));
          list.add(456);
      
          System.out.println(list);//[123, 456, AA, Person{name='Tom', age=12}, 456]
      
          //void add(int index, Object ele):在index位置插入ele元素
          list.add(1,"BB");
          System.out.println(list);//[123, BB, 456, AA, Person{name='Tom', age=12}, 456]
      
    2. boolean addAll(int index, Collection eles):從index位置開始將eles中的所有元素新增進來

      	//boolean addAll(int index, Collection eles):從index位置開始將eles中的所有元素新增進來
      	List list1 = Arrays.asList(1,2,3);
      	list.addAll(list1);
      	System.out.println(list.size());//9
      	System.out.println(list);//[123, BB, 456, AA, Person{name='Tom', age=12}, 456, 1, 2, 3]
      
    3. Object get(int index):獲取指定index位置的元素

       	//Object get(int index):獲取指定index位置的元素
      	System.out.println(list.get(3));//3
      
    4. int indexOf(Object obj):返回obj在集合中首次出現的位置

      	//int indexOf(Object obj):返回obj在集合中首次出現的位置,如果不存在返回-1
      	System.out.println(list.indexOf(456));//2
      	System.out.println(list.indexOf(789));//-1
      
      
    5. int lastIndexOf(Object obj):返回obj在當前集合中末次出現的位置

      	//int lastIndexOf(Object obj):返回obj在當前集合中末次出現的位置
      	System.out.println(list.lastIndexOf(456));//5
      
    6. Object remove(int index):移除指定index位置的元素,並返回此元素

      	// Object remove(int index):移除指定index位置的元素,並返回此元素
      	Object re = list.remove(1);
      	System.out.println(re);//BB
      	System.out.println(list);//[123, 456, AA, Person{name='Tom', age=12}, 456, 1, 2, 3]
      
      
    7. Object set(int index, Object ele):設定指定index位置的元素為ele

      	// Object set(int index, Object ele):設定指定index位置的元素為ele.返回被替換的元素
      	Object re1 = list.set(1, "CC");
      	System.out.println(re1);//456
      	System.out.println(list);//[123, CC, AA, Person{name='Tom', age=12}, 456, 1, 2, 3]
      
      
    8. List subList(int fromIndex, int toIndex):返回從fromIndex到toIndex位置的子集合

      	//List subList(int fromIndex, int toIndex):返回從fromIndex到toIndex位置的子集合,
      	// 左閉右開,不包含toIndex位置,不會改變原有集合;
      	System.out.println(list.subList(3, 5));//[Person{name='Tom', age=12}, 456]
      	System.out.println(list);//[123, CC, AA, Person{name='Tom', age=12}, 456, 1, 2, 3]
      

      總結:常用方法
      增:add(Object obj)
      刪:remove(int index) / remove(Object obj)
      改:set(int index,Object obj)
      查:get(int index)
      插:add(int index,Object obj),add(int index,Collection coll)
      長度:size()
      遍歷:① Iterator迭代器方式
      ② 增強for迴圈:foreach
      ③ 普通的迴圈

      @Test
          public void test2(){
              ArrayList list = new ArrayList();
              list.add(123);
              list.add(456);
              list.add("AA");
      
              //方式一:Iterator迭代器方式
              Iterator iterator = list.iterator();
              while (iterator.hasNext()) {
                  System.out.println(iterator.next());
              }
      
              System.out.println("*****************************");
      
              //方式二:增強for迴圈
              for (Object o: list) {
                  System.out.println(o);
              }
      
              System.out.println("*****************************");
      
              //方式三:普通for迴圈
              for (int i = 0; i < list.size(); i++) {
                  System.out.println(list.get(i));
              }
          }
      
       [面試題]
       *  面試題:ArrayList、LinkedList、Vector者的異同?
       *  同:三個類都是實現了List介面,儲存資料的特點相同:儲存序的、可重複的資料
       *  不同:見上(第2部分+第3部分)
      

五、Collection子介面:Set介面

  1. 儲存的資料特點無序的不可重複的元素
    以HashSet為例說明
    1. 無序的:不等於隨機性。儲存的資料在底層陣列中並非按照陣列索引的順序新增,而是根據資料的雜湊值新增
    2. 不可重複的:保證新增的元素按照equals()判斷時,不能返回true。即:相同的元素只能新增一個

  2. 元素新增過程: (以HashSet為例)

    我們向HashSet中新增元素a,首先呼叫元素a所在類的hashCode()方法,計算元素a的雜湊值,
    此雜湊值通過某種雜湊函式計算出在HashSet底層陣列中的存放位置(即為:索引位置),判斷
    陣列此位置上是否已經有元素:
    	如果此位置上沒有其他元素,則元素a新增成功。---->情況1
    	如果此位置上有其他元素b(或以連結串列形式存在的多個元素),則比較元素a與元素b的hash值:
    		如果hash值不同,則元素a新增成功。---->情況2
            如果hash值相同,進而需要呼叫元素a所在類的equals()方法:
    			equals()返回true,元素a新增失敗
                equals()返回false,則元素a新增成功。---->情況3
    
    對於新增成功的情況2和情況3而言:元素a與已經存在指定索引位置上資料一連結串列的方式儲存。
    jak 7:新新增的元素放到陣列中,指向原來的元素
    jdk 8:原來的元素在陣列中,指向新新增的元素
    
    HashSet底層:陣列+連結串列的結構(前提:jdk7)
    
  3. 常用方法
    Set介面中沒額外定義新的方法,使用的都是Collection中宣告過的方法。

  4. 常用實現類:

    1. HashSet:作為Set介面的主要實現類:執行緒不安全的;可以儲存null值

      @Test
      public void test1(){
          Set set = new HashSet();
          set.add(456);
          set.add(123);
          set.add("AA");
          set.add("CC");
          set.add(new User("Tom",12));
          set.add(new User("Tom",12));//體現不可重複性
          set.add(129);
      
          Iterator iterator = set.iterator();
          while (iterator.hasNext()){
              System.out.print(iterator.next() + " ");//AA CC 129 456 123 User{name='Tom', age=12}
          }
      
      }
      

      LinkedHashSet:作為HashSet的子類;遍歷其內部資料時,可以按照新增的順序遍歷。
      在新增資料的同時,每個資料還維護了兩個引用,記錄此資料的前一個資料和後一個資料
      對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet

      @Test
      public void test2(){
          Set set = new LinkedHashSet();
          set.add(456);
          set.add(123);
          set.add("AA");
          set.add("CC");
          set.add(new User("Tom",12));
          set.add(new User("Tom",12));//體現不可重複性
          set.add(129);
      
          Iterator iterator = set.iterator();
          while (iterator.hasNext()){
              System.out.print(iterator.next() + " ");//456 123 AA CC User{name='Tom', age=12} 129
          }
      
      }
      

      要求:

      1. 向Set中新增的資料,其所在類一定要重寫hashCode()和equals()
      2. 重寫的hashCode()和equals()儘可能保持一致性:相等的物件必須具有相同的雜湊碼
        重寫兩個方法的小技巧:物件中用作equals()方法比較的Filed,都應該用來計算hashCode值
    2. TreeSet: 可以按照新增物件的指定屬性,進行排序
      使用說明:

      1. 向TreeSet中新增資料,要求是相同類的物件

      2. 兩種排序方式:
        自然排序(實現Comparable介面) 中,比較兩個物件是否相同的標準為:compareTo()返回0,不再是equals()

        @Test
        public void test1(){
            TreeSet set = new TreeSet();
        
            //失敗:不能新增不同類的物件
        //        set.add(123);
        //        set.add(456);
        //        set.add("AA");
        //        set.add(new User("Tom",12));
        
                //舉例一:
        //        set.add(34);
        //        set.add(-34);
        //        set.add(43);
        //        set.add(11);
        //        set.add(8);
        
            //舉例二:
            set.add(new User("Tom",12));
            set.add(new User("Jerry",32));
            set.add(new User("Jim",2));
            set.add(new User("Mike",65));
            set.add(new User("Jack",33));
            set.add(new User("Jack",56));
        
        
            Iterator iterator = set.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
            }
        
        }
        

        User.java

        package com.zhkey.Learn1;
        
        /**
         * @author zhkey
         * @create 2020-12-08
         */
        public class User implements Comparable {
            private String name;
            private int age;
        
            public User() {
            }
        
            public User(String name, int age) {
                this.name = name;
                this.age = age;
            }
        
            public String getName() {
                return name;
            }
        
            public int getAge() {
                return age;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public void setAge(int age) {
                this.age = age;
            }
        
            @Override
            public String toString() {
                return "User{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                        '}';
            }
        
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
        
                User user = (User) o;
        
                if (age != user.age) return false;
                return name != null ? name.equals(user.name) : user.name == null;
            }
        
            @Override
            public int hashCode() {
                int result = name != null ? name.hashCode() : 0;
                result = 31 * result + age;
                return result;
            }
        
            //按照姓名從小到大排列,年齡從小到大排列
            @Override
            public int compareTo(Object o) {
                if(o instanceof User){
                    User user = (User)o;
        //            return this.name.compareTo(user.name);
                    int compare = this.name.compareTo(user.name);
                    if(compare != 0){
                        return compare;
                    }else{
                        return Integer.compare(this.age,user.age);
                    }
                }else{
                    throw new RuntimeException("輸入的型別不匹配");
                }
            }
        
        }
        

      定製排序中,比較兩個物件是否相同的標準為:compare()返回0,不再是equals()

      @Test
          public void test2(){
              Comparator com = new Comparator() {
              //照年齡從小到大排列
                  @Override
                  public int compare(Object o1, Object o2) {
                      if(o1 instanceof User && o2 instanceof User){
                          User u1 = (User)o1;
                          User u2 = (User)o2;
                          return Integer.compare(u1.getAge(),u2.getAge());
                      }else{
                          throw new RuntimeException("輸入的資料型別不匹配");
                      }
                  }
              };
              TreeSet treeSet = new TreeSet(com);
      
              treeSet.add(new User("Tom",12));
              treeSet.add(new User("Jerry",18));
              treeSet.add(new User("Leon",20));
              treeSet.add(new User("Messi",15));
              treeSet.add(new User("Jack",33));
              treeSet.add(new User("Jack",55));
              treeSet.add(new User("Marry",55));
      
              Iterator iterator = treeSet.iterator();
              while (iterator.hasNext()){
                  System.out.println(iterator.next());
              }
          }
      

相關文章