乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

包装类:基本类型的对象化

本章定位:Java 为每种基本类型提供了对应的包装类,实现"万物皆对象"。自动装箱/拆箱让代码更简洁,但也隐藏了缓存机制和空指针的陷阱——这正是面试中的重灾区。


小崔的诡异 Bug

小崔在飞翔科技的薪资系统中写了这样一段代码:

Integer a = 100;
Integer b = 100;
System.out.println(a == b);  // true  ✓ 符合预期

Integer c = 200;
Integer d = 200;
System.out.println(c == d);  // false ??? 什么鬼!

"同样是 Integer,为什么 100 相等,200 就不等了?"小崔怀疑自己产生了幻觉。

白歌微微一笑:"这是 Integer 的缓存机制。来,我们深入源码看看。"


八种包装类速览

基本类型包装类大小缓存范围
byteByte8 bit-128 ~ 127(全部)
shortShort16 bit-128 ~ 127
intInteger32 bit-128 ~ 127(可配置上限)
longLong64 bit-128 ~ 127
floatFloat32 bit无缓存
doubleDouble64 bit无缓存
charCharacter16 bit0 ~ 127(ASCII)
booleanBoolean—TRUE / FALSE(两个常量)

六种数值型包装类(Integer、Long、Short、Byte、Float、Double)都继承自抽象类 Number。


自动装箱(Autoboxing)与拆箱(Unboxing)

定义

概念方向编译后等价调用触发时机
装箱基本类型 → 包装对象Integer.valueOf(int)赋值给包装类型、方法参数、集合操作
拆箱包装对象 → 基本类型Integer.intValue()赋值给基本类型、算术运算、比较运算

字节码验证

// 源码
Integer i = 100;          // 装箱
int j = i;                // 拆箱
int k = i + 1;            // 先拆箱,再运算,再装箱(如果赋值给 Integer)
// 编译后等价代码
Integer i = Integer.valueOf(100);
int j = i.intValue();
int k = i.intValue() + 1;

Integer 缓存机制(深度原理)

源码分析

// Integer.IntegerCache 内部类(JDK 8 源码简化版)
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        int h = 127;                     // 默认上限
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            h = Math.min(Integer.parseInt(integerCacheHighPropValue), Integer.MAX_VALUE - (-low) -1);
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for (int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);  // 预先创建 -128 ~ 127 的所有 Integer 对象
    }
}

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];  // 从缓存取
    return new Integer(i);  // 超出范围才 new
}

缓存行为图解

可自定义上限:启动 JVM 时添加 -Djava.lang.Integer.IntegerCache.high=512,可将缓存上限扩展到 512。但下限 -128 不可改变。


完整示例一:装箱拆箱与缓存陷阱

场景

小崔写了一个薪资等级判断函数,用来比较员工的级别系数。

代码

public class WrapperTrapDemo {
    public static void main(String[] args) {
        // ========== 1. 缓存范围内的 == 比较 ==========
        Integer a = 100;          // Integer.valueOf(100) → 缓存
        Integer b = 100;          // Integer.valueOf(100) → 缓存(同一对象)
        System.out.println("100 == 100: " + (a == b));        // true

        // ========== 2. 超出缓存范围的 == 比较 ==========
        Integer c = 200;          // Integer.valueOf(200) → new Integer(200)
        Integer d = 200;          // Integer.valueOf(200) → 又 new Integer(200)
        System.out.println("200 == 200: " + (c == d));        // false!

        // ========== 3. equals() 始终比较数值 ==========
        System.out.println("200 equals 200: " + c.equals(d)); // true

        // ========== 4. 与基本类型比较 → 自动拆箱 ==========
        int primitive200 = 200;
        System.out.println("c == 200: " + (c == primitive200)); // true(拆箱后比数值)

        // ========== 5. new Integer 始终不缓存 ==========
        Integer e = new Integer(100);
        Integer f = new Integer(100);
        System.out.println("new 100 == new 100: " + (e == f)); // false

        // ========== 6. 包装类的 null 陷阱 ==========
        Integer salaryLevel = null;
        // int level = salaryLevel;  // 运行时 NPE!拆箱时 null.intValue()
        int level = salaryLevel != null ? salaryLevel : 0;  // 安全做法
        System.out.println("安全处理 null: level = " + level);
    }
}

运行输出

100 == 100: true
200 == 200: false
200 equals 200: true
c == 200: true
new 100 == new 100: false
安全处理 null: level = 0

parseXxx() 与 toString()

常用转换方法

方法功能示例
Integer.parseInt(String)字符串 → intInteger.parseInt("42") → 42
Double.parseDouble(String)字符串 → doubleDouble.parseDouble("3.14") → 3.14
Boolean.parseBoolean(String)字符串 → booleanBoolean.parseBoolean("true") → true
Integer.toString(int)int → 字符串Integer.toString(42) → "42"
Integer.toBinaryString(int)int → 二进制字符串Integer.toBinaryString(10) → "1010"
Integer.toHexString(int)int → 十六进制字符串Integer.toHexString(255) → "ff"
Integer.valueOf(String)字符串 → IntegerInteger.valueOf("42") → Integer(42)

parseInt 返回基本类型 int;valueOf 返回包装对象 Integer(会走缓存)。


完整示例二:飞翔科技薪资数据处理

场景

小崔从配置文件中读取薪资数据(字符串形式),需要解析为数值、比较范围、格式化输出。

代码

public class SalaryDataProcessor {
    public static void main(String[] args) {
        // ========== 1. 字符串解析为基本类型 ==========
        String baseSalaryStr = "15000";
        String bonusStr = "3.5";      // 3.5 个月年终奖
        String stockOptionsStr = "2000";

        int baseSalary = Integer.parseInt(baseSalaryStr);
        double bonusMonths = Double.parseDouble(bonusStr);
        long stockOptions = Long.parseLong(stockOptionsStr);

        System.out.println("基本工资: " + baseSalary);
        System.out.println("年终奖月数: " + bonusMonths);
        System.out.println("期权数量: " + stockOptions);

        // ========== 2. 数值比较(注意拆箱) ==========
        Integer threshold1 = 10000;
        Integer threshold2 = 20000;
        // 直接用 > < 比较 → 自动拆箱为 int → 放心用
        if (baseSalary > threshold1 && baseSalary < threshold2) {
            System.out.println("薪资在合理范围内");
        }

        // ========== 3. equals 安全的比较 ==========
        Integer salary1 = 15000;
        Integer salary2 = 15000;
        // ✅ 安全:用 equals 或 Objects.equals
        System.out.println("薪资相等: " + salary1.equals(salary2));
        System.out.println("(null安全): " + java.util.Objects.equals(salary1, salary2));

        // ========== 4. 数字格式化输出 ==========
        double totalAnnual = baseSalary * (12 + bonusMonths) + stockOptions;
        System.out.println("年度总收入: " + String.format("%,.2f", totalAnnual));

        // ========== 5. 进制转换(工号生成用) ==========
        int employeeId = 255;
        System.out.println("十进制: " + employeeId);
        System.out.println("二进制: " + Integer.toBinaryString(employeeId));
        System.out.println("十六进制: " + Integer.toHexString(employeeId).toUpperCase());
        System.out.println("八进制: " + Integer.toOctalString(employeeId));

        // ========== 6. 字符串 → Integer(valueOf 走缓存) ==========
        Integer id1 = Integer.valueOf("100");   // 从缓存取
        Integer id2 = Integer.valueOf("100");   // 同一个对象
        System.out.println("valueOf 100 缓存: " + (id1 == id2));  // true
    }
}

运行输出

基本工资: 15000
年终奖月数: 3.5
期权数量: 2000
薪资在合理范围内
薪资相等: true
(null安全): true
年度总收入: 234,500.00
十进制: 255
二进制: 11111111
十六进制: FF
八进制: 377
valueOf 100 缓存: true

包装类的不可变性

所有包装类都是 final 类,内部值由 private final 字段持有,一旦创建不可更改。

Integer x = 5;
x = x + 1;  // 看似修改了 x,实际上创建了新的 Integer(6) 对象

任何算术操作都会产生新对象。这是和 int 最大的语义区别。


易错场景

❌ 反例一:用 == 比较两个包装类

// ❌ 错误:== 比较引用,超出缓存范围就出错
public boolean isSameLevel(Integer level1, Integer level2) {
    return level1 == level2;  // 缓存内碰巧对,缓存外错!
}

纠正:

// ✅ 正确:使用 equals 或拆箱后比较
public boolean isSameLevel(Integer level1, Integer level2) {
    return Objects.equals(level1, level2);
    // 或 return level1 != null && level1.equals(level2);
}

❌ 反例二:包装类 null 参与运算

// ❌ 错误:自动拆箱时 NPE
Integer salary = null;
int bonus = salary + 5000;  // NullPointerException! salary 拆箱时调用 null.intValue()

纠正:

// ✅ 正确:使用前做 null 检查,或设默认值
Integer salary = null;
int safeSalary = (salary != null) ? salary : 0;
int bonus = safeSalary + 5000;

❌ 反例三:在集合中混合基本类型和包装类

// ❌ 可能造成意外的装箱/拆箱开销
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add(i);  // 每次装箱 Integer.valueOf(i),10万次
}
int sum = 0;
for (Integer val : list) {
    sum += val;   // 每次拆箱 val.intValue(),10万次
}

这种情况开销可以接受(因为 Integer 缓存了 -128~127),但超出缓存范围的数值会创建大量对象。


面试考点

Q1:int 和 Integer 有什么区别?

① int 是基本类型,存数值,默认值 0;Integer 是包装类,存对象引用,默认值 null。② int 不参与面向对象特性;Integer 可作为泛型参数、调用方法。③ Integer 内部有 -128~127 的缓存,valueOf() 在该范围内复用对象。④ 自动装箱/拆箱让两者在多数场景下可互换,但性能、null 安全、== 比较仍有差异。

Q2:Integer 的缓存机制是什么?为什么设计这个缓存?

Integer 在类加载时预创建 -128~127(共 256 个)Integer 对象,存入 IntegerCache 数组。调用 valueOf() 时先查缓存,命中则返回已有对象,否则新建。设计目的是减少小数值频繁装箱时的对象创建开销(这些小整数在循环、数组索引等场景使用频率极高)。可通过 JVM 参数 -Djava.lang.Integer.IntegerCache.high=xxx 调整上限。

Q3:自动装箱和拆箱的底层原理是什么?

装箱:编译器将 Integer i = 100 替换为 Integer i = Integer.valueOf(100)。拆箱:编译器将 int j = i 替换为 int j = i.intValue()。可通过 javap -c 反编译字节码验证。本质上是一种语法糖,编译后还原为显式的方法调用。

Q4:下面代码创建了几个 Integer 对象?

Integer a = 100;   // 和
Integer b = 200;

Integer a = 100 调用 valueOf(100),在 -128~127 范围内,从缓存获取,创建 0 个新对象(类加载时已创建)。Integer b = 200 超出范围,valueOf(200) 内部 new Integer(200),创建 1 个新对象。总共创建 1 个对象。

Q5:三目运算符中的装箱陷阱你知道吗?

Integer i = true ? 1 : null;  // 输出 i = ?

三目运算符的两个分支类型必须一致。1 是 int,null 会被隐式装箱推断为 Integer,所以 1 也会被装箱。但更危险的写法是:

int result = flag ? integerObj : integerObj2;

如果其中一个为 null,拆箱时 NPE!因为三目运算符要求两个分支类型一致,会触发拆箱。最佳实践:三目运算符两端类型显式保持一致,避免自动装箱/拆箱的隐式行为。


白歌在技术分享会上总结:"包装类是 JDK 5 引入的最重要的语法糖之一。用了十几年都没问题,但一旦在 == 比较或 null 值上栽跟头,debug 起来就会怀疑人生。记住两条铁律:比内容用 equals,比大小先拆箱再 > <。"

上一页
Object 类:万类之祖
下一页
String:不可变的字符串