Java 提供了一个丰富的集合类,包含了常用的数据结构和算法等。使用 Java 集合的优点部分如下:
在集合的接口和实现中大量使用了泛型,它为集合提供了一个可以容纳的对象类型,如果添加其他类型的元素,在编译时就会出错,这避免了在运行时出现类型转换异常。泛型也使得代码更加整洁,因为不需要显式地编写类型转换操作,编译器会帮我们实现。
Java 整个集合框架图如下:
可以看到,这个框图主要有两个主干: Collection 和 Map 。
Collection :它是一个接口,提供了对集合对象进行基本操作的通用接口方法,有很多具体的实现和继承,它被分为三大分支: List 、 Set 和 Queue 。 Map :它是由一系列键值对组成的集合,提供了 key 到 Value 的映射。 除此之外,还有 Iterator 迭代器, Collections 和 Arrays 工具类, Comparator 比较器等。
List 是一个有序列表,用特定的插入顺序来维护元素顺序。可以对列表中元素的插入位置进行控制,同时可以根据元素的索引访问元素。实现 List 接口的主要有 ArrayList 、 LinkedList 、 Vector 等。
ArrayList 是一个动态数组,在随机访问元素时性能较高,但插入和删除元素效率较低。 ArrayList 都有一个初始容量,代表了数组的大小,在 ArrayList 快满时,会进行扩容操作,每次增长 1.5 倍大小。但 ArrayList 是非同步的,在多线程场景下不要使用。
LinkedList 是一个双向链表,由于实现方式不同,它不支持随机访问,但很容易在列表中间进行插入和删除操作。与 ArrayList 一样, LinkedList 也是非同步的。
Vector 与 ArrayList 类似,基于动态数组实现,但 Vector 是同步的。它的操作与 ArrayList 几乎一样。
Map 是由一系列键值对组成的集合,提供了 key 到 Value 的映射。实现 Map 接口的有: HashMap 、 TreeMap 、 HashTable 、 EnumMap 等。
HashMap 以哈希表数据结构实现,查询对象时通过哈希函数将元素的哈希地址转换成数组索引,在出现碰撞冲突时,则使用链表的形式存储哈希地址相同的元素,在 JDK8 后,链表过长后会转换为红黑树。 HashMap 存储和查询效率较高,但需要考虑哈希函数、碰撞冲突等问题。
TreeMap 实现了 SortedMap 接口,内部以红黑树数据结构实现,其中键以某种排序规则排序,排序规则也可以通过 Comparator 比较器指定。
HashTable 也是以哈希表数据结构实现,遇到冲突时采用链表的形式。类似于 HashMap ,但它的同步的。
EnumMap 是将枚举类型作为键值的 Map 。由于键的数量相对固定,所以在内部用一个数组存储对应值。通常来说,效率高于 HashMap 。
Set 是一个不包括重复元素的集合,存入的元素没有顺序。内部通过 Map 实现, Set 里存储的值对应的是 Map 中的键,键对应的值是不变的,指向一个常量。实现 Set 接口的集合有: HashSet 、 TreeSet 、 EnumSet 等。
HashSet 底层基于 HashMap 实现,它内部元素的顺序是由哈希码来决定的,所以它不保证 Set 的迭代顺序。可以放入 null ,但只能放入一个。
与 HashSet 类似,它是基于 TreeMap 实现,以某种排序规则排序。它是使用元素的自然顺序排序,或根据创建 Set 时提供的 Comparator 进行排序。但不允许存入 null 值。
EnumSet 基于 EnumMap 实现,是枚举专用的 Set ,其中所有元素都是枚举类型。
Queue 通常是指先进先出的队列,也不允许随机访问队列中的元素。而 Deque 接口是 Queue 的子接口,它代表一个双端队列。
ArrayDeque 是基于有首尾指针的数组(环形缓冲区)实现的双端队列,它只能从首尾取出或插入元素。底层由数组实现,可以指定容量,默认容量为 16 ,并根据添加元素个数,动态扩展。
PriorityQueue 是一个优先级队列,它使用自然顺序或者制定的比较器来排序。队列的头是按指定排序方式的最小元素。
Comparator 和 Comparable 是两个接口,都可以用来对对象进行比较。
Comparable 接口用于当前对象和其他对象进行比较。它有一个 compareTo 方法,该方法只有一个参数。返回值为 int ,大于 0 表示当前对象大于参数对象;小于 0 表示当前对象小于参数对象;等于 0 表示两者相等。 Comparator 是一个比较器接口,用于对传入的两个对象进行比较。它有一个 compare 方法,该方法有两个参数。 例如,对一组 Student 对象进行排序,分别使用 Comparable 和 Comparator 接口实现功能。
Comparable 接口实现在对象类的内部,之后对象就变成了一个可以比较大小的对象,也就可以用来排序了。
首先 Student 类需要实现 Comparable 接口,重写其 compareTo 方法:
public class Student implements Comparable<Student> {
private String name;
private int age;
// setter、getter、toString
@Override
public int compareTo(Student another) {
int flag = this.name.compareTo(another.name);
if(flag == 0) {
flag = this.age - another.age;
}
return flag;
}
}
复制代码
然后利用 List 接口的 sort(Comparator<? super E> c) 默认方法,或者 Collections 工具类的 sort(List<T> list) 方法进行排序:
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("a", 4));
students.add(new Student("d", 2));
students.add(new Student("c", 5));
students.add(new Student("c", 3));
students.sort(null);
// Collections.sort(students);
for (Student student : students) {
System.out.println(student);
}
}
复制代码
Comparator 实现在对象类的外部,此时对象类的结构不需要有任何变化。
然后另外定义一个比较器类,实现 Comparator 接口并重写其 compare 方法:
class PersonComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
int flag = o1.getName().compareTo(o2.getName());
if(flag == 0) {
flag = o1.getAge() - o2.getAge();
}
return flag;
}
}
复制代码
然后利用 List 接口的 sort(Comparator<? super E> c) 方法,或者 Collections 工具类的 sort(List<T> list, Comparator<? super T> c) 方法进行排序:
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("a", 4));
persons.add(new Person("d", 2));
persons.add(new Person("c", 5));
persons.add(new Person("c", 3));
persons.sort(new PersonComparator());
// Collections.sort(persons, new PersonComparator());
for (Person person : persons) {
System.out.println(person);
}
}
复制代码