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 常用集合類已經掌握的爐火純青了。希望大家能夠輕松看懂上面的示例代碼。
