• 【Java 进阶】集合概述


    1. 数组和集合对比

    • 相同点
      • 都是容器,可以存储多个数据。
    • 不同点
      • 数组的长度是不可变的,集合的长度是可变的;
      • 数组可以存基本数据类型和引用数据类型;
      • 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类。

    2. 集合的体系结构

    3. Collection 概述和使用

    概述

    • 单列集合的顶层接口,它表示一组对象,这些对象也称为 Collection 的元素;
    • JDK 不提供此接口的任何直接实现,而提供其更具体的子接口(如 SetList)实现类(如上图)。

    创建对象

    • 利用多态的特性,即引用为 Collection 类型;
    • 子类对象可以是由具体实现类 ArrayList 创建。

    常用方法

    方法名说明
    boolean add(E e)添加元素
    boolean remove(Object o)从集合中移除指定的元素
    boolean removeIf(Object o)根据条件进行移除
    void clear()清空集合中的元素
    boolean contains(Object o)判断集合中是否存在指定的元素
    boolean isEmpty()判断集合是否为空
    int size()集合的长度,也就是集合中元素的个数

    示例代码

    需要注意的是,下面代码主要为了演示在接口 Collection 的各实现类中都有上述表格中的方法,且在个实现类中这些方法在使用上基本一致,因此使用多态的方式(即父类引用指向子类对象)进行了代码演示,在实际的使用中,一般都是本类型的引用指向本类型的对象。

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.function.Predicate;
    
    public class Test {
        public static void main(String[] args) {
            Collection<String> collection = new ArrayList<>();
            collection.add("William");
            collection.add("Shakespeare");
            collection.add("the");
            collection.add("greatest");
            collection.add("playwright.");
            System.out.println(collection);  // [William, Shakespeare, the, greatest, playwright.]
    
            boolean william = collection.remove("William");
            System.out.println(william);  // true
            boolean kant = collection.remove("Kant");
            System.out.println(kant);  // false
    
            // Lambda
            collection.removeIf(
                    (String string) -> {
                        return string.length() == 3;
                    }
            );
            System.out.println(collection);  // [Shakespeare, greatest, playwright.]
    
            // Anonymous
            collection.removeIf(new Predicate<String>() {
                @Override
                public boolean test(String string) {
                    return string.length() == 8;
                }
            });
            System.out.println(collection);  // [Shakespeare, playwright.]
    
            boolean shakespeare = collection.contains("Shakespeare");
            System.out.println(shakespeare);  // true
    
            boolean empty = collection.isEmpty();
            System.out.println(empty);  // false
    
            int size = collection.size();
            System.out.println(collection);  // [Shakespeare, playwright.]
            System.out.println(size);  // 2
    
            collection.clear();
            System.out.println(collection);  // []
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    补充说明

    关于上述 removeIf() 方法中 Lambda 表达式格式的理解,可以结合如下的源码:

    • ArrayList 中的 removeIf() 方法继承自 Collection 接口;
    • removeIf() 方法接收一个 Predicate 类型的形式参数;
    • Predicate 的源码显示这是一个接口,且只有一个抽象方法 test(),因此该接口满足使用 Lambda 表达式的前提条件
    • test() 抽象方法的形式参数为泛型,Lambda 表达式中的形式参数和集合中保存的元素一致即可,即为 String ,返回值需要和抽象方法 test() 的一致,即为 boolean
    • removeIf() 方法会使用一个迭代器对象遍历集合中的所有元素,针对每次遍历得到的元素,都会使用 test() 方法判断该元素是否满足删除条件,如果满足则将该元素删除。
    public interface Collection<E> extends Iterable<E> {
        
        ......
            
        /**
         * Removes all of the elements of this collection that satisfy the given
         * predicate.  Errors or runtime exceptions thrown during iteration or by
         * the predicate are relayed to the caller.
         *
         * @implSpec
         * The default implementation traverses all elements of the collection using
         * its {@link #iterator}.  Each matching element is removed using
         * {@link Iterator#remove()}.  If the collection's iterator does not
         * support removal then an {@code UnsupportedOperationException} will be
         * thrown on the first matching element.
         *
         * @param filter a predicate which returns {@code true} for elements to be
         *        removed
         * @return {@code true} if any elements were removed
         * @throws NullPointerException if the specified filter is null
         * @throws UnsupportedOperationException if elements cannot be removed
         *         from this collection.  Implementations may throw this exception if a
         *         matching element cannot be removed or if, in general, removal is not
         *         supported.
         * @since 1.8
         */
        default boolean removeIf(Predicate<? super E> filter) {
            Objects.requireNonNull(filter);
            boolean removed = false;
            final Iterator<E> each = iterator();
            while (each.hasNext()) {
                if (filter.test(each.next())) {
                    each.remove();
                    removed = true;
                }
            }
            return removed;
        }
        
        ......
    
    }
    
    
    /**
     * Represents a predicate (boolean-valued function) of one argument.
     *
     * 

    This is a functional interface * whose functional method is {@link #test(Object)}. * * @param the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); ...... // All other methods are either default or static }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    集合遍历

    迭代器介绍

    • 迭代器是集合专用的遍历方式;
    • 通过集合对象的 Iterator iterator() 方法可得到此集合元素的迭代器对象,该对象默认指向当前集合索引为 0 的元素位置。

    迭代器常用方法

    • boolean hasNext():判断当前位置是否有元素可以被取出;
    • E next():首先获取当前位置的元素,然后将迭代器对象移向下一个索引位置;
    • void remove():删除迭代器对象当前指向的元素。
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class Test {
        public static void main(String[] args) {
            Collection<String> collection = new ArrayList<>();
            collection.add("William");
            collection.add("Shakespeare");
            collection.add("the");
            collection.add("greatest");
            collection.add("playwright.");
    
            Iterator<String> iterator = collection.iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                if ("playwright.".equals(next)) {
                    iterator.remove();
                }
                System.out.println(next);
            }
    
            System.out.println(collection);  // [William, Shakespeare, the, greatest]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    迭代器源码分析

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    
        /**
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        ......
    
        /**
         * Shared empty array instance used for default sized empty instances. We
         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
         * first element is added.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access
    
        ......
    
        /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        ......
        
        /**
         * Returns an iterator over the elements in this list in proper sequence.
         *
         * 

    The returned iterator is fail-fast. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } ...... final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } ...... }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    4. List 概述和使用

    概述

    • List 是接口,不能直接使用,该接口两个主要的实现类ArrayListLinkedList
    • List 接口的实现类所创建的对象都是有序集合,这里的有序指的是存取顺序;
    • Set 接口的实现类不同,List 接口的实现类所创建的集合中允许存在重复的元素;
    • List 接口的实现类所创建的对象不仅支持通过整数索引的方式直接访问任意元素,还支持根据索引位置插入元素。

    特有方法

    上述介绍的 Collection 接口中的常用方法适用于集合体系中 Collection 继承条线的所有实现类,以下的方法仅仅适用于继承自 Collection 接口的 List 接口的实现类,如:ArrayListLinkedList

    方法名描述
    void add(int index, E element)在此集合中的指定位置插入指定的元素,原来位置索引的元素会后移
    E remove(int index)删除指定索引处的元素,返回被删除的元素
    E set(int index, E element)修改指定索引处的元素,返回被修改的元素
    E get(int index)返回指定索引处的元素

    5. ArrayList 源码分析

    下面以首先创建一个空的 ArrayList 后,再往其中添加一个元素的过程为例,分析 ArrayList 典型方法的底层源码,在这个过程中主要主要介绍一下几点事实:

    1. ArrayList 动态数组底层是通过数组的方式来实现的;
    2. 通过 ArrayList 空参构造方法 ArrayList() 创建的动态数组对象,其底层存储数据的容器是一个长度为零的数组;
    3. 在第一次尝试调用 add(E e) 方法向使用空参构造方法创建的 ArrayList 对象中添加元素时,ArrayList 底层会先将存储数据的数组容器扩容至长度为 10 ,然后再将元素添加至扩容后数组的第一个索引位置;
    4. 此后,当 ArrayList 对象底层的数组容器已满时,如果还希望调用 add(E e) 方法向其中添加元素,那么 ArrayList 对象底层的数组容器会先扩容原先的 1.5 倍,然后再向其中添加新的元素。

    空参构造创建空动态数组

    • 动态数组对象底层用于保存数据的容器是名为 elementData 成员变量,这是一个元素类型为 Object 的数组;
    • 当调用空参构造方法 ArrayList() 时,elementData 指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,后者是一个空数组。
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    
        ......
        
        /**
         * Shared empty array instance used for default sized empty instances. We
         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
         * first element is added.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        
        ......
        
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access
        
        ......
        
        /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        
        ......
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    第一次添加元素前扩容

    通过空参构造 ArrayList() 创建空的动态数组之后,在第一次尝试调用 add(E e) 方法添加元素时:

    • add(E e) 方法内部会调用 add(E e, Object[] elementData, int s) 方法;
    • 由于此时动态数组的元素个数和底层数组的个数都是 0 ,此时 elementData 会通过 grow() 方法扩容;
    • grow() 方法内部会调用 grow(int minCapacity) 方法实现具体的扩容操作(依赖于 Arrays 中的静态方法 copyOf(T[] original, int newLength));
    • 调用 grow(int minCapacity) 方法前需要知道 copyOf(T[] original, int newLength) 方法所需的 newLength 参数,即扩容后底层数组的长度;
    • 为了先得到扩容后底层数组的长度,需要首先调用 newCapacity(int minCapacity) 方法:
      • 首先尝试扩容 1.5 倍后得到新容量,由于 oldCapacity = 0 ,因此 newCapacity 扩容 1.5 倍之后还是 0 ;
      • 接着,由于第一个 if 条件成立,进入内层第一个 if 条件,该条件也成立,因此返回 DEFAULT_CAPACITY (为 10)和 minCapacity (为 1)中的较大值。
    • 调用 newCapacity(int minCapacity) 方法得到 copyOf(T[] original, int newLength) 方法所需的 newLength 参数为 10 ,至此,elementData 扩容为容量为 10 的数组;
    • 接着,方法 grow(int minCapacity)grow() 依次返回,最终在方法 add(E e, Object[] elementData, int s) 中完成元素添加,同时记录动态数组的长度的成员变量 size 递增。
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    
        ......
        
        /**
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;
        
        ......
        
        /**
         * The size of the ArrayList (the number of elements it contains).
         *
         * @serial
         */
        private int size;
        
        ......
        
        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         * @throws OutOfMemoryError if minCapacity is less than zero
         */
        private Object[] grow(int minCapacity) {
            return elementData = Arrays.copyOf(elementData,
                                               newCapacity(minCapacity));
        }
    
        private Object[] grow() {
            return grow(size + 1);
        }
        
        /**
         * Returns a capacity at least as large as the given minimum capacity.
         * Returns the current capacity increased by 50% if that suffices.
         * Will not return a capacity greater than MAX_ARRAY_SIZE unless
         * the given minimum capacity is greater than MAX_ARRAY_SIZE.
         *
         * @param minCapacity the desired minimum capacity
         * @throws OutOfMemoryError if minCapacity is less than zero
         */
        private int newCapacity(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity <= 0) {
                if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                    return Math.max(DEFAULT_CAPACITY, minCapacity);
                if (minCapacity < 0) // overflow
                    throw new OutOfMemoryError();
                return minCapacity;
            }
            return (newCapacity - MAX_ARRAY_SIZE <= 0)
                ? newCapacity
                : hugeCapacity(minCapacity);
        }
    
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE)
                ? Integer.MAX_VALUE
                : MAX_ARRAY_SIZE;
        }
    
        /**
         * Returns the number of elements in this list.
         *
         * @return the number of elements in this list
         */
        public int size() {
            return size;
        }
        
        ......
    
        /**
         * This helper method split out from add(E) to keep method
         * bytecode size under 35 (the -XX:MaxInlineSize default value),
         * which helps when add(E) is called in a C1-compiled loop.
         */
        private void add(E e, Object[] elementData, int s) {
            if (s == elementData.length)
                elementData = grow();
            elementData[s] = e;
            size = s + 1;
        }
    
    
        /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return {@code true} (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            modCount++;
            add(e, elementData, size);
            return true;
        }
        ......
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    6. LinkedList 基本使用

    概述

    LinkedList 集合的底层实现的数据结构是链表,其主要特点是查询慢,增删快。

    特有方法

    LinkedList 作为 List 接口的典型实现类,而 List 接口又继承自 Collection 接口,因此 LinkedList 支持 Collection 接口以及 List 接口中声明的所有方法。
    除此之外,LinkedList 本身还拥有一系列特有的操作方法,这些方法也是 ArrayList 类所不具备的:

    方法名说明
    public void addFirst(E e)在该列表开头插入指定的元素
    public void addLast(E e)将指定的元素追加到此列表的末尾
    public E getFirst()返回此列表中的第一个元素
    public E getLast()返回此列表中的最后一个元素
    public E removeFirst()从此列表中删除并返回第一个元素
    public E removeLast()从此列表中删除并返回最后一个元素
    import java.util.LinkedList;
    
    public class Main {
        public static void main(String[] args) {
            LinkedList<String> linkedList = new LinkedList<>();
            linkedList.addFirst("Shakespeare");
            linkedList.addFirst("William");
            System.out.println(linkedList);  // [William, Shakespeare]
    
            linkedList.addLast("is");
            linkedList.addLast("The Bard");
            System.out.println(linkedList);  // [William, Shakespeare, is, The Bard]
    
            String first = linkedList.getFirst();
            String last = linkedList.getLast();
            System.out.println(first);  // William
            System.out.println(last);  // The Bard
    
            String removeFirst = linkedList.removeFirst();
            String removeLast = linkedList.removeLast();
            System.out.println(removeFirst);  // William
            System.out.println(removeLast);  // The Bard
            System.out.println(linkedList);  // [Shakespeare, is]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    7. Set 概述和使用

    概述

    • List 一样,都是接口,不能直接创建实例对象使用;
    • 具有两个主要实现类,即 HashSetTreeSet ,实现了 Collection 接口中所有抽象方法;
    • HashSetTreeSet 两个实现类中,都不可以存储重复元素,且二者都不保证数据的存取顺序;
    • 没有索引的概念,不能使用普通 for 循环遍历,可以使用迭代器和增强 for 循环的方式进行遍历;

    TreeSet

    特点

    • 底层使用红黑树实现;
    • 可以将元素按照指定规则进行排序:
      • TreeSet():根据其元素的自然顺序进行排序;
      • TreeSet(Comparator comparator) :根据指定的比较器进行排序。

    自然排序

    • 案例需求
      • 使用 TreeSet 集合存储学生对象并通过 TreeSet 集合支持的方式进行遍历;
      • 元素遍历顺序要求按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序;
      • 如果两个学生对象的姓名和年龄都是一样,那么认为这两个学生对象相同的,第二个在尝试添加的时候不保存。
    • 实现步骤
      1. 使用空参构造创建 TreeSet 集合:
        • TreeSet 集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的。
      2. 自定义的 Student 类实现 Comparable 接口:
        • 实现自然排序主要就是让元素所属的类实现 Comparable 接口,重写 compareTo(T o) 方法。
      3. 重写接口中的 compareTo() 方法:
        • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。
    • 代码实现
    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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /**
         * @param o the object to be compared.
         * @return a negative integer, zero, or a positive integer as this object
         *         s less than, equal to, or greater than the specified object.
         */
        @Override
        public int compareTo(Student o) {
            int result = this.age - o.age;
            result = result == 0 ? this.name.compareTo(o.getName()) : result;
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    以下为测试代码:

    import java.util.TreeSet;
    
    public class Main {
        public static void main(String[] args) {
            TreeSet<Student> treeSet = new TreeSet<>();
            treeSet.add(new Student("William", 30));
            treeSet.add(new Student("Shakespeare", 400));
            treeSet.add(new Student("Socrates", 2000));
            treeSet.add(new Student("Confucius", 2000));
            treeSet.add(new Student("Confucius", 2000));
            System.out.println(treeSet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上述代码的输出结果为:

    [Student{name='William', age=30}, Student{name='Shakespeare', age=400}, Student{name='Confucius', age=2000}, Student{name='Socrates', age=2000}]
    
    • 1

    比较器排序

    • 案例需求
      • 使用 TreeSet 集合存储老师对象并通过 TreeSet 集合支持的方式进行遍历;
      • 元素遍历顺序要求按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序;
      • 如果两个老师对象的姓名和年龄都是一样,那么认为这两个老师对象相同的,第二个在尝试添加的时候不保存。
    • 实现步骤
      • 首先使用 TreeSet 的有参构造方法创建集合对象用以存储自定义对象,构造方法形参接收的是比较器对象;
      • 比较器对象实际就是 Comparator 接口的一个实现类对象,该实现类需重写 compare(T o1, T o2) 方法;
      • 重写方法时需要特别注意期望的自定义对象排序规则,即必须按照要求的主要条件和次要条件来写。
    • 代码实现
    public class Teacher {
        private String name;
        private int age;
    
        public Teacher() {
        }
    
        public Teacher(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 String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    以下为使用匿名内部类的测试代码:

    import java.util.Comparator;
    import java.util.TreeSet;
    
    public class Main {
        public static void main(String[] args) {
            TreeSet<Teacher> treeSet = new TreeSet<>(new Comparator<Teacher>() {
                @Override
                public int compare(Teacher o1, Teacher o2) {
                    int result = o1.getAge() - o2.getAge();
                    result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                    return result;
                }
            });
            treeSet.add(new Teacher("William", 30));
            treeSet.add(new Teacher("Shakespeare", 400));
            treeSet.add(new Teacher("Socrates", 2000));
            treeSet.add(new Teacher("Confucius", 2000));
            treeSet.add(new Teacher("Confucius", 2000));
            System.out.println(treeSet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以下为使用 Lambda 表达式的测试代码:

    import java.util.TreeSet;
    
    public class Main {
        public static void main(String[] args) {
            TreeSet<Teacher> treeSet = new TreeSet<>((Teacher o1, Teacher o2) -> {
                int result = o1.getAge() - o2.getAge();
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            });
            treeSet.add(new Teacher("William", 30));
            treeSet.add(new Teacher("Shakespeare", 400));
            treeSet.add(new Teacher("Socrates", 2000));
            treeSet.add(new Teacher("Confucius", 2000));
            treeSet.add(new Teacher("Confucius", 2000));
            System.out.println(treeSet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    两种比较方式小结

    • 两种比较方式小结:
      • 自然排序:待存入 TreeSet 集合的对象所述类实现 Comparable 接口,重写 compareTo() 方法,根据 compareTo() 方法返回值进行对象排序和存储;
      • 比较器排序:创建 TreeSet 对象的时候传递 Comparator 接口的实现类对象,重写 compare() 方法,根据其返回值进行对象排序和存储;
      • 在使用的时候,默认使用自然排序,当自然排序不能满足需求时,必须使用比较器排序,例如:String 类默认实现了 Comparable 接口,所以多个 String 对象存入 TreeSet 默认按照自然排序进行,如果希望实现先按照字符串长度,再按照字典序排列,则必须使用比较器排序。
    • 两种方式中关于返回值的规则:
      • 如果返回值为负数,表示待存入的元素和当前与之比较的元素是较小值,存在后者左边;
      • 如果返回值为 0 ,表示待存入的元素跟集合中当前元素重复了,不保存前者;
      • 如果返回值为正数,表示待存入的元素是较大值,存在当前元素右边。
    • 实现字符串先按长度后自然排序:
      • 基于匿名内部类:
    import java.util.Comparator;
    import java.util.TreeSet;
    
    public class Main {
        public static void main(String[] args) {
            // Anonymous class
            TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    int result = o1.length() - o2.length();
                    result = result == 0 ? o1.compareTo(o2) : result;
                    return result;
                }
            });
            treeSet.add("Dostoevsky");
            treeSet.add("Shakespeare");
            treeSet.add("Socrates");
            treeSet.add("Confucius");
            treeSet.add("Confucius");
            System.out.println(treeSet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 基于 Lambda 表达式:
    import java.util.TreeSet;
    
    public class Main {
        public static void main(String[] args) {
            TreeSet<String> treeSet = new TreeSet<>((String o1, String o2) -> {
                int result = o1.length() - o2.length();
                result = result == 0 ? o1.compareTo(o2) : result;
                return result;
            });
            treeSet.add("Dostoevsky");
            treeSet.add("Shakespeare");
            treeSet.add("Socrates");
            treeSet.add("Confucius");
            treeSet.add("Confucius");
            System.out.println(treeSet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    HashSet

    特点

    • 底层实现基于哈希表数据结构;
    • 不能保证元素的存储和取出顺序完全一致;
    • 作为 Set 接口的实现类,不可以存储重复元素;
    • 由于没有索引概念,因此不能使用普通 for 循环遍历。

    哈希值

    • 哈希值简介
      • JDK 根据对象的地址或者属性值计算出来的 int 类型的数值。
    • 哈希值获取
      • 默认使用Object 类中的 public int hashCode()方法返回对象的哈希码值。
    • 哈希值特点
      • 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的;
      • 默认情况下,不同对象的哈希值是不同的,通过重写 hashCode() 方法,可以实现让不同对象的哈希值相同,例如使得两个具有不同地址但是具有相同属性的对象具有相同的哈希值。
    • 哈希值计算:
    import java.util.Objects;
    
    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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @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 Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    JDK 1.7 及之前 HashSet 原理

    • 底层使用哈希表数据结构实现;
    • 集合中元素存储使用数组 + 链表;
    • HashSet 中添加元素的步骤大致如下:
      • 计算元素的哈希值;
      • 根据哈希值计算元素期望存入的数组索引;
      • 判断上述计算出来的数组索引处是否已有元素:
        • 如果数组索引处为空,直接将元素添加在此;
        • 如果数组索引处不为空,由于数组的每个索引处存储的元素都是一个链表,此时需要将待添加的元素和该索引对应链表的每个元素使用 equals() 方法进行比较,如果前者和后者的任意一个的比较结果相同,那么不添加该元素,否则将其添加至链表中。

    JDK 1.8 及之后 HashSet 原理

    • 底层使用哈希表数据结构;
    • 集合中元素存储使用数组 + 链表 + 红黑树:
      • 数组索引对应单元的节点个数少于等于 8 个:数组 + 链表;
      • 数组索引对应单元的节点个数多于 8 个:数组 + 红黑树。
    • HashSet 中添加元素的步骤大致如下:
      • 计算元素的哈希值;
      • 根据哈希值计算元素期望存入的数组索引;
      • 判断上述计算出来的数组索引处是否已有元素:
        • 如果数组索引处为空,直接将元素添加在此;
        • 如果数组索引处不为空:
          • 如果数组索引处是一个链表,此时需要将待添加的元素和该索引对应链表中的每个元素使用 equals() 方法进行比较,如果前者和后者的任意一个的比较结果相同,那么不添加该元素,否则将其添加至链表中;
          • 如果数组索引处是一个红黑树,此时只需要将待添加的元素和红黑树中的部分节点元素(每比较一次,减少一半的待比较节点个数)使用 equals() 方法进行比较,如果前者和后者的任意一个的比较结果相同,那么不添加该元素,否则将其添加至红黑树中。

    HashSet 集合使用案例

    • 案例需求
      • 创建一个存储学生对象的集合,存储多个学生对象;
      • 要求按照成员变量值(姓名、年龄)进行对象去重;
      • 使用 HashSet 支持的方式实现在控制台遍历该集合。
    • 代码实现
    import java.util.Objects;
    
    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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @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 Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    以下为测试代码:

    import java.util.HashSet;
    import java.util.Iterator;
    
    public class Main {
        public static void main(String[] args) {
            HashSet<Student> hashSet = new HashSet<>();
            hashSet.add(new Student("Dostoevsky", 100));
            hashSet.add(new Student("Shakespeare", 400));
            hashSet.add(new Student("Socrates", 2000));
            hashSet.add(new Student("Socrates", 2000));
            hashSet.add(new Student("Confucius", 2500));
    
            for (Student student : hashSet) {
                System.out.println(student);
            }
    
            Iterator<Student> iterator = hashSet.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 代码总结
      • 当使用 HashSet 集合存储自定义对象时,必须重写 hashCode()equals() 方法,以实现自定义对象的去重。

    8. Map 概述和使用

    特点

    • 双列集合的接口,双列集合中一个键对应一个值;
    • 双列集合中的键不可以重复,不同键对应的值可以重复。

    共有方法

    方法名称方法说明
    V put(K key, V value)添加元素或覆盖值
    V remove(Object key)根据键删除键值对
    void clear()移除所有的键值对
    boolean containsKey(Object key)判断集合是否包含指定的键
    boolean containsValue(Object value)判断集合是否包含指定的值
    boolean isEmpty()判断集合是否为空
    int size()集合的长度,也就是集合中键值对的个数
    V get(Object key)根据键获取值
    Set keySet()获取所有键的集合
    Collection values()获取所有值的集合
    Set> entrySet()获取所有键值对对象的集合

    下面对 HashMap 进行遍历的四种方式:

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BiConsumer;
    
    public class Main {
        public static void main(String[] args) {
            HashMap<String, String> hashMap = new HashMap<>();
            hashMap.put("Russia", "Dostoevsky");
            hashMap.put("England", "Shakespeare");
            hashMap.put("Greek", "Socrates");
            hashMap.put("China", "Confucius");
            System.out.println(hashMap);
    
            Set<String> keySet = hashMap.keySet();
            for (String key : keySet) {
                String value = hashMap.get(key);
                System.out.println(key + "=" + value);
            }
    
            Set<Map.Entry<String, String>> entries = hashMap.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                String key = entry.getKey();
                String value = entry.getValue();
                System.out.println(key + "=" + value);
            }
    
            hashMap.forEach(
                    (String key, String value) -> System.out.println(key + "=" + value)
            );
    
            hashMap.forEach(new BiConsumer<String, String>() {
                @Override
                public void accept(String key, String value) {
                    System.out.println(key + "=" + value);
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    HashMap

    特点

    • HashMap 底层是哈希表结构的;
    • 依赖 hashCode() 方法和 equals() 方法保证键的唯一;
    • 如果要存储的键是自定义对象,需要重写 hashCode()equals() 方法;
    • 类似于 HashSet ,根据 JDK 版本不同,集合中元素存储使用数组 + 链表或数组 + 链表 + 红黑树。

    遍历

    下面对 HashMap 进行遍历的四种方式:

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BiConsumer;
    
    public class Main {
        public static void main(String[] args) {
            HashMap<String, String> hashMap = new HashMap<>();
            hashMap.put("Russia", "Dostoevsky");
            hashMap.put("England", "Shakespeare");
            hashMap.put("Greek", "Socrates");
            hashMap.put("China", "Confucius");
            System.out.println(hashMap);
    
            Set<String> keySet = hashMap.keySet();
            for (String key : keySet) {
                String value = hashMap.get(key);
                System.out.println(key + "=" + value);
            }
    
            Set<Map.Entry<String, String>> entries = hashMap.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                String key = entry.getKey();
                String value = entry.getValue();
                System.out.println(key + "=" + value);
            }
    
            hashMap.forEach(
                    (String key, String value) -> System.out.println(key + "=" + value)
            );
    
            hashMap.forEach(new BiConsumer<String, String>() {
                @Override
                public void accept(String key, String value) {
                    System.out.println(key + "=" + value);
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    TreeMap

    特点

    • TreeMap 底层是红黑树结构;
    • 依赖自然排序或者比较器排序对键进行排序;
    • 如果键存储的是自定义对象,自定义对象所属的类需要实现 Comparable 接口并重写该接口的 compareTo() 方法,或者在创建 TreeMap 对象时候传入比较器对象。

    应用

    • 案例需求
      • 创建一个 TreeMap 集合,要求在集合中保存键值对对象;
      • 键是 Teacher 类型,值是籍贯, Teacher 成员属性是姓名和年龄;
      • 要求遍历时,键先按照老师的年龄进行排序,如果年龄相同则按照姓名进行排序。
    • 实现代码
      • 自然排序:
    public class Teacher implements Comparable<Teacher> {
        private String name;
        private int age;
    
        public Teacher() {
        }
    
        public Teacher(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 String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /**
         * @param o the object to be compared.
         * @return a negative integer, zero, or a positive integer as this object
         *         is less than, equal to, or greater than the specified object.
         */
        @Override
        public int compareTo(Teacher o) {
            int result = this.getAge() - o.getAge();
            result = result == 0 ? this.name.compareTo(o.getName()) : result;
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    以下是测试代码:

    import java.util.TreeMap;
    import java.util.function.BiConsumer;
    
    public class Main {
        public static void main(String[] args) {
            TreeMap<Teacher, String> treeMap = new TreeMap<>();
            treeMap.put(new Teacher("Dostoevsky", 100), "Russia");
            treeMap.put(new Teacher("Shakespeare", 400), "England");
            treeMap.put(new Teacher("Socrates", 2000), "Greek");
            treeMap.put(new Teacher("Confucius", 2000), "China");
    
            treeMap.forEach(new BiConsumer<Teacher, String>() {
                @Override
                public void accept(Teacher teacher, String s) {
                    System.out.println(teacher + " was from " + s);
                }
            });
    
            treeMap.forEach((teacher, s) -> System.out.println(teacher + " was from " + s));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 比较器排序:
    import java.util.Comparator;
    import java.util.TreeMap;
    import java.util.function.BiConsumer;
    
    public class Main {
        public static void main(String[] args) {
            TreeMap<Teacher, String> treeMap = new TreeMap<>(new Comparator<Teacher>() {
                @Override
                public int compare(Teacher o1, Teacher o2) {
                    int result = o2.getAge() - o1.getAge();
                    result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                    return result;
                }
            });
            treeMap.put(new Teacher("Dostoevsky", 100), "Russia");
            treeMap.put(new Teacher("Shakespeare", 400), "England");
            treeMap.put(new Teacher("Socrates", 2000), "Greek");
            treeMap.put(new Teacher("Confucius", 2000), "China");
    
            treeMap.forEach(new BiConsumer<Teacher, String>() {
                @Override
                public void accept(Teacher teacher, String s) {
                    System.out.println(teacher + " was from " + s);
                }
            });
    
            treeMap.forEach((teacher, s) -> System.out.println(teacher + " was from " + s));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    注意

    不管是用自然排序还是使用比较器排序,在实现接口时都需要指定泛型 Teacher

  • 相关阅读:
    字符设备和杂项设备总结
    一文全面介绍JWT框架知识
    线程的状态
    Linux系统删除文件夹命令
    Centos7 安装 Docker
    游戏中的-雪花算法
    Autobus 方法记录
    【Leetcode刷题Python】生词本单词整理
    14.Java RMI学习以及制作远程服务
    基于Gradio/Stable Diffusion/Midjourney的AIGC自动图像绘画生成软件 - Fooocus
  • 原文地址:https://blog.csdn.net/weixin_37780776/article/details/126086741