1.Java集合框架概述
Array存储对象具有弊端,而Java 集合像容器,可动态地把多个对象的引用放入容器中。
数组存储特点:一旦初始化,长度就确定;声明的类型,决定进行元素存储的类型。
数组存储数据弊端:长度确定不便于扩展;一旦定义好,其元素的类型就确定;提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数;存储是有序的、可重复的。---->存储数据的特点单一。
Java 集合类可用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
1.1.Collection和Map两种体系
Collection接口:单列数据,定义存取一组对象方法的集合。List:有序、可重复的集合;Set:无序、不可重复的集合。
Map接口:双列数据,保存具有映射关系“key-value对”的集合。
1.2.Collection 和 Collections
Collection:集合类上级接口,继承于他的接口主要有Set 和List。
Collections:集合类帮助类,有很多静态方法实现对各种集合的搜索、排序、线程安全化等。
1.3.集合类类型和方法
最常用集合类List和Map。 List具体实现:ArrayList和Vector,可变大小列表,适合构建、存储和操作任何类型对象的元素列表。List适用于按数值索引访问元素的情形。
Map提供更通用的元素存储方法,用于存储元素对称作"键"和"值",每个键映射到一个值。
2.Collection接口方法
Collection是List、Set和Queue的父接口,定义可用于操作Set、List和Queue集合。
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
Java5前,会丢失对象数据的具体类型,把所有对象当成Object处理;JDK 5.0增加泛型,Java集合可记住容器中对象的数据类型。
集合框架的概述:对多个数据进行存储操作的结构,简称Java容器,存储:指内存层面的存储,不涉及到持久化的存储(.tex,.jpg,.avi,数据库中)
|-----Collection接口:单列集合,用来存储一个一个的对象
|------List接口:存储有序的,可重复的数据。--》动态数组
|-----ArrayList/LinkedList/Vector
|------Set接口:存储无序的、不可重复的数据。----》高中的集合
|--HashSet/LinkedHashSet/TreeSet
|--Map接口:双列集合,用于存储一对(key—value)一对的数据 ---》高中函数y=f(x)
|-----HashMap/LinkedHashMap/TreeMap/Hashtable/Properties
名称 | 方法 |
添加 | add(Object obj) addAll(Collection coll) |
获取有效元素的个数 | int size() |
清空集合 | void clear() |
是否是空集合 | boolean isEmpty() |
是否包含某个元素 | boolean contains(Object obj):通过元素的equals方法判断是否是同一个对象。 boolean containsAll(Collection c):调用元素的equals方法来比较的。拿两个集合的元素挨个比较 |
删除 | boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素 boolean removeAll(Collection coll):取当前集合的差集 |
取两个集合的交集 | boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响 |
集合是否相等 | boolean equals(Object obj) |
转成对象数组 | Object[] toArray() |
获取集合对象的哈希值 | hashCode() |
遍历 | iterator():返回迭代器对象,用于集合遍历 |
@Test
public void test1() {
Collection collection = new ArrayList();
//add(Object e):将元素e添加到集合collection中
collection.add("AA");
collection.add("BB");
collection.add(123);
collection.add(new Date());
//size():获取添加元素的个数
System.out.println(collection.size());// 4
// addAll()(Collection coll):将coll集合中的元素添加到当前的集合中
Collection collection1 = new ArrayList();
collection1.add(456);
collection1.add("CC");
collection.addAll(collection1);
System.out.println(collection.size());// 6
System.out.println(collection);// [AA, BB, 123, Sun Nov 27 20:50:06 CST 2022, 456, CC]
// isEmpty():判断当前集合是否为空
System.out.println(collection.isEmpty());// false
// clear():清空集合元素
// collection.clear();
// System.out.println(collection.size());//0
// 省略Person类
collection.add(new Person("TOM",20));
// contains(Object obj):判断当前集合中是否包含obj
boolean contains = collection.contains(123);
System.out.println(contains);//true
// 向Collection接口的实现类的对象中添加数据obj时,要去obj所在类要重写equals()方法
System.out.println(collection.contains(new String("TOM")));//false
System.out.println(collection.contains(new Person("TOM", 20)));//true,需要重写equals方法
// containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中
Collection coll1 = Arrays.asList(123,456);
System.out.println(collection.containsAll(coll1));// true
}
@Test
public void test3(){
// remove(Object obj):从当前集合中删除obj元素
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("TOm"));
coll.add(false);
// 交集,获取当前集合和coll1集合的交集,并返回给当前集合
Collection coll1 = Arrays.asList(123,456,189);
coll.retainAll(coll1);
System.out.println(coll);// [123, 456]
// equals(Object):要返回true,需要当前集合和形参集合的元素都相同。
Collection coll2 = new ArrayList();
coll.add(123);
coll.add(456);
System.out.println(coll2.equals(coll1));// false
// hashCOde():返回当前对象的哈希值
System.out.println(coll.hashCode());// 5030299
// 集合--》数组:toArray()
Object[] obj = coll1.toArray();
for(Object arr: obj){
System.out.print(arr + "\t");//123 456 189
}
// 拓展:数组---》集合:调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"AA","BB"});
System.out.println(list);//[AA, BB]
List arr1 = Arrays.asList(new int[]{123,456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123,456});
System.out.println(arr2.size());//2
// iterator():返回Iterator接口的实例,用于遍历集合元素。
}
3.Iterator迭代器接口
遍历集合元素:Iterator对象称为迭代器(设计模式的一种),可用于遍历Collection集合中的元素;迭代器模式定义:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
Collection接口继承java.lang.Iterable接口,包含iterator()方法,所有实现Collection接口的集合类都有iterator方法,用以返回一个实现Iterator接口对象。
Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如需创建Iterator对象,则必须有一个被迭代的集合。集合对象每次调用iterator方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。迭代器Iterator遍历:内部有hasNext()和next() 方法。
调用it.next()方法前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
@Test
public void test2() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.add(new String("TOm"));
Iterator iterator = coll.iterator();
//方式一
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// 一直到最后,输出语句大于数量时,报NoSuchElementException异常
// 方式二
// for (int i = 0; i < coll.size(); i++) {
// System.out.print(iterator.next() + "\t");
// }
System.out.println();
// 方式三:推荐
while (iterator.hasNext()) {
// 123 456 com.atguigu.java.Person@78e03bb5 TOm
System.out.print(iterator.next() + "\t");
}
// 删除Iterator中的TOm
Iterator iterator1 = coll.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
if("TOm".equals(obj)){
iterator1.remove();
}
}
}
Iterator可删除集合元素,但遍历过程中通过迭代器对象remove方法,不是集合对象的remove方法。如还未调用next()或在上一次调用 next 方法后已调用了 remove 方法,再调用remove都会报IllegalStateException。
3.1.foreach 遍历集合
Java 5.0 提供foreach循环迭代Collection和数组;遍历无需获取Collection或数组长度,无需使用索引;遍历集合底层调用Iterator完成操作;foreach还可用来遍历数组。
@Test
public void test(){
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){
// 123 456 Person{name='Jerry', age=20} TOm false
System.out.print(obj + "\t");
}
System.out.println();
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
for(int i:arr){
// 1 2 3 4 5 6 7 8 9
System.out.print(i+ "\t");
}
}
4.List接口
鉴于Java数组存储数据的局限性,用List替代数组;List元素是有序且可重复,集合中每个元素都有其对应的顺序索引;List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可根据序号存取容器元素;JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
4.1.List接口方法
List除从Collection集合继承方法外,还添加了一些根据索引来操作集合元素的方法。
List接口方法 | 描述 |
void add(int index, Object ele) | 在index位置插入ele元素 |
boolean addAll(int index, Collection eles) | 从index位置开始将eles中的所有元素添加进来 |
Object get(int index) | 获取指定index位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastIndexOf(Object obj) | 返回obj在当前集合中末次出现的位置 |
Object remove(int index) | 移除指定index位置元素,并返回此元素 |
Object set(int index, Object ele) | 设置指定index位置的元素为ele |
List subList(int fromIndex, int toIndex) | 返回从fromIndex到toIndex位置的子集合 |
@Test
public void test() {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 20));
list.add(789);
// [123, 456, AA, Person{name='Tom', age=20}, 789]
System.out.println(list);
// void add(int index,Object ele):在index位置插入ele元素
list.add(1, "BB");
// [123, BB, 456, AA, Person{name='Tom', age=20}, 789]
System.out.println(list);
//boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加
List list1 = Arrays.asList(1, 2, 3);
list.addAll(list1);
System.out.println(list.size());// 9
//Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));//123
//int indexOf(Object obj):返回obj在集合中首次出现的位置,如果不存在,返回-1
System.out.println(list.indexOf("AA"));//3
//int lastIndexOf(Object obj):返回obj在集合中末次出现的位置,如果不存在,返回-1
System.out.println(list.lastIndexOf(456));//2
//Object remove(int index):移除指定index位置的元素,并返回此元素
System.out.println(list.remove(0));// 123
// [BB, 456, AA, Person{name='Tom', age=20}, 789, 1, 2, 3]
System.out.println(list);
//Object set(int index,Object ele):设置指定index位置的元素ele
list.set(1, "CC");
// [BB, CC, AA, Person{name='Tom', age=20}, 789, 1, 2, 3]
System.out.println(list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的自己和
// [AA, Person{name='Tom', age=20}]
System.out.println(list.subList(2, 4));
// [BB, CC, AA, Person{name='Tom', age=20}, 789, 1, 2, 3]
System.out.println(list);
//遍历
// 方式一
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 方式二
for (Object obj : list) {
System.out.println(obj);
}
// 方式三
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
List的ArrayList、LinkedList和Vector三个实现类比较
|-----Collection接口:单列集合,用来存储一个一个的对象 |------List接口:存储有序的,可重复的数据。--》动态数组 |-----ArrayList:作为List接口的主要实现类,线程不安全的,效率高,底层使用Object[] elementData存储 |--LinkedList:适用于频繁插入、删除的集合,比ArrayList效率高,底层用双向链表存储 |-----Vector:List古老实现类,线程安全,效率低,底层用Object[] elementData存储 ArrayList源码分析:jdk7 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); ArrayList源码分析:jdk8 ArrayList list = new ArrayList();//底层Object[] elementData初始化为{},并没有创建长度为10的数组 list.add(123);//第一次调用add时,底层才创建了长度10的数组,并将数据123添加到elementData[0] ... 扩容和jdk7一样 jdk7的ArrayList的创建类似于单例的饿汉式,而jdk8中类似于单例的懒汉式,延迟数组创建,节省内存 LinkedList的源码分析: LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null list.add(123); 其中,Node定义为: 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; } } ArrayList、LinkedList和Vector的异同 同:三个里都是实现List接口,存储数据的特点相同;存储有序的、可重复的数据
4.1.1.ArrayList
List接口的典型/主要实现类,本质上ArrayList是对象引用的一个”变长”数组。
Arrays.asList(...) 方法返回的 List 集合,既不是 ArrayList 实例,也不是Vector 实例。 Arrays.asList(...) 返回值是一个固定长度的 List 集合。
4.1.2.LinkedList
频繁插入或删除元素的操作,建议使用LinkedList类,效率较高。新增方法:void addFirst(Object obj);void addLast(Object obj);Object getFirst();Object getLast();Object removeFirst();Object removeLast()。
LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:prev变量记录前一个元素的位置,next变量记录下一个元素的位置。
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;
}
}
4.1.3.Vector
JDK1.0就有,大多数操作与ArrayList相同,区别之处在于Vector是线程安全的,但Vector比ArrayList慢,所以尽量避免使用。
新增方法:void addElement(Object obj);void insertElementAt(Object obj,int index);void setElementAt(Object obj,int index);void removeElement(Object obj);void removeAllElements()。
4.1.4.ArrayList和LinkedList
线程都不安全,比线程安全的Vector,执行效率高。ArrayList实现基于动态数组的数据结构,LinkedList实现基于链表。对于随机访问get和set,ArrayList觉得优于LinkedList,因LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因ArrayList要移动数据。
4.1.5.ArrayList和Vector
两者几乎是完全相同,区别:Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数Java程序员使用ArrayList而不是Vector,因同步完全可由程序员控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。
4.2.实现类的存储性能和特性
ArrayList和Vector都使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用synchronized方法(线程安全),性能比ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需进行前向或后向遍历,但插入数据时只需记录本项的前后项即可,所以插入速度较快。
4.3.ArrayLst和Vector的区别
同步性:Vector线程安全和同步的,而ArrayList是线程序不安全的,且不同步。数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半。
5.Set
5.1.Set接口方法
Collection子接口,set没提供额外方法,不允许包含相同元素,如把两个相同的元素加入同一个Set 集合中,则添加操作失败。Set判断两对象是否相同根据equals方法判断。
|-----Collection接口:单列集合,用来存储一个个对象 |------Set接口:存储无序的、不可重复的数据。 |----HashSet:Set接口的主要实现类,线程不安全,可以存null值 |----LinkedHashSet:HashSet子类,遍历其内部数据时,可按照添加的顺序遍历。对于频繁遍历操作,LinkedHashSet效率高于HashSet。 |----TreeSet:可按照添加对象的指定属性,进行排序。 1.Set接口中没有额外定义新方法,使用Collection中声明过的方法。 2.要求:向Set中添加数据,其所在的类一定要重写hashCode()和equals(),重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码 重写两个方法的小技巧:对象中equals()方法比较的Field,都应该用来计算 hashCodeSet:存储无序的,不同重复的数据 // hashSet 1.无序:不等随机。存储数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的hash值。 2.不可重复:保证添加的元素按照equals判断时,不能返回true。即:相同的元素只能添加一个。 二、添加元素的过程:以HashSet为例 向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即:索引位置),判断数组此位置上是否已有元素:如此位置上没有其他元素,则元素a添加成功。----》情况1 如此位置上有其他元素b(后以链表形式存在的多个元素),则比较元素a于元素b的hash值: 如果hash值不同,则元素a添加成功---》情况2 如果hash值相同,进而调用元素所在类的equals()方法:返回true,元素添加失败,返回false,则元素添加成功。----》情况3 对于添加成功的情况2和情况3而言:元素a于已经存在指定索引位置上数据以链表的方式存储。 jdk7:元素a方法数组中,指向原来的元素。 jdk8:原来的元素在数组中,指向元素a 总结:七上八下 HashSet底层:数组+链表
5.2.HashSet
Set 接口的典型实现,大多数使用Set集合时,都使用这个实现类。HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
特点:不能保证元素的排列顺序,HashSet 不是线程安全的,集合元素可是null。
判断两元素相等标准:两个对象的hashCode() 方法相等,且equals() 方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
向HashSet中添加元素的过程:当向HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)。
如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128....等)
public void test() {
Set set = new HashSet();
set.add(123);
set.add(456);
set.add("CC");
set.add(new Person("Jerry", 20));
set.add(new String("TOm"));
Iterator iterator1 = set.iterator();
while (iterator1.hasNext()) {
System.out.print(iterator1.next() + "\t");
}
}
5.2.1.重写hashCode方法原则
在程序运行时,同一个对象多次调用hashCode方法应该返回相同的值。当两个对象的 equals方法比较返回true时,这两个对象的hashCode方法的返回值也应相等。对象中用作 equals方法比较的Field,都应该用来计算hashCode值。
5.2.2.重写equals方法原则
以自定义的Customer类为例,当一个类有自己特有的“逻辑相等”概念,当改写equals的时,总要改写hashCode,根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
结论:复写equals方法时一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals中进行计算。
5.2.3.复写hashCode方法有3个原因
选择系数时要选择尽量大的系数。因如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高(减少冲突);并且31只占用5bits,相乘造成数据溢出的概率较小;31可由i*31== (i<<5)-1来表示,现很多虚拟机里面都有做相关优化。(提高算法效率);31是一个素数,素数作用就是如果用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)。
5.3.LinkedHashSet
LinkedHashSet是HashSet的子类。LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能,LinkedHashSet不允许集合元素重复。
public void test1() {
// LinkedHashSet()的使用
// LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个和后一个数据
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add(new Person("Jerry", 20));
set.add(new Person("Jerry", 20));
set.add(new String("TOm"));
Iterator iterator1 = set.iterator();
while (iterator1.hasNext()) {
// 456 123 AA Person{name='Jerry', age=20} TOm
System.out.print(iterator1.next() + "\t");
}
}
5.4.TreeSet
SortedSet接口实现类,TreeSet可确保集合元素排序,底层用红黑树结构存储数据。
1.向TreeSet中添加的数据,要求是相同类的对象 2.两种排序方式:自然排序和定制排序 3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再equals(). 3.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再equals().
新增的方法如下:Comparator comparator();Object first();Object last();Object lower(Object e);Object higher(Object e);SortedSet subSet(fromElement, toElement);SortedSet headSet(toElement);SortedSet tailSet(fromElement) 。
TreeSet和后面要讲的TreeMap采用红黑树的存储结构;特点:有序,查询速度比List快。
5.4.1.红黑树五大性质
节点必须是红色或黑色;根节点是黑色;叶子节点(外部节点、空节点)都是黑色;红色节点的子节点都是黑色(推论:红色节点的父节点都是黑色;从根节点到叶子节点的所有路径上不能有两个连续的红色节点);从任意节点到叶子节点的所有路径都包含相同数据的黑色节点。
public class Person implements Comparable {
private String name;
private int age;
public Person() {
}
public Person(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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if (o instanceof Person) {
Person person = (Person) o;
// return this.name.compareTo(person.name);
int compare = -this.name.compareTo(person.name);
if (compare != 0) {
return compare;
} else {
return Integer.compare(this.age, person.age);
}
} else {
throw new RuntimeException("输入的类型不匹配");
}
}
}
@Test
public void test2(){
TreeSet set = new TreeSet();
// 失败,不能添加不同类的对象
// set.add(456);
// set.add(123);
// set.add("AA");
// set.add(new Person("Jerry", 20));
// 举例1
// set.add(23);
// set.add(45);
// set.add(43);
// set.add(3);
// set.add(78);
// Iterator iterator = set.iterator();
// while (iterator.hasNext()){
// // 3 23 43 45 78
// System.out.print(iterator.next() + "\t");
// }
set.add(new Person("Jerry", 20));
set.add(new Person("Tom", 20));
set.add(new Person("Hi", 20));
set.add(new Person("Nd", 20));
set.add(new Person("CH", 20));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
// Person{name='Tom', age=20} Person{name='Nd', age=20} Person{name='Jerry', age=20} Person{name='Hi', age=20} Person{name='CH', age=20}
System.out.print(iterator.next() + "\t");
}
}
@Test
public void test3(){
// 定制排序
Comparator com = new Comparator() {
// 按照年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return Integer.compare(p1.getAge(), p2.getAge());
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new Person("Jerry", 20));
set.add(new Person("Tom", 23));
set.add(new Person("Hi", 25));
set.add(new Person("Nd", 50));
set.add(new Person("CH", 60));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
// Person{name='Jerry', age=20} Person{name='Tom', age=23} Person{name='Hi', age=25} Person{name='Nd', age=50} Person{name='CH', age=60}
System.out.print(iterator.next() + "\t");
}
5.5.排序
5.5.1.自然排序
TreeSet 会调用集合元素的 compareTo(Object obj) 方法比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable接口。实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。
Comparable 的典型实现:
实现方法 | 描述 |
BigDecimal、BigInteger 及所有的数值型对应的包装类 | 按它们对应的数值大小进行比较 |
Character | 按字符的 unicode值来进行比较 |
Boolean | true 对应的包装类实例大于 false 对应的包装类实例 |
String | 按字符串中字符的 unicode 值进行比较 |
Date、Time | 后边的时间、日期比前面的时间、日期大 |
向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。对于 TreeSet 集合而言,判断两个对象是否相等的唯一标准:两个对象通过 compareTo(Object obj) 方法比较返回值。
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解。
5.5.2.定制排序
TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。利用int compare(T o1,T o2)方法,比较o1和o2的大小:方法返回正整数,则表示o1大于o2;返回0,表示相等;返回负整数,表示o1小于o2。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
5.5.3.TreeSet的自然排序与定制排序
//定义一个 Employee 类。
//该类包含:private 成员变量 name,age,birthday,其中 birthday 为
public class Employee implements Comparable {
private String name;
private int age;
private MyData birthday;
public Employee(String name, int age, MyData birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public Employee() {
}
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;
}
public MyData getBirthday() {
return birthday;
}
public void setBirthday(MyData birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
// 按照姓名顺序排
@Override
public int compareTo(Object o) {
if (o instanceof Employee) {
Employee e = (Employee) o;
return this.name.compareTo(e.name);
} else {
throw new RuntimeException("类型不匹配");
}
}
}
//创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(下一章:
//TreeSet 需使用泛型来定义)
//分别按以下两种方式对集合中的元素进行排序,并遍历输出:
//使 Employee 实现 Comparable 接口,并按 name 排序
//创建 TreeSet 时传入 Comparator 对象,按生日日期的先后排序。
public class EmployeeTest {
@Test
public void test1() {
// 使用自然排序
TreeSet set = new TreeSet();
Employee e1 = new Employee("Thy", 24, new MyData(1999, 1, 5));
Employee e2 = new Employee("CH", 23, new MyData(1998, 3, 15));
Employee e3 = new Employee("Hgx", 22, new MyData(1996, 8, 23));
Employee e4 = new Employee("Lyt", 21, new MyData(1997, 5, 28));
Employee e5 = new Employee("Wty", 34, new MyData(1990, 10, 05));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
@Test
public void test2() {
// 按生日日期的先后排序
TreeSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Employee && o2 instanceof Employee) {
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
MyData b1 = e1.getBirthday();
MyData b2 = e2.getBirthday();
int minusYear = b1.getYear() - b2.getYear();
if (minusYear != 0) {
return minusYear;
}
int minusMonth = b1.getMonth() - b2.getMonth();
if (minusMonth != 0) {
return minusMonth;
}
return b1.getDay() - b2.getDay();
} else {
throw new RuntimeException("类型不匹配");
}
}
});
Employee e1 = new Employee("Thy", 24, new MyData(1999, 1, 5));
Employee e2 = new Employee("CH", 23, new MyData(1998, 3, 15));
Employee e3 = new Employee("Hgx", 22, new MyData(1996, 8, 23));
Employee e4 = new Employee("Lyt", 21, new MyData(1997, 5, 28));
Employee e5 = new Employee("Wty", 34, new MyData(1990, 10, 05));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}