Java 常用集合類使用總結


Java 集合類有兩種:單列集合和雙列集合。

單列集合的頂層接口是 Collection ,JDK 不提供此接口的任何直接實現,它主要提供了 List 和 Set 兩個更具體的子接口。
其中 List 接口的常用實現類為 ArrayList 和 LinkedList ,Set 的常用實現類為 HashSet 和 TreeSet 。

雙列集合主要是 Map 接口,其常用實現類為 HashMap 和 TreeMap 。
下面我們就針對上面的常用集合類,快速介紹一下。


一、List 介紹

List 接口的常用實現類為 ArrayList 和 LinkedList ,其特點為:
ArrayList 底層是數組結構實現,查詢快、增刪慢。
LinkedList 底層是鏈表結構實現,查詢慢、增刪快。

下面我們主要以 ArrayList 為例進行代碼演示:

import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {
    public static void main(String[] args) {
        //List是有序集合,可以添加重復元素
        ArrayList<String> list = new ArrayList<>();
        list.add("j");
        list.add("o");
        list.add("b");
        list.add("s");
        list.add("s");

        //普通 for 循環
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        //增強 for 循環
        for (String str : list) {
            System.out.println(str);
        }

        //采用迭代器循環,刪除集合中的每個元素
        Iterator<String> iterator = list.iterator();
        String item;
        while (iterator.hasNext())
        {
            item = iterator.next();
            System.out.println(item);
            //通過迭代器,可以刪除當前的元素
            iterator.remove();
        }

        //此時 List 為空列表,因為被迭代器刪完了
        System.out.println(list);
    }
}

LinkedList 跟 ArrayList 的用法一樣,這里就不介紹了,它比 ArrayList 多了一些特有的方法:
方法名 說明
public void addFirst(E e) 在該列表開頭插入指定的元素
public void addLast(E e) 將指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一個元素
public E getLast() 返回此列表中的最后一個元素
public E removeFirst() 從此列表中刪除並返回第一個元素
public E removeLast() 從此列表中刪除並返回最后一個元素


二、Set 介紹

這里是本篇博客的重點,我會詳細介紹一下 Set 結合,以便簡化后續的 Map 集合的介紹。

Set 的常用實現類為 HashSet 和 TreeSet ,需要注意的是:Set 不能存儲重復的元素,不能使用普通的 for 循環遍歷。
對於 Java 自帶的數據類型(整數,字符串等)來說,HashSet 和 TreeSet 能夠自動去重,不用我們實現去重規則。
對於 HashSet 和 TreeSet ,我會重點介紹 TreeSet ,最后再簡要介紹一下 HashSet 。

我們先使用 TreeSet 存取字符串為例進行代碼演示:

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * Set集合的基本使用
 */
public class SetDemo {
    public static void main(String[] args) {
        //對於 Java 自帶的數據類型(整數,字符串等),set 集合會自動去重
        TreeSet<String> set = new TreeSet<>();
        set.add("jobs");
        set.add("monkey");
        set.add("alpha");
        set.add("wolfer");
        set.add("jobs"); //存儲了一個重復的字符串

        //for (int i = 0; i < set.size(); i++) {
            //Set集合是沒有索引的,所以不能使用普通的 for 循環進行遍歷
        //}

        //采用迭代器遍歷
        Iterator<String> it = set.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        //采用增強 for 循環進行遍歷
        for (String s : set) {
            System.out.println(s);
        }

        //此行代碼打印的結果:[alpha, jobs, monkey, wolfer]
        System.out.println(set);

        /*
        通過上面打印出的結果可以發現兩個重要現象:
        1 最終只打印了一次 jobs 字符串,
        這說明對於 Java 自帶的數據類型(整數,字符串等),set 集合會自動去重

        2 打印出的內容,是按照字母的升序排序的,
        這說明 TreeSet 在存儲元素后,會默認自動對集合內的元素按照升序排序。
        */
    }
}

從上面的代碼中,我們需要特別注意 TreeSet 的一個重要特點:
TreeSet 在存儲元素時,除了去重之外,還會 默認 對所存儲的集合元素按照升序排序。
那么如果 TreeSet 中存儲的是我們自定義的類對象,TreeSet 該如何進行去重排序處理呢?

答案就是:你必須自己實現 去重排序邏輯(至於升序,還是降序,由你自己定),否則 TreeSet 就會拋出異常,讓你無法使用。
下面我們以兩種方案來實現 TreeSet 存儲我們自定義的類對象,並且自己實現去重排序邏輯。

(1)自定義類對象,需要實現 Comparable 接口的 compareTo 方法

我們以 Student 學生為例,假設案例場景為:TreeSet 存儲 Student 對象,並且按照 age 年齡升序排序,然后打印出來。
我們自定義的 Student 類,必須實現 Comparable 接口的 compareTo 方法,然后才能實例化並添加到 TreeSet 中。

具體實現代碼如下:

//我們自定義的 Student 學生類,需要實現 Comparable 接口的 compareTo 方法
public class Student implements Comparable<Student>{
    //字段:【姓名】和【年齡】
    private String name;
    private int age;

    //構造函數:采用 IDEA 自動生成【無參構造】和【全參構造】
    public Student() {}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //對【姓名】和【年齡】字段,采用 IDEA 自動生成的 get 和 set 方法
    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;}

    //采用 IDEA 自動生成重寫 toString 方法
    //方便打印【學生對象】,並進行查看
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //實現 Comparable 接口的 compareTo 方法,實現去重排序邏輯
    @Override
    public int compareTo(Student o) {
        //this 表示現在要存入的元素
        //o 表示已經存入到集合中的元素

        //按照學生的【年齡】進行升序排序(當然你也可以在這里采用降序)
        int result = this.age - o.age;
        //如果年齡相同,則按照【姓名】的字母升序排序(當然你也可以在這里采用降序)
        result = result == 0 ? this.name.compareTo(o.getName()) : result;

        //如果最終 result 等於 0 ,那就表示【姓名】和【年齡】都重復,也就是學生對象重復
        //此時 TreeSet 就不會進行存儲,因為 Set 不允許出現重復的元素
        return result;
    }
}

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student s1 = new Student("jobs",28);
        Student s2 = new Student("alpha",27);
        Student s3 = new Student("monkey",29);
        Student s4 = new Student("wolfer",32);
        //故意添加一個重復的元素:【姓名】和【年齡】都相同的 student 對象
        Student s5 = new Student("jobs",28);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        for (Student stu : ts) {
            System.out.println(stu);
        }

        /*
        最后打印的結果為:
        Student{name='alpha', age=27}
        Student{name='jobs', age=28}
        Student{name='monkey', age=29}
        Student{name='wolfer', age=32}

        可以發現:重復的 jobs 學生對象,只打印了一條,並且按照年齡升序排列
        */
    }
}

(2)通過 TreeSet 構造函數,傳入一個對象,該對象實現了 Comparator 接口中的 compare 方法

我們仍然以 Student 學生為例,將上面的案例場景再實現一遍,這樣通過對比,就能夠很容易理解。
這次的方案,Student 學生類對象不需要實現任何接口,只是作為一個實體類。
我們需要在 TreeSet 的構造函數中,傳入一個對象,該對象實現了 Comparator 接口中的 compare 方法。

具體代碼如下所示:

//我們自定義的 Student 學生類,純粹就是一個實體類
public class Student {
    //字段:【姓名】和【年齡】
    private String name;
    private int age;

    //構造函數:采用 IDEA 自動生成【無參構造】和【全參構造】
    public Student() {}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //對【姓名】和【年齡】字段,采用 IDEA 自動生成的 get 和 set 方法
    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;}

    //采用 IDEA 自動生成重寫 toString 方法
    //方便打印【學生對象】,並進行查看
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TreeSetDemo {
    public static void main(String[] args) {
        //實例化 Comparator 接口的匿名對象,實現 compare 方法
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            //實現 Comparator 接口的 compare 方法
            @Override
            public int compare(Student o1, Student o2) {
                //o1表示現在要存入的元素
                //o2表示已經存入到集合中的元素

                //按照學生的【年齡】進行升序排序(當然你也可以在這里采用降序)
                int result = o1.getAge() - o2.getAge();
                //如果年齡相同,則按照【姓名】的字母升序排序(當然你也可以在這里采用降序)
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;

                //如果最終 result 等於 0 ,那就表示【姓名】和【年齡】都重復,也就是學生對象重復
                //此時 TreeSet 就不會進行存儲,因為 Set 不允許出現重復的元素
                return result;
            }
        });

        /*
        由於 Comparator 接口中只有一個 compare 方法需要我們實現
        接口中的其它方法,前面都有 default 關鍵字,有默認的代碼實現
        因此我們可以簡化上面的 TreeSet 實例化代碼,采用 Lambda 表達式代替匿名對象實現方式

        TreeSet<Student> ts = new TreeSet<>((o1,o2) -> {
                //o1表示現在要存入的那個元素
                //o2表示已經存入到集合中的元素
                int result = o1.getAge() - o2.getAge();
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            }
        );
        */

        Student s1 = new Student("jobs",28);
        Student s2 = new Student("alpha",27);
        Student s3 = new Student("monkey",29);
        Student s4 = new Student("wolfer",32);
        //故意添加一個重復的元素:【姓名】和【年齡】都相同的 student 對象
        Student s5 = new Student("jobs",28);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        for (Student stu : ts) {
            System.out.println(stu);
        }
    }
}

(3)TreeSet 兩種實現方案的比較

第一種方案:
通過自定義類對象,需要實現 Comparable 接口的 compareTo 方法

優點:
去重排序代碼只需要在自定義對象類中寫一次,后續很多地方使用 TreeSet 添加我們的自定義對象時,不需要再關注去重排序了。

缺點:
不夠靈活,所有地方的代碼,在使用 TreeSet 添加我們的自定義對象時,只能按照自定義類中的這一種規則去重排序。
第二種方案:
通過 TreeSet 構造函數,傳入一個對象,該對象實現了 Comparator 接口中的 compare 方法

優點:
比較靈活,在使用 TreeSet 添加我們的自定義對象時,不同地方的代碼,可以根據具體需要,自定義去重排序規則。

缺點:
比較繁瑣,每次使用 TreeSet 添加我們的自定義對象時,都必須去實現去重排序規則。
最佳方案:
其實以上兩種實現方案可以同時存在。優先使用第一種方案,如果第一種方案不能滿足要求,再使用第二種方案。

還是拿上面的 Student 學生案例進行舉例:
Student 類采用第一種方案實現 (假如是升序),這樣任何地方如果需要用到 TreeSet 添加 Student 對象時,不需要關注去重排序規則。如果個別地方需要用到其它排序規則時(假如是降序),我們只需要采用第二種方案即可。雖然 Student 已經實現了第一種方案的接口方法,但是如果此時如果采用了第二種方案,TreeSet 構造函數中的去重排序規則的優先級,會高於自定義類中的去重規則,所以最終還是采用第二種方案的排序規則。因此兩種方案可以共存,沒有沖突。


(4)HashSet 存儲自定義類對象

上面已經介紹過,對於 Java 自帶的數據類型(整數,字符串等),set 集合會自動去重,HashSet 也不例外,這里就不演示了。
這里只重點介紹一下 HashSet 存儲我們自定義的類對象時,我們需要如何實現去重。答案就是重寫類中的 equals 方法和 hashCode 方法。這兩種方法都可以使用 IDEA 自動生成,因此非常簡單,我們還是以 Student 學生為例,進行代碼演示:

//我們自定義的 Student 學生類,使用 IDEA 自動生成 equals 方法和 hashCode 方法
public class Student {
    //字段:【姓名】和【年齡】
    private String name;
    private int age;

    //構造函數:采用 IDEA 自動生成【無參構造】和【全參構造】
    public Student() {}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //對【姓名】和【年齡】字段,采用 IDEA 自動生成的 get 和 set 方法
    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;}

    //采用 IDEA 自動生成重寫 toString 方法
    //方便打印【學生對象】,並進行查看
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //采用 IDEA 自動生成重寫 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    //采用 IDEA 自動生成重寫 hashCode 方法
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<Student> hs = new HashSet<>();

        Student s1 = new Student("jobs",28);
        Student s2 = new Student("jobs",28);
        Student s3 = new Student("jobs",28);

        hs.add(s1);
        hs.add(s2);
        hs.add(s3);

        //由於 3 個 Student 對象的【姓名】和【年齡】相同,
        //因此 set 認為是相同的對象,不會重復添加
        //最終只會打印出一條 Student 數據
        for (Student student : hs) {
            System.out.println(student);
        }

        //打印結果為:Student{name='jobs', age=28}
    }
}


三、Map 介紹

Map 就是鍵值對的雙列集合,其中鍵不能重復,值可以重復。
如果新增數據的鍵,與原有數據的鍵重復的話,就會修改該鍵對應的值。

Map 的常用實現類為 HashMap 和 TreeMap 。
對於 Java 自帶的數據類型(整數,字符串等)作為鍵使用,Map 會自動判斷是否重復。

我們還是以 TreeMap 為例,代碼演示如下:

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class MapDemo {
    public static void main(String[] args) {
        TreeMap<String,String> tm = new TreeMap<>();
        tm.put("c","jobs");
        tm.put("d","monkey");
        tm.put("a","wolfer");
        tm.put("b","alpha");
        //故意添加一個重復的鍵,這樣會導致該鍵對應的值被修改
        //最終 c 的值,原來為 jobs 被修改為 jobs888
        tm.put("c","jobs888");

        //遍歷 map 集合中的 value 值
        Collection<String> values = tm.values();
        for(String value : values) {
            System.out.println(value);
        }

        //第一種遍歷 map 的方式
        Set<String> keySet = tm.keySet();
        for (String key : keySet) {
            String value = tm.get(key);
            System.out.println(key + "," + value);
        }

        //第二種遍歷 map 的方式
        Set<Map.Entry<String, String>> entrySet = tm.entrySet();
        for (Map.Entry<String, String> me : entrySet) {
            String key = me.getKey();
            String value = me.getValue();
            System.out.println(key + "," + value);
        }
    }
}

對於 Map 集合來說,其鍵的集合,可以被認為是 Set 集合,有關 Set 集合,我們在上面已經詳細介紹過了,因此 Map 這里就不多介紹了,直接上代碼,如果你已經詳細看過上面 Set 的介紹的話,下面的代碼,自然可以能夠看懂。

下面我們還是以 Student 學生為例,采用 Student 對象作為 Map 的鍵,分別采用 HashMap 和 TreeMap 進行代碼演示:

//定義一個 Student 類
public class Student implements Comparable<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;}

    //重寫 toString 方法,方便打印查看 Student 對象內容
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //主要用來判斷 Student 對象是否重復
    @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 && name.equals(student.name);
    }

    //主要用來判斷 Student 對象是否重復
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    //主要用來 TreeMap 的鍵排序
    @Override
    public int compareTo(Student o) {
        //按照年齡進行升序排序
        int result = this.getAge() - o.getAge();
        //年齡相同的情況下,按照姓名排序。
        result = result == 0 ? this.getName().compareTo(o.getName()) : result;
        //如果姓名和年齡都相同,Map 認為鍵重復,會修改該鍵的值
        return result;
    }
}

下面使用 Student 作為 Map 的鍵,進行代碼演示:
import java.util.HashMap;
import java.util.Set;
import java.util.TreeMap;

public class MapDemo {
    public static void main(String[] args) {
        //HashMap 采用 Studnet 對象作為鍵的代碼演示
        HashMap<Student, String> hm = new HashMap<>();
        Student s1 = new Student("jobs", 26);
        Student s2 = new Student("monkey", 28);
        Student s3 = new Student("wolfer", 30);
        Student s4 = new Student("alpha", 22);
        //故意實例化一個【姓名】和【年齡】重復的 Student 對象
        Student s5 = new Student("jobs", 26);

        hm.put(s1, "88分");
        hm.put(s2, "90分");
        hm.put(s3, "60分");
        hm.put(s4, "86分");
        //故意添加一個【姓名】和【年齡】重復的 Student 對象
        //這樣對應鍵的值,就會被修改
        hm.put(s5, "100分");

        Set<Student> hmkeys = hm.keySet();
        for (Student key : hmkeys) {
            String value = hm.get(key);
            System.out.println(key + "----" + value);
        }

        System.out.println("----------------------------------");

        //TreeMap 采用 Studnet 對象作為鍵的代碼演示
        //TreeMap 鍵的去重排序方式,采用 Student 類中的年齡【升序】
        TreeMap<Student, String> tmasc = new TreeMap<>();
        tmasc.put(s1, "88分");
        tmasc.put(s2, "90分");
        tmasc.put(s3, "60分");
        tmasc.put(s4, "86分");
        //故意添加一個【姓名】和【年齡】重復的 Student 對象
        //這樣對應鍵的值,就會被修改
        tmasc.put(s5, "100分");

        //打印出的結果,按照 Student 的年齡【升序】排列
        Set<Student> tmasckeys = tmasc.keySet();
        for (Student key : tmasckeys) {
            String value = tmasc.get(key);
            System.out.println(key + "----" + value);
        }

        System.out.println("----------------------------------");

        //TreeMap 采用 Studnet 對象作為鍵的代碼演示
        //TreeMap 鍵的去重排序方式,采用自定義方法,按照 Student 年齡【倒序】
        TreeMap<Student, String> tmdesc = new TreeMap<>((o1, o2) -> {
            int result = o2.getAge() - o1.getAge();
            result = result == 0 ? o2.getName().compareTo(o1.getName()) : result;
            return result;
        });
        tmdesc.put(s1, "88分");
        tmdesc.put(s2, "90分");
        tmdesc.put(s3, "60分");
        tmdesc.put(s4, "86分");
        //故意添加一個【姓名】和【年齡】重復的 Student 對象
        //這樣對應鍵的值,就會被修改
        tmdesc.put(s5, "100分");

        //打印出的結果,按照 Student 的年齡【倒序】排列
        Set<Student> tmdesckeys = tmdesc.keySet();
        for (Student key : tmdesckeys) {
            String value = tmdesc.get(key);
            System.out.println(key + "----" + value);
        }
    }
}


到此為止,Java 常用集合類已經介紹完畢,希望對大家有幫助。上面的代碼演示,都經過測試無誤,比較具有實戰意義。如果你能夠輕松看懂,說明你對 Java 常用集合類已經掌握的爐火純青了。希望大家能夠輕松看懂上面的示例代碼。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM