第一模块:集合基础 —— 搞懂 “顶层规范” 与 “遍历逻辑”

1. 集合进阶 - 01:单列集合顶层接口 Collection

首先要明确:集合是 “存储多个数据的容器”,但它和数组的本质区别是 “动态扩容”(数组长度固定,集合长度可自动调整)。而 Collection 就是所有 单列集合(只存单个元素,如 List/Set) 的 “顶层接口”—— 它定义了所有单列集合的通用行为,相当于 “制定了一套规则”。

① Collection 接口的核心方法(必须掌握)

这些方法是所有单列集合(ArrayList、HashSet 等)都能直接用的,我们用代码案例演示:

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        // 1. 创建Collection实现类对象(接口不能直接new,用ArrayList实例化)
        Collection<String> coll = new ArrayList<>();

        // 2. 核心方法使用
        coll.add("张三"); // 添加元素,返回true(添加成功)
        coll.add("李四");
        coll.add("王五");
        System.out.println(coll); // 输出:[张三, 李四, 王五]

        System.out.println(coll.size()); // 获取元素个数,输出:3
        System.out.println(coll.contains("李四")); // 判断是否包含某个元素,输出:true

        coll.remove("王五"); // 删除元素,返回true(删除成功)
        System.out.println(coll); // 输出:[张三, 李四]

        coll.clear(); // 清空所有元素
        System.out.println(coll.isEmpty()); // 判断是否为空,输出:true
    }
}

② 关键注意点

  • Collection 是接口,不能直接 new Collection(),必须用它的实现类(如 ArrayList、HashSet)实例化;
  • 方法的返回值要重视:add()/remove() 返回布尔值,表示 “操作是否成功”(比如向 Set 中添加重复元素,add() 会返回 false);
  • 泛型 <String>:指定集合中只能存 String 类型(Java 5 + 特性,避免类型转换异常,后面会详细讲泛型)。

2. 集合进阶 - 02:迭代器(Iterator)—— 集合的 “专属遍历工具”

当我们需要 “逐个访问集合中的元素” 时,就需要 迭代器(Iterator)—— 它是 Collection 接口的 “内部工具”,所有单列集合都能通过 iterator() 方法获取迭代器对象。

① 迭代器的核心方法

迭代器只有 3 个核心方法,逻辑是 “先判断、再获取、后删除”:

方法作用
hasNext()判断 “下一个位置是否有元素”,返回布尔值
next()获取 “下一个位置的元素”,并移动指针
remove()删除 “上一个被 next () 获取的元素”

② 代码案例:用迭代器遍历 Collection

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("苹果");
        coll.add("香蕉");
        coll.add("橙子");

        // 1. 获取迭代器对象
        Iterator<String> it = coll.iterator();

        // 2. 遍历:hasNext()判断,next()获取
        while (it.hasNext()) { // 先判断“有没有下一个”
            String fruit = it.next(); // 再获取“下一个元素”,指针后移
            System.out.println(fruit); // 输出:苹果、香蕉、橙子

            // 3. (可选)删除元素:只能删“上一个next()获取的元素”
            if ("香蕉".equals(fruit)) {
                it.remove(); // 注意:不能用coll.remove()!会触发ConcurrentModificationException
            }
        }

        System.out.println(coll); // 输出:[苹果, 橙子]
    }
}

③ 避坑重点:ConcurrentModificationException(并发修改异常)

为什么不能在迭代器遍历的时候,用集合的 remove() 方法删元素?

  • 迭代器有一个 “修改计数器”:当迭代器创建后,若集合通过自身方法(如 coll.remove())修改了元素个数,计数器会变化;
  • 迭代器每次 next() 时都会检查计数器:如果发现 “迭代器记录的计数器” 和 “集合当前的计数器” 不一致,就会抛出异常。
  • 正确做法:用迭代器自身的 it.remove() 方法,它会同步更新计数器,避免异常。

3. 集合进阶 - 03:增强 for 循环 & Lambda 表达式 —— 更简洁的遍历

迭代器的语法有点繁琐,Java 提供了更简单的遍历方式:增强 for 循环(foreach)Lambda 表达式(Java 8+),它们本质上还是 “迭代器的语法糖”。

① 增强 for 循环(foreach)

语法:for (元素类型 变量名 : 集合/数组) { 循环体 }
特点:简洁,适合 “只遍历、不修改” 的场景(修改元素会有问题,比如遍历 List 时改元素值可以,但删元素仍会抛异常)。

代码案例:

Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("Python");
coll.add("Go");

// 增强for遍历集合
for (String lang : coll) {
    System.out.println(lang); // 输出:Java、Python、Go
}

// 增强for也能遍历数组(本质和集合无关,只是语法统一)
String[] arr = {"张三", "李四"};
for (String name : arr) {
    System.out.println(name); // 输出:张三、李四
}

② Lambda 表达式(Java 8+)

Collection 接口在 Java 8 中新增了 forEach() 方法,接收一个 “消费型接口(Consumer)” 参数,配合 Lambda 表达式可以实现 “一行遍历”。

语法:集合.forEach(元素 -> 遍历操作)
代码案例:

Collection<String> coll = new ArrayList<>();
coll.add("北京");
coll.add("上海");
coll.add("广州");

// Lambda表达式遍历
coll.forEach(city -> System.out.println(city)); // 输出:北京、上海、广州

// 简化版(方法引用):当“遍历操作是调用一个已有的静态方法/实例方法”时
coll.forEach(System.out::println); // 效果和上面一样,更简洁

③ 三种遍历方式对比

遍历方式优点缺点适用场景
迭代器(Iterator)支持遍历中删除元素语法繁琐需要删除元素的场景
增强 for(foreach)语法简洁不支持删除元素,不能获取索引(List)仅遍历、不修改的场景
Lambda 表达式最简洁(一行代码)不支持删除元素,可读性依赖 Lambda 熟练度Java 8+,简单遍历场景

4. 集合进阶 - 04:List 中常见的方法和五种遍历方式

ListCollection 的子接口,它的核心特点是 “有序(元素存入顺序 = 取出顺序)、可重复(允许存多个相同元素)、支持索引(像数组一样通过下标访问)”—— 这也是它和 Set 的核心区别。

① List 的专属方法(比 Collection 多的方法)

因为支持索引,List 新增了一批 “基于索引操作” 的方法,核心如下:

import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a"); // 末尾添加元素(Collection的方法)
        list.add(1, "b"); // 专属:在索引1的位置插入元素
        System.out.println(list); // 输出:[a, b]

        list.set(1, "c"); // 专属:修改索引1的元素值
        System.out.println(list); // 输出:[a, c]

        String elem = list.get(0); // 专属:获取索引0的元素
        System.out.println(elem); // 输出:a

        int index = list.indexOf("c"); // 专属:查找元素第一次出现的索引
        System.out.println(index); // 输出:1

        list.remove(0); // 专属:删除索引0的元素(注意和Collection的remove(元素)区分)
        System.out.println(list); // 输出:[c]
    }
}

② List 的五种遍历方式(重点!)

因为 List 支持索引,比 Collection 多了 “普通 for 循环” 和 “ListIterator 双向遍历” 两种方式,共 5 种:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListTraverse {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");

        // 1. 普通for循环(List专属,利用索引)
        System.out.println("=== 普通for ===");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        // 2. 增强for循环
        System.out.println("=== 增强for ===");
        for (String name : list) {
            System.out.println(name);
        }

        // 3. 迭代器(Iterator)
        System.out.println("=== Iterator ===");
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // 4. ListIterator(List专属,双向遍历)
        System.out.println("=== ListIterator(双向) ===");
        ListIterator<String> lit = list.listIterator(list.size()); // 从末尾开始
        while (lit.hasPrevious()) { // 反向判断:“上一个位置有没有元素”
            String name = lit.previous(); // 反向获取:“上一个元素”,指针前移
            System.out.println(name); // 输出:王五、李四、张三(反向遍历)
        }

        // 5. Lambda表达式
        System.out.println("=== Lambda ===");
        list.forEach(System.out::println);
    }
}

第二模块:数据结构基础 —— 理解 “集合底层的本质”

学完 List 的用法,你可能会问:为什么 ArrayList 查询快、增删慢?为什么 LinkedList 增删快、查询慢? 答案在 “数据结构” 里 —— 集合的性能完全由底层数据结构决定。

集合进阶 - 05:核心数据结构(栈、队列、数组、链表)

我们先讲 4 种最基础的数据结构,它们是 ArrayList、LinkedList 的底层实现:

1. 数组(ArrayList 底层)

  • 结构特点:连续的内存空间,每个元素有唯一的 “索引(下标)”,像一排整齐的柜子,每个柜子有编号。
  • 核心性能:

    • 查询快:通过索引直接定位元素,时间复杂度 O(1)(不管数组多大,找索引 5 的元素只要 1 步);
    • 增删慢:

      • 末尾增删:快(直接在最后加 / 删,O (1));
      • 中间增删:慢(要移动后面所有元素,比如在索引 1 插入元素,后面的元素都要后移,数组越大越慢,O (n))。
  • 缺点:长度固定(ArrayList 的 “动态扩容” 本质是 “创建新数组、复制旧元素”,不是真的在原数组上扩容)。

2. 链表(LinkedList 底层)

  • 结构特点:不连续的内存空间,每个元素(节点)包含 “数据” 和 “指针(指向前后节点)”,像一串糖葫芦,每个糖葫芦通过绳子连起来。

    • 单向链表:每个节点只有 “下一个节点的指针”;
    • 双向链表(LinkedList 用的是这个):每个节点有 “上一个节点的指针” 和 “下一个节点的指针”。
  • 核心性能:

    • 查询慢:没有索引,要找某个元素必须从 “头节点” 开始遍历,数组越大越慢,时间复杂度 O(n)
    • 增删快:

      • 首尾增删:快(只要改首尾节点的指针,O (1));
      • 中间增删:只要找到目标节点,改前后节点的指针即可(不需要移动其他元素,O (1))—— 前提是 “已经找到目标节点”(找节点的过程还是 O (n))。
  • 优点:长度动态(不需要扩容,新增节点直接连在链表上)。

3. 栈(Stack)——“先进后出”(FILO)

  • 结构特点:像一个杯子,只能从 “顶端” 添加和取出元素(类比:叠盘子,只能从最上面放 / 拿)。
  • 核心操作:

    • push():向栈顶添加元素(压栈);
    • pop():从栈顶删除并返回元素(弹栈);
    • peek():查看栈顶元素(不删除)。
  • 底层实现:LinkedList 可以实现栈(用 addFirst() 模拟 push,removeFirst() 模拟 pop)。

4. 队列(Queue)——“先进先出”(FIFO)

  • 结构特点:像一条排队的队伍,只能从 “队尾” 添加元素,从 “队头” 取出元素(类比:买奶茶排队,先到先得)。
  • 核心操作:

    • offer():向队尾添加元素(入队);
    • poll():从队头删除并返回元素(出队);
    • peek():查看队头元素(不删除)。
  • 底层实现:LinkedList 可以实现队列(用 addLast() 模拟 offer,removeFirst() 模拟 poll)。

总结与下节预告

到这里,我们已经掌握了:

  1. 集合的顶层接口 Collection 的核心方法;
  2. 三种遍历方式(迭代器、增强 for、Lambda)的用法与避坑点;
  3. List 的专属方法与五种遍历方式;
  4. 数组、链表、栈、队列的结构与性能差异(这是理解 ArrayList 和 LinkedList 的关键)。

下一节,我们会深入 ArrayList 源码分析—— 看看它的 “动态扩容” 是怎么实现的?初始容量为什么是 10?以及 “数组结构” 在源码中是如何体现的,彻底搞懂 “ArrayList 为什么查询快”。

上一节我们深入了 ArrayList、LinkedList 的源码和迭代器原理,还掌握了泛型类、方法与接口的基础用法。这一节将完成泛型进阶(通配符),拆解红黑树这一核心数据结构,并讲解HashSetLinkedHashSet的实现逻辑,建立 “数据结构→集合实现→使用场景” 的完整链路。

第四模块:泛型进阶 —— 通配符与综合应用

1. 集合进阶 - 10:泛型的通配符(?

泛型的核心是 “类型安全”,但当需要 “兼容多种泛型类型” 时(比如接收任意泛型的 List),直接写List<Object>会有问题 —— 因为泛型不具备协变性List<String>不是List<Object>的子类)。此时需要用泛型通配符? 解决。

① 通配符的基本用法:List<?>(任意类型的 List)

List<?>表示 “未知类型的 List”,可以接收任何泛型类型的 List,但有两个限制:

  • 不能向集合中添加元素(除了null):因为不知道具体类型,添加任何类型都可能不安全;
  • 可以从集合中获取元素:获取的元素类型会被自动转为Object

代码案例

import java.util.ArrayList;
import java.util.List;

public class WildcardBasic {
    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        strList.add("Java");
        List<Integer> intList = new ArrayList<>();
        intList.add(2024);

        // 调用方法:接收任意泛型的List
        printList(strList); // 输出:[Java]
        printList(intList); // 输出:[2024]
    }

    // 泛型通配符:List<?> 接收任意类型的List
    public static void printList(List<?> list) {
        // 允许获取元素(转为Object)
        for (Object obj : list) {
            System.out.println(obj);
        }

        // 不允许添加元素(除了null):编译报错
        // list.add("hello"); // 错误:无法确定list的具体类型,添加String可能不安全
        list.add(null); // 允许:null是所有类型的默认值
    }
}

② 带上限的通配符:List<? extends E>(E 及其子类)

当需要 “限制泛型类型为某个类的子类” 时,用? extends E(比如 “只接收 Number 或其子类的 List”)。
核心特点

  • 可以获取元素:获取的元素类型为E(父类类型,安全);
  • 仍不能添加元素:因为无法确定具体是哪个子类(比如List<? extends Number>可能是List<Integer>List<Double>,添加IntegerDouble的 List 会出错)。

代码案例

public class WildcardUpperBound {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        List<Double> doubleList = new ArrayList<>();
        doubleList.add(3.14);

        // 允许接收Number的子类(Integer、Double)
        calculateSum(intList); // 输出:10.0
        calculateSum(doubleList); // 输出:3.14

        // 不允许接收非Number子类(如String):编译报错
        // List<String> strList = new ArrayList<>();
        // calculateSum(strList); // 错误:String不是Number的子类
    }

    // 带上限的通配符:? extends Number(只接收Number及其子类)
    public static double calculateSum(List<? extends Number> list) {
        double sum = 0.0;
        // 允许获取元素(转为Number类型,调用Number的方法)
        for (Number num : list) {
            sum += num.doubleValue(); // 所有Number子类都有doubleValue()方法
        }
        return sum;
    }
}

③ 带下限的通配符:List<? super E>(E 及其父类)

当需要 “限制泛型类型为某个类的父类” 时,用? super E(比如 “只接收 Integer 或其父类的 List”)。
核心特点

  • 可以添加元素:添加E类型或其子类的元素(安全,因为父类 List 可以接收子类对象);
  • 获取元素时只能转为Object:因为无法确定具体是哪个父类。

代码案例

import java.util.ArrayList;
import java.util.List;

public class WildcardLowerBound {
    public static void main(String[] args) {
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();

        // 允许接收Integer的父类(Number、Object)
        addInteger(numList); // numList变为:[100, 200]
        addInteger(objList); // objList变为:[100, 200]

        // 不允许接收Integer的子类(如Integer的子类不存在,或自定义子类):编译报错
        // List<Integer> intList = new ArrayList<>();
        // addInteger(intList); // 错误:Integer不是Integer的父类(下限是E,需≥E)
    }

    // 带下限的通配符:? super Integer(只接收Integer及其父类)
    public static void addInteger(List<? super Integer> list) {
        // 允许添加Integer及其子类(这里Integer没有子类,直接加Integer)
        list.add(100);
        list.add(200);

        // 获取元素时只能转为Object
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

④ 通配符使用场景总结

通配符类型语法能添加元素?能获取元素?适用场景
任意类型List<?>只能加null能,转为Object仅遍历集合,不修改
上限(E 及其子类)List<? extends E>不能(除null能,转为E读取集合元素(如计算总和)
下限(E 及其父类)List<? super E>能,加 E 或其子类能,转为Object修改集合元素(如添加特定类型)

第五模块:高级数据结构 —— 红黑树(TreeSet/TreeMap 底层)

在学习 Set 集合前,必须先理解红黑树—— 因为TreeSetTreeMap的底层就是红黑树,它决定了这两个集合 “有序、无重复” 的特性。

1. 集合进阶 - 11~13:红黑树的核心原理

红黑树是一种自平衡的二叉查找树,它通过 5 条规则保证 “树的高度始终接近 log₂n”,从而让查询、插入、删除的时间复杂度稳定在 O (logn)(避免二叉查找树退化为链表的 O (n) 问题)。

① 红黑树的 5 条核心规则(必须牢记)

  1. 节点要么是红色,要么是黑色
  2. 根节点必须是黑色
  3. 所有叶子节点(NIL 节点,空节点)必须是黑色
  4. 如果一个节点是红色,那么它的两个子节点必须是黑色(即 “红色节点不连续”,避免出现父 - 子 - 孙都是红色的情况);
  5. 从任意节点到其所有叶子节点的路径上,黑色节点的数量相同(称为 “黑高相等”,保证树的平衡)。

示意图(简化:省略 NIL 叶子节点):

        10(黑)  // 根节点黑
       /   \
      5(红) 15(红) // 红节点的子节点必须黑
     / \     / \
    3(黑)7(黑)12(黑)20(黑) // 所有路径黑高相同(2个黑节点:10→5→3 或 10→15→20)

② 红黑树的 “自平衡” 机制:插入 / 删除后的调整

当插入或删除节点后,红黑树的规则可能被破坏(比如出现连续红节点、黑高不相等),此时需要通过两种操作恢复平衡:

  1. 旋转:分为左旋和右旋,本质是 “调整节点的父子关系”,不改变二叉查找树的有序性(左子树 < 根 < 右子树)。

    • 左旋:将节点的右子树提升为父节点,原父节点降为左子树;
    • 右旋:将节点的左子树提升为父节点,原父节点降为右子树。
  2. 变色:将节点的颜色从红改为黑,或从黑改为红(配合旋转使用,恢复红黑规则)。

③ 红黑树的核心优势

  • 平衡性能好:即使频繁插入 / 删除,树的高度仍接近 log₂n,避免退化;
  • 效率稳定:查询、插入、删除的时间复杂度均为 O (logn),比普通二叉查找树更可靠;
  • 适合有序场景:因为是二叉查找树,中序遍历(左→根→右)可得到 “从小到大” 的有序序列(这是 TreeSet/TreeMap “有序” 的根源)。

第六模块:Set 集合 ——HashSet 与 LinkedHashSet

Set 集合的核心特性是 “无序、无重复”(TreeSet 是有序的特例),底层依赖 Map 实现(Set 的元素作为 Map 的 key,value 用一个固定的PRESENT对象填充)。

1. 集合进阶 - 14:HashSet 详解

HashSet是最常用的 Set 实现类,底层是HashMap,核心特性是 “无序、无重复、允许存 null”。

① 核心成员变量(依赖 HashMap)

public class HashSet<E> extends AbstractSet<E> implements Set<E> {
    // 底层存储结构:HashMap(Set的元素作为HashMap的key)
    private transient HashMap<E, Object> map;

    // 固定的value:所有key对应的value都是这个对象,节省空间
    private static final Object PRESENT = new Object();
}

② 核心方法(本质是调用 HashMap 的方法)

HashSet 方法底层调用的 HashMap 方法作用
add(E e)map.put(e, PRESENT) != null添加元素:若 key 不存在(返回 null),则添加成功;若存在(返回旧 value),则添加失败(保证无重复)
remove(Object o)map.remove(o) == PRESENT删除元素:若 key 存在且 value 是 PRESENT,则删除成功
contains(Object o)map.containsKey(o)判断元素是否存在
size()map.size()获取元素个数
clear()map.clear()清空所有元素

③ 关键特性(继承自 HashMap)

  • 无序性:元素的存储顺序不保证与添加顺序一致(因为 HashMap 的 key 存储在哈希表中,位置由哈希值决定);
  • 无重复性:依赖 HashMap 的 key 不可重复(若两个元素的hashCode()相等且equals()返回 true,则视为同一个元素,添加失败);
  • 允许存 null:因为 HashMap 的 key 可以为 null(仅允许一个 null,因为 key 不可重复);
  • 线程不安全:和 HashMap 一样,无同步锁,多线程修改可能出现并发问题;
  • 性能:添加、删除、查询的时间复杂度均为 O (1)(哈希表无冲突时),冲突严重时会退化到 O (n)(但 HashMap 会用红黑树优化,冲突严重时变为 O (logn))。

2. 集合进阶 - 14:LinkedHashSet 详解

LinkedHashSetHashSet的子类,底层是LinkedHashMap,核心特性是 “有序(插入顺序)、无重复”。

① 核心原理(继承自 HashSet,重写底层 Map)

public class LinkedHashSet<E> extends HashSet<E> implements Set<E> {
    // 构造方法:调用HashSet的构造方法,传入一个LinkedHashMap
    public LinkedHashSet() {
        super(new LinkedHashMap<>()); // 底层用LinkedHashMap存储
    }
}

LinkedHashMap 的底层是 “哈希表 + 双向链表”:

  • 哈希表:保证添加、删除、查询的效率(O (1));
  • 双向链表:记录元素的 “插入顺序”,遍历集合时按插入顺序返回(这是 LinkedHashSet “有序” 的根源)。

② 关键特性(对比 HashSet)

特性HashSetLinkedHashSet
底层结构HashMap(哈希表)LinkedHashMap(哈希表 + 双向链表)
有序性无序(哈希值决定顺序)有序(按插入顺序)
无重复性是(依赖 HashMap key 不可重复)是(继承自 HashSet)
允许存 null是(仅一个)是(仅一个)
线程安全
性能略高(无链表维护开销)略低(需维护双向链表)
遍历顺序随机(不固定)插入顺序

③ 使用场景对比

  • 若只需 “无重复、无序”,追求极致性能 → 用HashSet
  • 若需要 “无重复、有序(插入顺序)”,可接受轻微性能损耗 → 用LinkedHashSet
  • 若需要 “无重复、有序(自然排序 / 自定义排序)” → 用TreeSet(下一节讲解)。

总结与下节预告

到这里,我们已经掌握了:

  1. 泛型通配符的三种用法(任意类型、上限、下限)及适用场景;
  2. 红黑树的 5 条规则与自平衡机制(TreeSet/TreeMap 的底层基础);
  3. HashSet(底层 HashMap,无序无重复)和 LinkedHashSet(底层 LinkedHashMap,有序无重复)的实现与特性。

下一节,我们将讲解TreeSet(红黑树实现,有序无重复),包括它的两种排序方式(自然排序、自定义排序),并通过综合练习巩固 Set 集合的用法。

上一节我们掌握了泛型通配符、红黑树原理,以及 HashSet/LinkedHashSet 的实现。这一节将聚焦 TreeSet 的排序机制(红黑树的实际应用)、Map 集合的核心用法(双列集合顶层规范),并深入HashMapTreeMap的源码细节,彻底打通 “数据结构→集合实现→实战应用” 的链路。

第七模块:TreeSet—— 基于红黑树的有序 Set

TreeSet是 Set 集合的有序实现,底层依赖TreeMap(红黑树),核心特性是 “有序(自然排序 / 自定义排序)、无重复”,本质是通过红黑树的 “二叉查找” 特性保证元素有序,通过 “key 不可重复” 保证无重复。

1. 集合进阶 - 15~16:TreeSet 的两种排序方式

TreeSet 的 “有序” 不是 “插入顺序”,而是 “元素的比较顺序”,分为两种实现方式:自然排序自定义排序

① 方式 1:自然排序(实现 Comparable 接口)

自然排序是 TreeSet 的默认排序方式,要求集合中的元素必须实现java.lang.Comparable接口,并重写compareTo(T o)方法 —— 该方法定义了元素的 “比较规则”,返回值决定元素的顺序:

  • 返回 负数:当前元素排在参数元素的前面
  • 返回 0:当前元素与参数元素 “相等”,TreeSet 会视为同一个元素,不添加
  • 返回 正数:当前元素排在参数元素的后面

代码案例:自定义对象的自然排序

import java.util.TreeSet;

// 1. 自定义类实现Comparable接口,指定比较类型为Student
class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 2. 重写compareTo方法:按年龄升序排序(年龄相同则按姓名升序)
    @Override
    public int compareTo(Student o) {
        // 第一步:按年龄比较
        int ageDiff = this.age - o.age;
        if (ageDiff != 0) {
            return ageDiff; // 年龄不同:返回差值(正数→当前元素大,排后面)
        }
        // 第二步:年龄相同,按姓名的字典序比较(String已实现Comparable)
        return this.name.compareTo(o.name);
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

public class TreeSetNaturalSort {
    public static void main(String[] args) {
        TreeSet<Student> treeSet = new TreeSet<>();
        // 添加元素:TreeSet会自动调用compareTo方法排序
        treeSet.add(new Student("张三", 20));
        treeSet.add(new Student("李四", 18));
        treeSet.add(new Student("王五", 20));
        treeSet.add(new Student("李四", 18)); // 与已存在的“李四18”相等,添加失败

        // 遍历:按年龄升序,年龄相同按姓名升序
        for (Student s : treeSet) {
            System.out.println(s);
        }
        // 输出结果:
        // Student{name='李四', age=18}
        // Student{name='王五', age=20}
        // Student{name='张三', age=20}
    }
}

② 方式 2:自定义排序(传入 Comparator 比较器)

若元素未实现 Comparable 接口,或想覆盖默认的自然排序规则,可在创建 TreeSet 时传入java.util.Comparator接口的实现类(或 Lambda 表达式),通过compare(T o1, T o2)方法定义排序规则:

  • 返回 负数:o1 排在 o2 的前面
  • 返回 0:o1 与 o2 相等,不添加;
  • 返回 正数:o1 排在 o2 的后面

代码案例:用 Lambda 实现自定义排序

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetCustomSort {
    public static void main(String[] args) {
        // 1. 创建TreeSet时传入Comparator(Lambda表达式):按年龄降序排序
        TreeSet<Student> treeSet = new TreeSet<>((s1, s2) -> {
            // 自定义规则:按年龄降序,年龄相同按姓名降序
            int ageDiff = s2.age - s1.age; // 注意:s2 - s1 是降序
            if (ageDiff != 0) {
                return ageDiff;
            }
            return s2.name.compareTo(s1.name); // 姓名降序
        });

        // 添加元素:按自定义规则排序
        treeSet.add(new Student("张三", 20));
        treeSet.add(new Student("李四", 18));
        treeSet.add(new Student("王五", 20));

        // 遍历:按年龄降序,年龄相同按姓名降序
        for (Student s : treeSet) {
            System.out.println(s);
        }
        // 输出结果:
        // Student{name='张三', age=20}
        // Student{name='王五', age=20}
        // Student{name='李四', age=18}
    }
}

③ 关键注意点

  • 排序优先级:自定义排序(Comparator)的优先级高于自然排序(Comparable)—— 若同时存在,TreeSet 会优先使用 Comparator;
  • 无重复的判定:无论是哪种排序,只要compareTocompare方法返回 0,TreeSet 就视为元素重复,不会添加;
  • null 的处理:TreeSet 不允许添加 null 元素(会抛出NullPointerException)—— 因为无法对 null 进行比较。

第八模块:Map 集合 —— 双列集合的顶层规范

Map 是 Java 集合框架中的双列集合(存储 “键值对”key-value),与单列集合(Collection)的核心区别是 “通过 key 定位 value”,key 不可重复(保证唯一性),value 可重复。

1. 集合进阶 - 17(上):Map 的核心特点与常用 API

① Map 的核心特点

  • 键值对映射:每个 key 对应一个 value,通过 key 可快速获取 value(类似字典的 “字→释义”);
  • key 唯一性:若添加相同 key 的键值对,新的 value 会覆盖旧的 value;
  • value 可重复:不同 key 可对应相同的 value;
  • 无序 / 有序:

    • 无序:HashMap、HashTable(底层哈希表);
    • 有序:LinkedHashMap(按插入顺序)、TreeMap(按 key 排序)。

② Map 的常用 API(以 HashMap 为例)

Map 接口定义了大量操作键值对的方法,核心如下:

import java.util.HashMap;
import java.util.Map;

public class MapApiDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        // 1. 添加键值对:put(key, value),返回旧value(若key不存在则返回null)
        map.put("张三", 90);
        map.put("李四", 85);
        map.put("王五", 95);
        Integer oldValue = map.put("张三", 92); // 覆盖“张三”的value,oldValue=90
        System.out.println("旧value:" + oldValue); // 输出:旧value:90

        // 2. 获取value:get(key),key不存在返回null
        Integer score = map.get("李四");
        System.out.println("李四的分数:" + score); // 输出:李四的分数:85

        // 3. 判断key/value是否存在
        boolean hasKey = map.containsKey("王五");
        boolean hasValue = map.containsValue(95);
        System.out.println("是否包含key'王五':" + hasKey); // 输出:true
        System.out.println("是否包含value95:" + hasValue); // 输出:true

        // 4. 删除键值对:remove(key),返回被删除的value
        Integer removedValue = map.remove("王五");
        System.out.println("删除的value:" + removedValue); // 输出:删除的value:95

        // 5. 其他常用方法
        System.out.println("键值对个数:" + map.size()); // 输出:2
        System.out.println("是否为空:" + map.isEmpty()); // 输出:false
        map.clear(); // 清空所有键值对
        System.out.println("清空后是否为空:" + map.isEmpty()); // 输出:true
    }
}

③ Map 的三种遍历方式

Map 没有继承 Iterable 接口,不能直接用增强 for 或迭代器遍历,需通过 “获取 key 集合”“获取键值对集合” 或 “Lambda 表达式” 三种方式遍历:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("张三", 90);
        map.put("李四", 85);
        map.put("王五", 95);

        // 方式1:键找值(先获取key集合,再遍历key找value)
        System.out.println("=== 键找值 ===");
        Set<String> keySet = map.keySet(); // 获取所有key的Set集合
        for (String key : keySet) {
            Integer value = map.get(key);
            System.out.println(key + ":" + value);
        }

        // 方式2:键值对(直接获取entry集合,遍历entry)
        System.out.println("=== 键值对 ===");
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); // 获取所有entry的Set集合
        for (Map.Entry<String, Integer> entry : entrySet) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + ":" + value);
        }

        // 方式3:Lambda表达式(Java 8+,最简洁)
        System.out.println("=== Lambda ===");
        map.forEach((key, value) -> System.out.println(key + ":" + value));
    }
}

2. 集合进阶 - 17(下)~18:Map 核心实现类详解

Map 的常用实现类有 4 个:HashMap、LinkedHashMap、TreeMap、HashTable,我们重点解析前三个(HashTable 已过时,不推荐使用)。

① HashMap(底层:哈希表 + 红黑树,JDK 8)

HashMap 是 Map 最常用的实现类,核心特性与 HashSet 一致(因为 HashSet 底层是 HashMap):

  • 无序:key 的存储顺序由哈希值决定,与插入顺序无关;
  • key 允许为 null(仅一个,因为 key 不可重复),value 允许为 null
  • 线程不安全:无同步锁,多线程修改可能出现并发问题(需用ConcurrentHashMap替代);
  • 性能:添加、删除、查询的时间复杂度均为 O (1)(哈希表无冲突时),冲突严重时退化为 O (logn)(红黑树优化)。

源码核心回顾(集合进阶 - 13~16)

  1. 底层结构:数组(Node[] table)+ 链表(Node节点)+ 红黑树(TreeNode节点);
  2. 哈希冲突解决:链地址法 —— 相同哈希值的 key 存入同一个数组位置(桶),先形成链表,当链表长度≥8 且数组容量≥64 时,转为红黑树;
  3. 扩容机制:

    • 初始容量:16(默认),容量始终是 2 的幂次(保证哈希计算时 “取模” 高效,用(n-1) & hash替代hash % n);
    • 负载因子:0.75(默认)—— 当 “键值对个数 ≥ 数组容量 × 负载因子” 时,触发扩容,扩容为原容量的 2 倍;
  4. key 的哈希计算hash(key) = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)—— 将 key 的哈希码高位与低位异或,减少哈希冲突。

② LinkedHashMap(底层:哈希表 + 双向链表)

LinkedHashMap 是 HashMap 的子类,核心特性与 LinkedHashSet 一致:

  • 有序:通过双向链表记录 key 的 “插入顺序”,遍历时光标按链表顺序移动,保证输出顺序与插入顺序一致;
  • key 允许为 nullvalue 允许为 null
  • 线程不安全
  • 性能:比 HashMap 略低(需维护双向链表的插入和删除),但遍历效率更高(按链表顺序,无需遍历整个哈希表)。

源码核心

  • 继承自 HashMap,重写了Node节点为Entry节点,新增beforeafter指针,形成双向链表;
  • 新增

    accessOrder

    参数(默认 false):

    • accessOrder=false:按 “插入顺序” 排序(默认);
    • accessOrder=true:按 “访问顺序” 排序(调用get(key)后,该 key 会移到链表末尾,适合实现 LRU 缓存)。

③ TreeMap(底层:红黑树)

TreeMap 是 Map 的有序实现类,核心特性与 TreeSet 一致(底层是红黑树):

  • 有序:按 key 的 “自然排序” 或 “自定义排序” 排列(中序遍历红黑树可得到有序 key);
  • key 不允许为 null(无法比较 null),value 允许为 null
  • 线程不安全
  • 性能:添加、删除、查询的时间复杂度均为 O (logn)(红黑树的平衡特性)。

源码核心回顾(集合进阶 - 17~18)

  1. 底层结构:红黑树(Entry节点构成,每个节点包含 key、value、左子树、右子树、颜色);
  2. 排序机制:

    • 自然排序:key 必须实现Comparable接口,重写compareTo方法;
    • 自定义排序:创建 TreeMap 时传入Comparator接口实现类,重写compare方法;
  3. key 的比较:每次添加键值对时,通过比较器确定 key 在红黑树中的位置,保证树的有序性和平衡性。

④ 四种 Map 实现类对比

实现类底层结构有序性key 允许 nullvalue 允许 null线程安全时间复杂度适用场景
HashMap哈希表 + 红黑树无序是(1 个)O(1)无有序需求,追求高性能
LinkedHashMap哈希表 + 双向链表插入 / 访问顺序是(1 个)O(1)需要保留插入 / 访问顺序
TreeMap红黑树自然 / 自定义排序O(logn)需要按 key 有序排列
HashTable哈希表无序O(1)已过时,多线程用 ConcurrentHashMap

总结与下节预告

到这里,我们已经掌握了:

  1. TreeSet 的两种排序方式(自然排序 Comparable、自定义排序 Comparator)及无重复判定规则;
  2. Map 集合的核心 API 与三种遍历方式(键找值、键值对、Lambda);
  3. HashMap、LinkedHashMap、TreeMap 的底层结构、特性与适用场景,以及 HashMap 的源码细节(哈希计算、扩容、红黑树转换)。

下一节,我们将讲解 Map 的工具类(Collections)、可变参数,以及 6 个综合练习(随机点名器、带权重点名、集合嵌套等),把前面的知识落地到实战场景,解决实际问题。

上一节我们系统掌握了 TreeSet 的排序机制和 Map 核心实现类的原理,这一节将聚焦 集合工具类(Collections)可变参数 这两个实用特性,并通过 6 个综合练习(随机点名、集合嵌套等)将所有知识落地,帮你彻底打通 “理论→实践” 的最后一公里。

第九模块:集合工具类与可变参数

1. 集合进阶 - 19:可变参数(Variable Arguments)

在实际开发中,经常需要定义 “参数个数不固定” 的方法(比如计算多个数的和),Java 5 引入的 可变参数 可以简化这种场景 —— 它允许方法接收 “任意个数的同类型参数”,本质是 “数组的语法糖”。

① 可变参数的语法与使用

  • 语法:在参数类型后加 ...,表示该参数是可变参数,格式为 方法名(参数类型... 参数名)
  • 注意:可变参数必须是方法的 最后一个参数,且一个方法只能有一个可变参数。

代码案例:用可变参数计算总和

public class VarargsDemo {
    public static void main(String[] args) {
        // 调用可变参数方法:可传入任意个数的int参数(0个、1个、多个)
        int sum1 = calculateSum(10, 20);
        int sum2 = calculateSum(5, 15, 25, 35);
        int sum3 = calculateSum(); // 允许传入0个参数

        System.out.println("sum1:" + sum1); // 输出:sum1:30
        System.out.println("sum2:" + sum2); // 输出:sum2:80
        System.out.println("sum3:" + sum3); // 输出:sum3:0
    }

    // 可变参数方法:计算多个int的总和
    public static int calculateSum(int... nums) {
        // 可变参数本质是数组,可通过数组语法遍历
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        return sum;
    }

    // 可变参数必须是最后一个参数(正确示例)
    public static void printInfo(String name, int... scores) {
        System.out.println("姓名:" + name);
        System.out.println("成绩:" + java.util.Arrays.toString(scores));
    }

    // 错误:可变参数不能在非最后位置
    // public static void errorMethod(int... nums, String name) {} // 编译报错
}

② 可变参数与集合的结合

可变参数常与 Arrays.asList() 配合,快速将多个元素转为 List 集合(注意:Arrays.asList() 返回的是固定长度的 List,不能调用 add()/remove() 等修改方法,需转为 ArrayList):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class VarargsWithCollection {
    public static void main(String[] args) {
        // 1. 可变参数快速创建List(固定长度)
        List<String> fixedList = Arrays.asList("Java", "Python", "Go");
        System.out.println(fixedList); // 输出:[Java, Python, Go]
        // fixedList.add("C++"); // 错误:UnsupportedOperationException(固定长度)

        // 2. 转为可修改的ArrayList
        List<String> modifiableList = new ArrayList<>(Arrays.asList("Java", "Python", "Go"));
        modifiableList.add("C++");
        System.out.println(modifiableList); // 输出:[Java, Python, Go, C++]

        // 3. 自定义方法:将可变参数元素添加到集合
        addElements(modifiableList, "Ruby", "Swift");
        System.out.println(modifiableList); // 输出:[Java, Python, Go, C++, Ruby, Swift]
    }

    // 可变参数方法:将元素添加到集合
    public static <T> void addElements(List<T> list, T... elements) {
        for (T elem : elements) {
            list.add(elem);
        }
    }
}

2. 集合进阶 - 20:集合工具类 Collections

java.util.Collections 是操作集合的 工具类,提供了大量静态方法,用于实现集合的排序、查找、同步化等功能(类似 Arrays 操作数组)。核心方法可分为 4 类:

① 排序相关方法

  • sort(List<T> list):对 List 按 自然排序(元素需实现 Comparable);
  • sort(List<T> list, Comparator<? super T> c):对 List 按 自定义排序(传入 Comparator);
  • reverse(List<?> list):反转 List 中元素的顺序;
  • shuffle(List<?> list):随机打乱 List 中元素的顺序(常用在 “洗牌” 场景)。

代码案例

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsSort {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(1);
        list.add(2);
        list.add(5);
        list.add(4);

        // 1. 自然排序(升序)
        Collections.sort(list);
        System.out.println("自然排序后:" + list); // 输出:[1, 2, 3, 4, 5]

        // 2. 自定义排序(降序)
        Collections.sort(list, (a, b) -> b - a);
        System.out.println("自定义排序(降序)后:" + list); // 输出:[5, 4, 3, 2, 1]

        // 3. 反转
        Collections.reverse(list);
        System.out.println("反转后:" + list); // 输出:[1, 2, 3, 4, 5]

        // 4. 随机打乱
        Collections.shuffle(list);
        System.out.println("打乱后:" + list); // 输出:随机顺序(如[3, 1, 5, 2, 4])
    }
}

② 查找与替换相关方法

  • binarySearch(List<? extends Comparable<? super T>> list, T key):对 已排序的 List 进行二分查找,返回元素索引(未找到返回负数);
  • max(Collection<? extends T> coll):返回集合中的 最大元素(自然排序);
  • min(Collection<? extends T> coll):返回集合中的 最小元素(自然排序);
  • fill(List<? super T> list, T obj):用指定元素填充 List(覆盖所有原有元素)。

代码案例

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsSearch {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(40);
        list.add(50);

        // 1. 二分查找(需先排序,此处已有序)
        int index = Collections.binarySearch(list, 30);
        System.out.println("30的索引:" + index); // 输出:30的索引:2

        // 2. 查找最大/最小元素
        Integer max = Collections.max(list);
        Integer min = Collections.min(list);
        System.out.println("最大值:" + max); // 输出:最大值:50
        System.out.println("最小值:" + min); // 输出:最小值:10

        // 3. 填充元素
        Collections.fill(list, 99);
        System.out.println("填充后:" + list); // 输出:[99, 99, 99, 99, 99]
    }
}

③ 同步化相关方法(线程安全)

Collections 提供了将 “非线程安全集合” 转为 “线程安全集合” 的方法(底层通过加同步锁实现),但性能较低,推荐用 java.util.concurrent 包下的集合(如 ConcurrentHashMap)替代:

  • synchronizedList(List<T> list):返回线程安全的 List;
  • synchronizedSet(Set<T> s):返回线程安全的 Set;
  • synchronizedMap(Map<K,V> m):返回线程安全的 Map。

代码案例

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsSynchronized {
    public static void main(String[] args) {
        // 非线程安全的ArrayList
        List<String> unsafeList = new ArrayList<>();
        // 转为线程安全的List
        List<String> safeList = Collections.synchronizedList(unsafeList);

        // 多线程环境下使用safeList,避免并发修改异常
        safeList.add("Java");
        System.out.println(safeList); // 输出:[Java]
    }
}

④ 其他常用方法

  • emptyList()/emptySet()/emptyMap():返回空的不可修改集合(避免创建新对象,节省内存);
  • singletonList(T o)/singletonSet(T o)/singletonMap(K k, V v):返回只包含一个元素的不可修改集合。
分类: Java-Backend 标签: Java

评论

暂无评论数据

暂无评论数据

目录

目录