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 常用集合类已经掌握的炉火纯青了。希望大家能够轻松看懂上面的示例代码。