集合是能够容纳其他对象的对象,容纳的对象称为集合的元素,例如数组就是一种最基本的集合对象。集合内的元素与元素之间具有一定的数据结构,并提供了一些有用的算法,从而为程序组织和操纵批量数据提供强有力的支持。但是,使用 Array 存储对象方面具有一些弊端,而 Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。
注意:集合、数组都是对多个数据进行存储操作的结构,简称 Java 容器。此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储。
数组在内存存储方面的特点:
数组初始化以后,其长度就确定了。
数组声明的类型,就决定了进行元素初始化的类型。我们就只能操作指定类型的数据了。
比如: String[] arr; int[] arr1; Object[] arr2。
数组在存储数据方面的弊端:
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
Java 的集合工具类都定义在 java.util 包中,该程序包及其子程序包为 Java 程序提供了一系列有用的工具。Java API 将集合分为两种,一种称为集合 (Collection) 类,用接口 Collection 描述其操作,其中存放的基本单位是单个对象,以列表 (List) 和集合 (Set) 为代表;另一种称为映射 (Map) ,用接口 Map 描述其操作,其中存放的基本单位是对象对 (object pairs) ,其中一个对象称为键 (key) 对象,另一个对象称为值 (value) 对象。
Collection 接口:单列数据,定义了存取一组对象的方法的集合。
List:元素有序、可重复的集合。 → ” 动态 “ 数组
Set:元素无序、不可重复的集合。 → 数学中的 ” 集合 “
Map 接口:双列数据,保存具有映射关系 “key - value 对” 的集合。 → 数学中的函数:y = f (x)
Java Collection API 的核心接口和常用集合类如下图所示:
Collection 接口是 List 接口和 Set 接口的父接口,通常情况下不直接使用。Collection 接口中定义了一些通用方法,通过它们可以实现对集合的添加、删除等基本操作。List 接口和 Set 接口实现了 Collection 接口,所以这些方法对 List 和 Set 集合是通用的。如下所示为 Collection 接口定义的常用方法。
Java Collection API 为集合对象提供了 Iterator (迭代器) 接口,用来实现遍历集合中的元素。
GOF (Gang of Four) 给迭代器模式的定义为:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于 “ 公交车上的售票员 ” 。
Collection 接口继承了 java.lang.Iterable 接口,该接口有一个 iterator() 方法,那么所有实现了Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象。
Iterator 仅用于遍历集合,lterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
lterator 接口包含三个方法:
public interface Iterator {
Object next();
boolean hasNext();
void remove();
}
next() 和 hasNext():
通过反复调用 next() 方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next() 方法将抛出一个 NoSuchElementException 。因此,需要在调用 next() 之前调用 hasNext() 方法。如果迭代器对象还有多个供访问的元素,这个方法就返回 true 。如果想要查看集合中的所有元素,就请求一个迭代器,并在 hasNext() 返回 true 时反复地调用 next() 方法。
Collection coll = new ArrayList();
Iterator iterator = coll.iterator();
// hasNext():判断是否还有下一个元素
while(iterator.hasNext()) {
// next():指针下移,将下移后的集合位置上的元素返回
System.out.println(iterator.next());
}
错误方式一:
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);
Iterator iterator = coll.iterator();
while (iterator.next() != null) {
System.out.println(iterator.next());
}
}
运行结果:
456
Tom
java.util.NoSuchElementException
错误方式二:
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);
while (coll.iterator().hasNext()) {
System.out.println(coll.iterator().next());
}
}
运行结果:
123
123
123
123
... // 123 死循环
集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
remove():
Iterator 接口的 remove() 方法,可以在遍历的时候,删除上次调用 next() 方法时返回的元素。此方法不同于集合直接调用 remove(),是遍历过程中通过迭代器对象的 remove() 方法。
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);
// 删除集合中 "Tom"
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if("Tom".equals(obj)) {
iterator.remove();
}
}
// 遍历集合
iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
运行结果:
123
456
Person{name='Jerry', age=20}
false
注意:
如果还未调用 next() 或在上一次调用 next() 方法后已经调用了 remove() 方法,在调用 remove() 方法都会报 IllegalStateException。
如果想删除两个相邻的元素,不能直接地这样调用:
iterator.remove();
iterator.remove(); // Error!
必须先调用 next() 越过将要删除的元素。
iterator.remove();
iterator.next();
iterator.remove(); // OK!
使用 “for each” 循环遍历集合元素
Java 5.0 提供了 “for each” 循环迭代访问 Collection 和数组,也称 “增强 for 循环”。“for each” 遍历操作不许获取 Colllection 或数组的长度,无需使用索引访问元素。但 “for each” 遍历集合的底层是调用 Iterator 完成的。
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("Tom"));
coll.add(false);
// for(集合或数组中元素的类型 局部变量 : 集合或数组对象)
// 内部仍然调用了迭代器
for(Object obj : coll) {
System.out.println(obj);
}
}
运行结果:
123
456
Person{name='Jerry', age=20}
Tom
false
遍历数组:
public static void main(String[] args) {
String[] arr = new String[]{"AA", "AA", "AA"};
// 方式一:普通 for 赋值
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 方式二:增强 for 循环
for(String s : arr) {
System.out.println(s);
}
}
List 接口属于列表类型,且列表的主要特征是以线性方式存储对象,因此 List 容器中的元素有序且可重复,容器中的每个元素都有其对应的顺序索引。List 容器中的每个元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素,所以通常使用 List 替代数组。List 接口继承了 Collection 接口,除继承了 Collection 中声明的方法外,List 接口还增加了一些按位置存取元素、查找、建立 List 视图等新的操作。
在 java.util 包中,提供了实现 List 接口的 ArrayList 类(向量表)、LinkedList 类(双向链表)和 Vector 类(向量)三个工具类。
List:存储有序的、可重复的数据。 → “动态” 数组,替换原有的数组。
List 存储元素的要求:
向 List 中添加的元素,添加的对象所在的类要重写 equals() 方法。
JDK 7 情况下:
ArrayList list = new ArrayList(); // 底层创建了长度是 10 的 Object[] 数组 elementData
list.add(123); // elementData[0] = new Integer(123);
...
list.add(11); // 如果此次添加导致底层 elementData 数组容量不够,则扩容。
// 默认情况下,扩容为原来容量的 1.5 倍,同时需要将原有数组中的数据复制到新的数组中。
// 结论:建议开发中使用带参的构造器:ArrayList list = new Arraylist(int capacity);
JDK 8 中 ArrayList 的变化:
ArrayList list = new ArrayList(); // 底层 Object[] elementData 初始化为 {},并没有创建长度是 10 的数组。
list.add(123); // 第一次调用 add() 时,底层才创建了长度为 10 的数组,并将数据 123 添加到 elementData,
...
// 后续的添加和扩容操作与 JDK 7 相同。
小结:JDK 7 中的 ArrayList 的对象的创建类似于单例的饿汉式,而 JDK 8 中的 ArrayList 的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList list = new LinkedList(); // 内部声明了 Node 类型的 first 和 last 属性,默认值为 null。
list.add(123); // 将 123 封装到 Node 中,创建了 Node 对象。
// 其中,Node 定义为:体现了 LinkedList 的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
JDK 7 和 JDK 8 中通过 Vector() 构造器创建对象时,底层都创建了长度为 10 的数组。
在扩容方面,默认扩容为原来的数组长度的 2 倍。
注意:
1. list.addAll(list1); 和 list.add(list1);
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 12));
list.add(456);
List list1 = Arrays.asList(1, 2, 3);
list.addAll(list1); // 将 list1 的每一个元素添加到 list 中
System.out.println(list.size()); // 结果 list 的大小为 9
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 12));
list.add(456);
list.add(list1); // 将 list1 作为整体一个元素添加到 list 中,结果 list 的大小为 7
System.out.println(list.size()); // 结果 list 的大小为 7
2. remove(int index) 和 remove(Object obj)
删除索引为 2 的元素:
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updataList(list);
System.out.println(list);
}
private void updataList(List list) {
list.remove(2); // index: 2
}
运行结果:
[1, 2]
删除数据为 2 的元素:
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updataList(list);
System.out.println(list);
}
private void updataList(List list) {
list.remove(new Integer(2)); // element: 2
}
运行结果:
[1, 3]
总结:
Set 集合是最简单的一种集合,存放于集中的对象不按特定方式排序,只是简单地把对象加入集合中,类似于向口袋里放东西。Set 集合包括 Set 接口以及 Set 接口的所有实现类。因为 Set 接口实现了 Collection 接口,所以 Set 接口拥有 Collection 接口提供的所有方法,它自身没有提供额外的方法。
JDK 中提供了实现 Set 接口的 HashSet 类和 TreeSet 类。
Set 接口:存储的数据是无序的、不可重复的,类似数学中的 “集合”。
HashSet 类是用哈希 (Hash) 表实现了 Set 接口,是 Set 的典型实现,大多数时候使用 Set 集合时都使用这个实现类。一个 HashSet 对象中的元素存储在一个哈希表中,而且用 HashSet 类实现 Set 集合能够快速定位集合中的元素。 用 HashSet 类实现的 Set 集合中的对象必须是唯一的,所以在添加对象时,要重写 equals() 方法进行验证,从而保证插入集合中的对象标识的唯一性。用 HashSet 类实现的 Set 集合按照哈希码排序,并根据对象的哈希码来确定对象的存储位置,所以在添加对象时,要重写 hashCode() 方法,从而保证插入集合中的对象能够合理地分在集合中,以便于快速定位集合中的对象。
Set 接口存储的数据是无序的、不可重复的,以 HashSet 为例说明:
HashSet 具有以下特点:
HashSet 集合判断两个元素相等的标准:
两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
向 HashSet 中添加元素的过程:
我们向 HashSet 中添加一个元素 a,首先调用元素 a 所在类的 hashCode() 方法,计算元素 a 的哈希值,
此哈希值接着通过某种算法(散列函数)计算出在 HashSet 底层数组的存放位置(即为:索引位置),并判断数组此位置上是否已经有元素:
如果此位置没有其他元素,则元素 a 添加成功。(情况 1)
如果此位置上有其他元素 b(或以链表形式存在多个元素),则比较元素 a 与元素 b 的 hash 值:
如果 hash 值不同,则添加元素成功。(情况 2)
如果 hash 值相同,进而需要调用元素 a 所在类的 equals() 方法:
补充:
关于添加成功的情况 2 和情况 3 而言:元素 a 与已经存在指定索引位置上数据以链表的方式存储。(HashSet 底层:数组 + 链表的结构)
LinkedHashSet 作为 HashSet 的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据(双向链表),这使得元素看起来是以插入顺序保存的。LinkedHashSet 插入性能略低于 HashSet,但如果需要频繁的遍历操作,LinkedHashSet 效率高于 HashSet。
HashSet 和 LinkedHashSet 存储元素的要求:
向 HashSet 和 LinkedHashSet 中添加的元素,其所在的类一定要重写 hashCode() 和 equals()。重写的 hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相等的哈希值。
TreeSet 类不仅实现了 Set 接口,还实现了 java.util.SortedSet 接口,从而保证在遍历集合时按照递增的顺序获得对象。遍历对象时可能是按照自然顺序递增排列(自然排序),所以,存入由 TreeSet 类实现的 Set 集合的对象时必须实现 Comparable 接口;也可能是按照指定比较器 Comparator 递增排列(定制排序),即可以通过比较器对由 TreeSet 类实现的Set集合中的对象进行排序。默认情况下,TreeSet 采用自然排序。
// 自然排序
@Test
public void test1() {
TreeSet set = new TreeSet();
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 2));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Jack", 66));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
// 定制排序
@Test
public void test2() {
Comparator com = new Comparator() {
// 按照年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(), u2.getAge());
} else {
throw new RuntimeException("输入类型不匹配");
}
}
};
TreeSet set = new TreeSet(com); // 不加参数按照自然排序方式,加上参数按照定制排序。
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 2));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Mary", 33));
set.add(new User("Jack", 66));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
TreeSet 存储元素的要求:
向 TreeSet 中添加的数据,要求是同一个类的对象。
TreeSet 类通过实现 SortedSet 接口所增加的方法:
Map 集合为映射类型,与 Collection 并列存在,用于保存具有映射关系映射关系的数据:key-value(类似与数学中的函数:y = f(x) )。映射与集和列表有明显的区别,映射中的每个对象都是成对存在的。映射中存储的每个对象都有一个相应的键(key)对象,在检索对象时必须通过相应的键对象来获取值(value)对象。Map 中的 key 和 value 都可以是任何引用类型的数据。其中, Map 中的 key 用 Set 来存放,不允许重复。
Map:双列数据,存储 key-value 对的数据。 → 类似与数学中的函数:y = f(x)
HashMap:作为 Map 的主要实现类。线程不安全,效率高。可以存储 null 的 key 和 value。
LinkedHashMap:保证在遍历 Map 元素时,可以按照添加的顺序实现遍历。
原因:在原有的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行的效率高于 HashMap。
TreeMap:保证按照添加的 key-value 对进行排序,实现排序遍历。此时考虑 key 的自然排序或定制排序。(底层使用红黑树)
Hashtable:作为 Map 的古老实现类。线程安全,效率低。不能存储 null 的 key 和 value。
Map 结构的理解:
添加、删除、修改操作:
查询操作:
元视图操作方法:
总结:常用方法
注意:
Map 允许值对象为 null,并且没有个数限制。所以,当 get() 方法的返回值为 null 时,可能有两种情况:一种是在集合中没有该键对象,另一种是该键对象没有映射任何值对象,即值对象为 null。因此,在 Map 集合中不应该利用 get() 方法来判断是否存在某个键,而应该利用 containsKey() 方法来判断。
Map 常用方法测试:
添加、修改操作:
@Test
public void test(){
Map map = new HashMap();
// put(Object key,Object value)
map.put("AA", 123);
map.put(45, 123);
map.put("BB", 56);
System.out.println(map);
// 修改,当 key 相同时,替换 value
map.put("AA", 87);
System.out.println(map);
// putAll(Map m)
Map map1 = new HashMap();
map1.put("CC", 123);
map1.put("DD", 123);
map.putAll(map1);
System.out.println(map);
}
运行结果:
{AA=123, BB=56, 45=123}
{AA=87, BB=56, 45=123}
{AA=87, BB=56, CC=123, DD=123, 45=123}
删除操作:
@Test
public void test(){
Map map = new HashMap();
map.put("AA", 123);
map.put(45, 123);
map.put("BB", 56);
// remove(Object key)
Object value = map.remove("AA"); // 返回移除元素的 value
System.out.println(value);
Object value1 = map.remove("CC"); // 返回 null
System.out.println(value1);
System.out.println(map);
// clear()
map.clear(); // 与 map = null 操作不同,map 还在,只是里面没有数据了
System.out.println(map.size());
System.out.println(map);
}
运行结果:
123
null
{BB=56, 45=123}
0
{}
查询操作:
@Test
public void test() {
Map map = new HashMap();
map.put("AA", 123);
map.put(45, 123);
map.put("BB", 56);
// Object get(Object key)
System.out.println(map.get(45));
System.out.println(map.get(455)); // null
//boolean containsKey(Object key)
boolean isExist = map.containsKey("BB");
System.out.println(isExist);
// boolean containsValue(Object value)
isExist = map.containsValue(123);
System.out.println(isExist);
// boolean isEmpty()
map.clear();
System.out.println(map.isEmpty());
// boolean equals(Object obj)
Map map1 = new HashMap();
map1.put("AA", 123);
map1.put(45, 123);
map1.put("BB", 56);
map.put("AA", 123);
map.put(45, 123);
map.put("BB", 56);
System.out.println(map.equals(map1));
}
运行结果:
123
null
true
true
true
true
元视图操作:
@Test
public void test34() {
Map map = new HashMap();
map.put("AA", 123);
map.put(45, 1234);
map.put("BB", 56);
// 遍历所有的 key 集:keySet()
Set set = map.keySet(); // map 不能直接调用 iterator()
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println();
// 遍历所有的 value 集:values()
Collection values = map.values();
for (Object obj : values) {
System.out.println(obj);
}
System.out.println();
// 遍历所有的 Key-value 集
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
// entrySet 集合中的元素都是 Entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
运行结果:
AA
BB
45
123
56
1234
AA---->123
BB---->56
45---->1234
HashMap 类实现了 Map 接口,是通过哈希码对其内部的映射关系进行快速查找,即基于哈希表的 Map 接口的实现,是 Map 接口使用频率最高的实现类。其允许使用 null 键和 null 值,与 HashSet 一样,不保证映射的顺序。
HashMap 的底层存储结构:
HashMap 源码中的重要常量:
JDK 8 之前:
HashMap map = new HashMap():
HashMap 的内部存储结构其实是数组和链表的结合。在实例化以后,底层创建了长度为 Capacity 的一维数组 Entry[] table,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “ 桶 ” (bucket),每个 bucket 都有自己的索引,系统可以根据索引快速的查找 bucket 中的元素。每个 bucket 中存储一个元素,即一个 Entry 对象,但每一个 Entry 对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个 Entry 链。而且新添加的元素作为链表的 head。
添加元素的过程( map.put(key1, value1) ):
首先,调用 key1 所在类的 hashCode() 计算 key1 的哈希值,此哈希值经过某种算法(散列函数)计算以后, 得到在 Entry 数组中的存放位置。
如果此位置上的数据为空,此时的 key1-value1 添加成功。(情况 1)
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表的形式存在)),比较 key1 和已经存在的一个或多个数据的哈希值:
如果 key1 的哈希值与已经存在的数据的哈希值都不相同,此时 key1-value1 添加成功。
(情况 2)
如果 key1 的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用 key1 所在类的 equals(key2) 方法,比较:
补充:
关于添加成功的情况 2 和情况 3 而言:此时 key1-value1 和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。
默认的扩容方式:扩容为原来的 2 倍,并将原有的数据复制过来。
HashMap 的扩容:当 HashMap 中的元素越来越多的时候,hash 冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对 HashMap 的数组进行扩容,而在 HashMap 数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是 resize。
JDK 8 相较于 JDK 8 之前在底层实现方面的不同:
HashMap map = new HashMap(); 默认情况下,先不创建长度为16的数组。
当首次调用 map.put() 时,底层再去创建一个长度为 16 的数组。
JDK 8 底层的数组是:Node 类型;而 JDK 7 底层的数组是:Entry 类型。
JDK 7 底层结构:数组 + 链表。 JDK 8 中底层结构:数组 + 链表 + 红黑树。 当数组的某一个索引位置上的元素以链表的形式存在的数据个数大于 8 ,且当前数组的长度大于 64 时,此时此索引位置上的所有数据改为使用红黑树存储。(方便查找)
JDK 7 :新添加的元素指向已经存在的元素;JDK 8:已经存在的元素指向新添加的元素。
LinkedHashMap 是 HashMap 的子类。在 HashMap 存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。与 LinkedHashSet 类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。
HashMap 中的内部类:Node
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
}
LinkedHashMap 中的内部类:Entry
static class Entry extends HashMap.Node {
Entry before, after;
Entry(int hash, K key, V value, Node next) {
super(hash, key, value, next);
}
}
TreeMap 类不仅实现了 Map 接口,还实现了 Map 接口的子接口 java.util.SortedMap。由 TreeMap 类实现的 Map 集合,不允许键对象为 null,因为集合中的映射关系是根据键对象按照一定顺序排列的。向 TreeMap 中添加 kay-value,要求 key 必须是由同一个类创建的对象。因为要按照 key 进行排序。
TreeMap 的 Key 的排序:自然排序、定制排序。
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException。
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口。
TreeMap 判断两个两个 key 相等的标准相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回 0。
// 自然排序
@Test
public void test1() {
TreeMap map = new TreeMap();
User u1 = new User("Tom", 23);
User u2 = new User("Jerry", 32);
User u3 = new User("Jake", 20);
User u4 = new User("Rose", 18);
map.put(u1, 98);
map.put(u2, 89);
map.put(u3, 76);
map.put(u4, 100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
运行结果:
User{name='Tom', age=23}---->98
User{name='Rose', age=18}---->100
User{name='Jerry', age=32}---->89
User{name='Jake', age=20}---->76
// 定制排序:按年龄排序
@Test
public void test2() {
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User) {
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(), u2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom", 23);
User u2 = new User("Jerry", 32);
User u3 = new User("Jake", 20);
User u4 = new User("Rose", 18);
map.put(u1, 98);
map.put(u2, 89);
map.put(u3, 76);
map.put(u4, 100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
运行结果:
User{name='Rose', age=18}---->100
User{name='Jake', age=20}---->76
User{name='Tom', age=23}---->98
User{name='Jerry', age=32}---->89
Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
排序操作:(均为static方法)
查找、替换操作:(均为static方法)
同步控制:(均为static方法)
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
public static void main(String[] args) {
// 返回的 list1 即为线程安全的 List
List list1 = Collections.synchronizedList(list);
}