Java 集合詳解 | 一篇文章解決Java 三大集合

更好閱讀體驗:Java 集合詳解 | 一篇文章搞定Java 三大集合

好看的皮囊像是一個個容器,有趣的靈魂像是容器里的數據。接下來講解Java集合數據容器。

文章篇幅有點長,還請耐心閱讀。如只是為了解決某個疑問,可以閱讀目錄來查找你所需的內容。

開門見山:「Java集合框架圖」

 

 

1.Iterator(迭代器)

迭代器的基本功能就是遍歷集合中的所有元素。

Iterable 接口組合了迭代器Iterator,通過方法:Iterator iterator(); 來獲取迭代器。

Collection 和 Map是 Java集合框架的根接口,Iterable 接口提供了迭代的功能。

1.1.集合和迭代器的關係

Collection 接口通過繼承Iterable 接口來獲得迭代功能;

Map 接口的迭代功能是嫁接Collection 接口的迭代功能,看Map 接口的抽象方法就知道了:具體怎麼用在Map 集合的常用方法中講到。

// 通過鍵來遍歷,返回值是Set集合,Map的key是Set集合,特性也和Set一樣的
Set<K> keySet();
// 通過值來遍歷,返回值是Collection
Collection<V> values();
// 通過鍵值對來遍歷,返回值是Set集合
Set<Map.Entry<K, V>> entrySet();

2.集合和集合特點

List集合:元素按進入先後有序排序有序排序保存,可以存儲重複元素

Set集合:不可以存儲重複元素

Map集合:Key值不可以重複(key等於Set集合),Value值可以重複

 數據結構:數組、雙向鏈表、哈希表、二叉樹;關於數據底層存儲結構,決定了集合絕大部分的特性,如:查詢和增刪快慢,是否有序,是否可以重複。

線程安全:就是集合中的方法使用了同步鎖關鍵字:synchronized;有鎖的效率低。

3.怎麼選擇集合

根據需求和集合特點來選擇所需的集合類型。

 

 「集合的具體使用」👇

4.List 集合

4.1.單集合操作

List<String> list = new ArrayList<String>();
// 1.1 新增元素
list.add("測試1");
list.add("測試2");
System.out.println("集合大小="+list.size());
// 1.2 指定下標新增,index 範圍:[0,list.size()]
list.add(1,"測試3");
for (int i=0;i<list.size();i++){
    // 2.1 獲取元素
    String str = list.get(i);
    System.out.println("新增="+str);
}
// 3.1 修改元素,index 範圍:[0,list.size())
list.set(2,"測試?");
for (String str : list){
    System.out.println("修改"+str);
}
// 4.1 刪除第3個元素,index 範圍:[0,list.size())
// list.remove(2);
// 4.2 刪除指定對象值
list.remove(new String("測試?"));
System.out.println("刪除="+list);//AbstractCollection重寫了toString()

4.2.集合間操作

//集合間操作
LinkedList<String> linkedList = new LinkedList<String>();
// 1 新增集合所有元素(合集),如果集合間無相同元素,則為並集
linkedList.addAll(list);
linkedList.addAll(2,list);
Iterator<String> listIterator = linkedList.listIterator();
while (listIterator.hasNext()){
    String next = listIterator.next();
    System.out.println("++"+next);
}
// 2 獲取兩者都有的元素(交集)
list.add("存在和價值");
list.retainAll(linkedList);
list.forEach(str -> {
    System.out.println("交集="+str);
});

// 3 移除指定集合元素(差集)
list.add("存在和價值");
list.removeAll(linkedList);
for (String str : list){
    System.out.println("差集="+str);
}

// 4 並集,如果集合見存在相同元素,則去除相同在合併就可以得到並集
list.removeAll(linkedList);
list.addAll(linkedList);
for (int i=0;i<list.size();i++){
    // 2.1 獲取元素
    String str = list.get(i);
    System.out.println("並集="+str);
} 

list 集合的「四種遍歷方式」都在代碼裏面了👆

還有很多其他方法,ArrayList 和 LinkedList 還有一些特有的方法,可自行學習。

5.Set 集合

Set 集合不能保存重複元素。通過equals() 和 hashCode() 都相等來決定元素是否相等(重複元素)。

5.1.實現equals() 和hashCode()方法

AbstractSet 抽象類默認實現equals() 和 hashCode(),實現類會調用存儲對象的hashCode() 方法來得到hashCode值,然後根據該hashCode值決定該對象在的存儲位置;通過equal() 方法來判斷兩個對象的地址是否相等、類型是否相等、是否包含在集合內。所以兩個對象equals()相等,hashcode()一定相等;hashcode()不相等,則equals()肯定不相等就不用再判斷了提高效率。

重寫equals() 和hashCode()方法

hashCode 值的計算方式

 一個實體類有多個成員變量,為了避免偶然出現哈希值相等問題,常常將多列的哈希值成語質數再相加:hashCode=(int)var*31+var.hashCode()*31;

「例子」

public class Person {
    private String name;
    private int 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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode())*prime;
        result = prime * result + age*prime;
        return result;
    }
}

如果覺得這樣生成hashCode麻煩,可以用Objects類型的hash方法生成

@Override
public int hashCode() {
     return Objects.hash(name,age);
}

在使用HashSet 或 LinkedHashSet 集合時,實現hashCode和equals 方法即可。但在使用TreeSet 的時候,實體類還需要重寫比較器來實現定製排序。

5.2.比較器的實現

「比較器實現方式匯總」

  1. 實體類實現Comparable接口,重寫compareTo方法;
  2. 專門寫一個實現類實現Comparator接口,重寫compare方法;創建TreeSet的時候傳入該實體類的對象;
  3. 匿名實現Comparator;
  4. lambda實現Comparator;

第1種方式實現Comparable接口,重寫compareTo方法;例如Person類通過age 和 name 字段進行排序

// 1.實現Comparable接口,重寫compareTo方法
@Override
public int compareTo(Object obj) {
    if (!(obj instanceof Person))
        throw new RuntimeException("不是正確對象");

    Person p = (Person) obj;
    if (p.age > this.age) return -1;
    if (p.age == this.age) {
        return this.name.compareTo(p.name);
    }
    return 1;
}

比較器有多種實現方式,這只是其中一種比較簡單的。

第2、3、4方式核心代碼都一樣,只是寫法不同

// 2.單獨類實現
public class ComparatorImpl implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        if (o1==o2) return 0;
        if (!(o1 instanceof Person) || !(o2 instanceof Person))
            throw new RuntimeException("不是Person 類型");

        Person p1=(Person)o1;
        Person p2=(Person)o2;
        int num=p1.getName().compareTo(p2.getName());
        if (num==0){
            return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
        }
        return num;
    }
}

// 怎麼使用
Set<Person> treeSet = new TreeSet<Person>(new ComparatorImpl());

// 3.匿名方式
TreeSet ts = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {內容一樣}
});

// 4.lambda 方式 (匿名方式的簡寫)
TreeSet ts = new TreeSet((o1, o2)->{內容一樣}
});

1和2的實現方式的特點是寫死了,所有使用的定製排序都是一樣的,不能對不同的對象定製不同的排序規則,需要對每個對象定製不同排序則使用3或4的實現方式。

5.3.Set 集合通用方法

5.4.TreeSet 特有方法

  1. Comparator comparator():用於獲取定製排序Comparator 對象。如果實現了自定義定製排序,則該方法返回Comparator對象;反正返回null。
  2. Object first():返回集合中的第一個元素。
  3. Object last():返回集合中的最後一個元素。
  4. Object lower(Object e):返回集合中位於指定元素之前的元素(即小於指定元素的最大元素,參考元素不需要是TreeSet集合里的元素)。
  5. Object higher(Object e):返回集合中位於指定元素之後的元素(即大於指定元素的最小元素,參考元素不需要是TreeSet集合里的元素)。
  6. SortedSet subSet(Object fromElement, ObjecttoElement):返回此Set的子集合,範圍從fromElement(包含)到toElement(不包含)。
  7. SortedSet headSet(Object toElement):返回此Set的子集,由小於toElement的元素組成。
  8. SortedSet tailSet(Object fromElement) :返回此Set 的子集,由大於或等於fromElement的元素組成。

6.1.Map 集合

「建議:要想掌握好Map集合得先學會Collection集合」

Map集合存儲鍵值對數據,Key 擁有Set集合特點,Value 擁有着Collection的特點,Map 像是Set+Collection的集合。所以在使用map存儲、獲取對象時,用作Key的對象必須實現equals 和 hashCode 方法。不重寫這兩個方法就會出現,相同的Key的數據也可以保存到Map集合中,就會出現**「key不唯一」**,如:當你使用map.get() 獲取到的是「null」。

6.2.為什麼必須重寫equals 和 hashCode 方法

「必須舉個例子說明,因為常常有人在這掉入陷阱」

public class Student {
    private String name;
    private int age;

    public Student(){}

    public Student(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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 測試是否會保存相同的key

// 測試
public class TestMap {
    public static void main(String[] args) {
        Student student1 = new Student("張三", 18);
        Student student2 = new Student("我是唯一", 28);
        Student student3 = new Student("張三", 18);
        
        HashMap<Student, Student> studentHashMap = new HashMap<>();
        studentHashMap.put(student1,student1);
        studentHashMap.put(student2,student2);
        studentHashMap.put(student3,student3);

        Iterator<Map.Entry<Student, Student>> iterator = studentHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Student, Student> next = iterator.next();
            System.out.println("key="+next.getKey()+"|value="+next.getValue());
        }

    }
}

結果:相同的Key也保存成功了

 

 測試通過key來獲取value:關鍵來了最容易理解錯的一步

錯誤示範:使用前面定義的對象引用來獲取

// 錯誤示範:使用前面定義的對象引用來獲取
Student student = studentHashMap.get(student3);
System.out.println(student);

錯誤示範結果:在存有相同的key的map集合中,拿到了value值

正確示範:使用新定義的對象引用來獲取(定義的對象字段值是相同的)

// 正確示範:使用新定義的對象引用來獲取:定義的對象字段值是相同的
Student student4 = new Student("張三", 18);
Student student = studentHashMap.get(student4);
System.out.println(student);

測試結果

 實現equals 和 hashCode方法後再來正確示範

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name,age);
    }

 實現後的測試結果

 

之所以會測不出來null的問題是因為在同一個類中測試往往喜歡使用前面的存儲時的引用來操作map.get,那肯定是測不出來的,因為new的所有對象的地址肯定都是唯一的,所以用唯一的地址去獲取,那肯定是可以get到的。正確的是新建一個內容相同,地址不同的引用去獲取。

當使用TreeMap 時,作為key的對象還需要定製排序,這個操作和Set集合的定製排序一樣,具體看Set 集合定製排序。

6.3.Map集合常用方法

HashMap<String, String> hashMap = new HashMap<>();
// 新增
hashMap.put("test1","test1-value");
hashMap.put("test2","test2-value");
// map和其他集合都重寫了toString
System.out.println(hashMap);
System.out.println("集合大小"+hashMap.size());
System.out.println("集合大小是否=0:"+hashMap.isEmpty());
System.out.println("是否包含key=test1的元素:"+hashMap.containsKey("test1"));
System.out.println("是否包含value=test1的元素:"+hashMap.containsValue("test1"));
System.out.println("獲取Key=test1的值:"+hashMap.get("test1"));
// 移除元素
hashMap.remove("test1");
System.out.println(hashMap);
// 集合合併
HashMap<String, String> hashMap1 = new HashMap<>();
hashMap1.put("test1","test1"); // 相同key的會覆蓋原來的值
hashMap1.put("hashMap1","hashMap1-value");
hashMap.putAll(hashMap1);
System.out.println(hashMap); 

輸出結果

通過迭代器來遍歷:「key 集合」「value集合」「key-value集合(entry)」

看代碼就知道key集合是Set集合,value集合是Collection集合,entry集合是Set集合

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("testKey1","testValue1");
hashMap.put("testKey2","testValue2");
// 1.通過 鍵 來遍歷,返回值是Set集合,Map的key是Set集合,特性也和Set一樣的
//獲取集合中所有key的Set集合
Set<String> keySet = hashMap.keySet();
//通過Set集合的迭代器進行遍歷 (為了好理解分開兩步來獲取,開發時可以一步到位)
Iterator<String> iteratorKey = keySet.iterator();
while (iteratorKey.hasNext()) {
    String next = iteratorKey.next();
    System.out.println("key="+next);
}
// 2.通過 值 來遍歷,返回值是Collection
// 獲取集合中所有value的集合
Collection<String> collection = hashMap.values();
//通過集合的迭代器進行遍歷
Iterator<String> iteratorValue = collection.iterator();
while (iteratorValue.hasNext()){
    String next = iteratorValue.next();
    System.out.println("value="+next);
}
// 3.通過 鍵值對 來遍歷,返回值是Set集合
//獲取Map元素對象的Set集合
Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
//通過Set集合的迭代器進行遍歷
Iterator<Map.Entry<String, String>> iteratorEntry = entrySet.iterator();
while (iteratorEntry.hasNext()){
    //單個map元素對象
    Map.Entry<String, String> entry = iteratorEntry.next();
   System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}

輸出結果

6.4.Map集合的三種遍歷方式

前面講了三種方式,前兩種只是准對key 和 value的遍歷。

在這裡匯總的都是針對整個Map的遍歷方式:通用迭代器方式forEach」「forEach方法」

// 通用遍歷方式
Iterator<Map.Entry<String, String>> iteratorEntry = hashMap.entrySet().iterator();
while (iteratorEntry.hasNext()){
    Map.Entry<String, String> entry = iteratorEntry.next();
    System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}
// forEach
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println("key=" + key + ",value=" + value);
}
// 調用forEach方法,lambda語法
hashMap.forEach((key,value)->{
    System.out.println("key="+key+",value="+value);
});

HashMap、LinkedHashMap、TressMap和ConcurrentHashMap……那麼多的方法,只能自己去學習了。

7.Collections 集合工具類

Collections 是用於操作List、Set和Map等集合的工具類。該工具類提供了大量方法(功能)對集合元素進行排序、查詢、修改等操作,對集合對象實現同步控制等方法。

7.1.排序問題

常用於對List集合元素排序的方法有

  1. 自然排序:void sort(List list);
  2. 比較器定製排序:void sort(List list,Comparator c);
  3. 反轉排序:void reverse(List list);
  4. 隨機排序:void shuffle(List list);
    // 0.數據準備
    List<String> arrayList = new ArrayList<>();
    arrayList.add("z");
    arrayList.add("a");
    arrayList.add("c");
    arrayList.add("b");
    System.out.println("插入順序:"+arrayList);
    // 1.自然排序
    Collections.sort(arrayList);
    System.out.println("自然排序:"+arrayList);
    // 2.比較器定製排序
    Collections.sort(arrayList, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o2.compareTo(o1);
        }
    });
    System.out.println("定製排序:"+arrayList);
    // 3.反轉排序
    Collections.reverse(arrayList);
    System.out.println("反轉排序:"+arrayList);
    // 4.隨機排序
    Collections.shuffle(arrayList);
    System.out.println("隨機排序:"+arrayList);

7.2.解決線程安全問題

ArrayList 和 Vector 的主要區別是線程安全,雖然Vector可以解決線程安全,但不建議使用。可以通過集合工具類(Collections類)來解決ArrayList 並發訪問線程安全問題。對於Set和Map集合線程安全問題,也可以通過Collections類來解決。

// 創建對象時使用
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<String>());

Collections 工具類還有查找操作、修改操作等等,更多知識自行學習。

 

 原創不易,還請三聯。更多優質文章👆

Tags: