20260729-笔记.md 19 KB

JavaSE 基础 — Day13 笔记

日期:2026-07-29
项目c260729
作者:WanJL


一、包装类(Wrapper Classes)

1.1 为什么需要包装类?

Java 中分为基本数据类型引用数据类型。Java 的核心思想是「万事万物皆为对象」,对象就一定有属性和方法。但 Java 中的 8 种基本数据类型(byteshortintlongfloatdoublecharboolean)是从 C/C++ 延续过来的,并不符合 Java 的面向对象特性——它们没有属性、也没有方法。

为了解决这个问题,Java 为每个基本数据类型都设置了对应的引用数据类型,即包装器类(Wrapper Classes)

📄 来源:packaging/Demo01.java

1.2 基本类型与包装类的对应关系

基本数据类型 包装器类(引用数据类型)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

包装器类具有属性和方法,属于引用数据类型。

📄 来源:packaging/Demo01.java

1.3 装箱(Boxing)与拆箱(Unboxing)

装箱(Boxing):从基本数据类型 → 引用数据类型(包装类)
拆箱(Unboxing):从引用数据类型(包装类) → 基本数据类型

// 手动装箱(JDK9 前的方式,之后已标记为过时)
Integer integer = new Integer(a); // 装箱

// 自动装箱(JDK5+,推荐方式)
Integer i = a;       // int → Integer,自动装箱
Integer i1 = 15;     // 直接赋值,自动装箱

// 手动拆箱
int a1 = integer.intValue(); // 调用 intValue() 方法拆箱

// 自动拆箱(JDK5+,推荐方式)
int a2 = i;          // Integer → int,自动拆箱

📄 来源:packaging/Demo01.java

1.4 自动装箱与拆箱的底层原理

  • 自动装箱:编译器自动调用 Integer.valueOf(int) 方法
  • 自动拆箱:编译器自动调用 Integer.intValue() 方法
  • 自动装箱/拆箱机制从 JDK 5 开始引入

1.5 使用包装类的优势

  1. 拥有属性和方法:可以调用字符串解析、进制转换等方法
  2. 泛型支持:集合框架(如 ArrayList<Integer>)必须使用包装类,不能使用基本类型
  3. 允许 null:包装类属于引用类型,可以为 null,这在数据库映射等场景中很有用
  4. 工具方法:提供了丰富的静态工具方法(如 Integer.parseInt()Integer.valueOf() 等)

1.6 包装类在 Bean 类中的应用

在实际开发中,Bean 类(如 Person)的属性通常会使用包装类而非基本类型,原因包括:

  • 对象的属性可能为 null(数据库字段可为空)
  • 便于与泛型集合配合使用
  • 统一使用引用数据类型,符合 OOP 风格

    public class Person {
    private String name;
    private Integer age;  // 使用 Integer 而非 int,允许 age 为 null
        
    // getter / setter 方法
    }
    

📄 来源:packaging/Person.java

1.7 包装类的常用方法

Integer 为例,包装类提供了一些非常实用的静态方法:

方法 说明
Integer.parseInt(String s) 将字符串解析为 int
Integer.valueOf(int i) int 转换为 Integer(自动装箱底层调用)
Integer.valueOf(String s) 将字符串转换为 Integer
int Integer.intValue() Integer 转换为 int(自动拆箱底层调用)
Integer.MAX_VALUE int 的最大值(常量: 2147483647)
Integer.MIN_VALUE int 的最小值(常量: -2147483648)

其他包装类(DoubleBooleanCharacter 等)也有类似的方法。

1.8 包装类的缓存机制

部分包装类(IntegerByteShortLongCharacter)在自动装箱时存在缓存机制

  • Integer 缓存池:默认缓存 -128 ~ 127 范围的整数
  • 在此范围内,自动装箱复用同一个对象(== 比较为 true
  • 超出此范围,每次自动装箱都会新建对象

    Integer a = 100;  // 从缓存池获取
    Integer b = 100;  // 从缓存池获取
    System.out.println(a == b);  // true
    
    Integer c = 200;  // 超出缓存范围,新建对象
    Integer d = 200;  // 超出缓存范围,新建对象
    System.out.println(c == d);  // false
    
    // 推荐使用 equals() 比较包装类对象的值
    System.out.println(c.equals(d));  // true
    

注意:包装类对象的值比较应始终使用 equals() 方法,不要使用 ==


二、集合框架入门(Collection Framework)

2.1 集合的概念

集合(Collection) 是用来存储 Java 变量、对象的容器,也称为集合容器。

与数组一样,集合也是容器,可以存储多个数据。但二者有显著区别:

对比维度 数组 集合
长度 一旦创建,长度不可变 长度可变,可动态增减
存储类型 可存储基本数据类型和引用数据类型 只能存储引用数据类型(若需存基本类型,须用对应的包装类)
元素类型 只能存储同一种数据类型(类型固定) 可存储多种数据类型(但建议存储单一类型,以便泛型约束)

📄 来源:collection/Demo01.java

2.2 集合的体系结构

Java 的集合框架分为两大派系:

Collection(单列集合)          Map(双列集合)
      / \                          / \
  List    Set                  HashMap   TreeMap
   /\      /\                 LinkedHashMap
ArrayList  HashSet
LinkedList TreeSet
Vector     LinkedHashSet
  • CollectionMapListSet 都是 接口(Interface),只定义规则,不实现
  • 具体功能由它们的 实现类 完成

Collection(单列集合)

  • List(有序集合):元素有顺序,可重复
    • ArrayList:数组结构实现
    • LinkedList:链表结构实现
    • Vector:早期版本,线程安全
  • Set(无序集合):元素无顺序,不可重复
    • HashSet:哈希表实现
    • TreeSet:红黑树实现(可排序)
    • LinkedHashSet:可保持插入顺序

Map(双列集合)

  • 键(Key)值(Value) 组成
  • Key 在集合中是唯一的,不能重复
  • Value 可以重复

📄 来源:collection/Demo01.java

2.3 List 与底层数据结构

集合被称为集合框架,因为它把底层数据结构进行了封装。

List 直译为"列表",对应数据结构中的线性表结构。线性表结构大致分为四种:

2.3.1 栈结构(Stack)

  • 特点:先进后出(LIFO — Last In First Out)
  • 操作术语

    • 加入元素 → 入栈 / 压栈(push)
    • 移除元素 → 出栈 / 弹栈(pop)

      入栈顺序: A → B → C
      出栈顺序: C → B → A
      

2.3.2 队列结构(Queue)

  • 特点:先进先出(FIFO — First In First Out)

    入队顺序: A → B → C
    出队顺序: A → B → C
    

2.3.3 数组结构(Array)

  • 在内存中开辟一段连续的空间存储数据
  • 优点:查询速度快,可通过索引快速定位(arr[index]
  • 缺点:增删速度慢,因为需要移动后续元素
  • 对应实现类ArrayList

    索引: [0] [1] [2] [3] [4] [5]
    值:   10  20  30  40  50  60
      ↑ 连续存储空间
    

2.3.4 链表结构(Linked List)

  • 在内存中不连续存储,不需要开辟连续的存储空间
  • 每个节点(Node)包含数据 + 指向下一个节点的指针(地址)
  • 优点:增删速度快,只需改变相邻节点的指针
  • 缺点:随机查询速度慢,需要从头结点依次遍历查找
  • 对应实现类LinkedList

    头结点 → Node1 → Node2 → Node3 → null
        data    data    data
        next→   next→   next→
    

2.3.5 List 实现类的数据结构对应

实现类 对应数据结构 查询特点 增删特点
ArrayList 数组(Array) 快(索引定位) 慢(元素移动)
LinkedList 链表(Linked List)、队列(Queue)、栈(Stack) 慢(从头遍历) 快(改指针)

📄 来源:collection/Demo02.java

2.4 ArrayList 底层实现详解

ArrayListList 接口最常用的实现类,底层使用可变数组(动态数组)存储数据。

2.4.1 核心特性

  • 本质:长度可变的数组,底层维护的是一个 Object[] elementData
  • 允许存储 null
  • Vector 类似,但不是线程同步的(非线程安全),性能更高
  • 内部维护一个 size 属性,表示数组中有效元素的个数(不是数组长度)
  • size() 方法返回 size 属性
  • isEmpty() 返回 size == 0

📄 来源:collection/Demo02.java

2.4.2 默认容量与扩容机制

// 默认初始容量为 10
private static final int DEFAULT_CAPACITY = 10;

// 空数组(用户指定容量为 0 时使用)
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认容量空数组(无参构造时使用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 底层维护的数组
transient Object[] elementData;

image-20260729111736848

扩容流程:

  1. 使用无参构造时,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA(容量为 0 的空数组)
  2. 第一次添加元素时,立即将容量扩容到 DEFAULT_CAPACITY(10)
  3. 后续元素超出容量时,自动按 1.5 倍 扩容(oldCapacity + (oldCapacity >> 1)

2.4.3 三个构造方法

构造方法 说明
ArrayList() 无参构造,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA(初始容量 0,首次添加时扩容到 10)
ArrayList(int initialCapacity) 指定初始容量,若大于 0 则 elementData = new Object[initialCapacity];若为 0 则 elementData = EMPTY_ELEMENTDATA
ArrayList(Collection<? extends E> c) 传入另一个集合对象,将其所有元素添加到新建的 ArrayList
// 无参构造 — 默认容量 10(懒加载,首次 add 时扩容)
ArrayList list01 = new ArrayList<>();

// 指定初始容量为 20
ArrayList list02 = new ArrayList<>(20);

// 以另一个集合为参数创建
ArrayList list03 = new ArrayList<>(list02);

System.out.println(list01.size()); // 0
System.out.println(list02.size()); // 0
System.out.println(list03.size()); // 0

📄 来源:collection/Demo02.java

2.4.4 关键属性速查

属性/方法 说明
DEFAULT_CAPACITY = 10 默认初始容量
elementData 底层维护的 Object[] 数组
size 有效元素的个数(非数组长度)
size() 返回 size 属性
isEmpty() 返回 size == 0

💡 注意ArrayListsize 是元素个数,elementData.length 才是数组的物理容量(capacity),二者不一定相等。size <= capacity 恒成立。

2.4.5 ArrayList 扩容相关方法

trimToSize() — 缩减容量

将底层数组的容量缩减为当前有效元素的个数,以节省内存空间。

// 底层实现逻辑
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
  • 如果 size == 0,将 elementData 设置为 EMPTY_ELEMENTDATA(空数组)
  • 否则,通过 Arrays.copyOf() 复制到新的数组,新数组长度为 size
ensureCapacity(int minCapacity) — 确保容量(手动扩容)

在向数组添加元素之前,都会先检查添加后的元素个数是否会超过当前数组长度。如果超过,就会扩容。数组扩容通过 ensureCapacity() 来实现。

这个方法用 public 修饰,开发者可以 主动调用 来手动扩容——当需要添加大量元素之前,提前扩容能提升性能。

// 手动扩容到期望的最小容量
ArrayList list = new ArrayList<>();
list.ensureCapacity(100); // 提前扩容到 100,避免多次自动扩容

扩容触发条件(两个条件同时满足才会扩容):

  1. minCapacity > elementData.length —— 期望的最小容量大于当前数组长度
  2. !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY) —— 既不是默认空数组,也不是期望容量 ≤ 默认容量

通俗解释:如果是刚 new 出来的空 ArrayListelementData 为空数组),且期望容量没超过 10,不会立即扩容,等到第一次 add 时再扩容到 10。

grow(int minCapacity) — 扩容核心方法
Object[] grow(int minCapacity) {
    // oldCapacity: 旧的容量
    // minCapacity - oldCapacity: 最小需要增加的容量
    // oldCapacity >> 1:  最好增加的容量(旧容量除以 2)
    return Arrays.copyOf(elementData,
        ArraysSupport.newLength(oldCapacity,
            minCapacity - oldCapacity,
            oldCapacity >> 1));
}

扩容计算公式:

变量 说明 示例(旧容量 = 10)
oldCapacity 旧容量 10
minCapacity - oldCapacity 最小需要增加的容量 最少加 1
oldCapacity >> 1 最好增加的容量 = 旧容量 ÷ 2 10 >> 1 = 5
新容量 oldCapacity + oldCapacity >> 1 = 旧容量的 1.5 倍 10 + 5 = 15

结论ArrayList 每次扩容时,新容量 = 旧容量 × 1.5 倍(即 oldCapacity + oldCapacity / 2

main 方法中的位移运算演示
System.out.println(20 << 1);  // 20 × 2 = 40
System.out.println(20 >> 1);  // 20 ÷ 2 = 10
  • <<:左移,相当于乘以 2
  • >>:右移,相当于除以 2
  • oldCapacity >> 1 即为 oldCapacity / 2

📄 来源:collection/Demo02.java

2.4.6 ArrayList 常用方法速查

方法 说明
add(E e) 向集合末尾添加元素
add(int index, E element) 向集合指定位置插入元素
get(int index) 获取指定索引位置的元素
getFirst() 获取集合第一个元素(JDK 21+)
getLast() 获取集合最后一个元素(JDK 21+)
set(int index, E element) 修改指定索引位置的元素
remove(int index) 删除指定索引位置的元素
remove(Object o) 删除第一个匹配的指定元素
size() 获取集合中元素的个数
isEmpty() 判断集合是否为空(size == 0
contains(Object o) 判断集合是否包含指定元素
indexOf(Object o) 返回指定元素首次出现的索引,不存在则返回 -1
toArray() 将集合转换为 Object[] 数组
clear() 清空集合中的所有元素
ArrayList<String> list = new ArrayList<>();
list.add("Java");           // 添加元素
list.add(0, "Python");      // 在索引 0 插入
list.get(0);                // 获取索引 0 的元素
list.getFirst();            // 获取第一个元素
list.getLast();             // 获取最后一个元素
list.set(0, "C++");         // 修改索引 0 的元素
list.remove(0);             // 删除索引 0 的元素
list.remove("Java");        // 按元素删除
list.size();                // 元素个数
list.isEmpty();             // 是否为空
list.contains("Java");      // 是否包含
list.indexOf("Java");       // 查找索引
Object[] arr = list.toArray(); // 转数组
list.clear();               // 清空

📄 来源:collection/Demo02.java


三、随堂练习要点

本日课程知识点概览

知识点 说明 对应文件
包装类概念 8种基本类型→包装类,装箱/拆箱 packaging/Demo01.java
包装类在 Bean 中的应用 Integer 替代 int packaging/Person.java
包装类缓存机制 -128~127 缓存池,== vs equals()
集合框架概述 集合 vs 数组对比,体系结构 collection/Demo01.java
线性表数据结构 栈/队列/数组/链表 特点 collection/Demo02.java
ArrayList 底层原理 可变数组、默认容量10、扩容机制、三个构造方法、size vs capacity collection/Demo02.java
ArrayList 扩容方法 trimToSize 缩减容量、ensureCapacity 手动扩容、grow 1.5倍扩容公式 collection/Demo02.java
位运算符 >> 右移(除2)、<< 左移(乘2)在扩容中的使用 collection/Demo02.java
ArrayList 常用方法 add/get/set/remove/size/isEmpty/contains/indexOf/toArray/getFirst/getLast collection/Demo02.java

包装类重点

  1. 概念理解:为什么 Java 需要包装类?基本类型和包装类各有什么优劣?
  2. 装箱/拆箱操作:手动 vs 自动,理解编译器的底层转换逻辑
  3. 实际应用:在 Bean 类中优先使用包装类(如 Integer 替代 int),便于表达 null 语义
  4. 常见陷阱
    • == 比较包装类时的缓存问题
    • 包装类对象参与算术运算时的自动拆箱
    • null 包装类拆箱会抛出 NullPointerException

集合框架重点

  1. 理解集合 vs 数组的区别:长度可变性、元素类型限制
  2. 记忆体系结构:Collection(List / Set)和 Map 两大分支
  3. 掌握四种线性表结构:栈(LIFO)、队列(FIFO)、数组(查询快增删慢)、链表(增删快查询慢)
  4. 关联实现类ArrayList → 数组,LinkedList → 链表/队列/栈
  5. ArrayList 底层细节
    • 默认容量 10(懒加载,首次 add 才扩容)
    • 三个构造方法的区别
    • sizecapacity 的区别:size 是元素个数,capacity 是数组物理长度
    • 允许存储 null,非线程安全
    • ArrayList 常用方法add()/get()/set()/remove()/size()/isEmpty()/contains()/indexOf()/toArray()/getFirst()/getLast()
    • trimToSize():缩减容量到有效元素个数
    • ensureCapacity():手动提前扩容优化性能
    • grow():每次扩容为旧容量的 1.5 倍oldCapacity + oldCapacity >> 1
    • >> 右移位运算等价于除以 2

四、拓展阅读