第十二章 Collection集合

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值来进行比较
Booleantrue 对应的包装类实例大于 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());
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值