CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Java 语言基础/课时/基本类型与引用类型
RawBlame
课时目录14 节

Java 语言基础

  1. 01Java 与 JVM 是什么
  2. 02基本语法与 main
  3. 03基本类型与引用类型
  4. 04条件与循环
  5. 05方法与参数
  6. 06数组
  7. 07类与封装
  8. 08继承接口与多态
  9. 09集合框架入门
  10. 10字符串
  11. 11异常处理
  12. 12文件 IO 入门
  13. 13Maven 与项目结构
  14. 14综合练习与 dsa-java 分工

基本类型与引用类型

int/double/boolean、包装类、null。

35 分钟

基本类型与引用类型

这一课你将能做到

  1. 默写 8 大基本类型及各自适用场景,说清基本类型和引用类型的本质区别
  2. 预判整数溢出、浮点精度丢失的结果,写出正确的类型转换代码
  3. 解释 Integer 缓存陷阱和 NullPointerException(NPE)的来源——这两个是后端线上事故与面试的高频考点

为什么要学

变量是程序存放数据的最小单位。类型选错,轻则数据被截断(订单金额少一个零),重则精度错乱(金额算分不齐)、线上 NPE 把接口打挂。本站后端里这些对应关系你以后天天见:

  • MySQL 的 BIGINT 主键 → Java long(int 最大约 21 亿,不够存雪花 ID)
  • 金额 DECIMAL(10,2) → Java BigDecimal(绝不能用 double)
  • Redis 缓存的布尔标志 → Java Boolean(引用类型,可能是 null)

类型系统是 Java「强类型」的根基:编译期就拦住大部分数据错误。

先建立图像

Java 的数据分两个世界:

基本类型(primitive)          引用类型(reference)
┌─────────────┐              ┌─────────────┐
│ 变量 = 值本身 │              │ 变量 = 对象的「遥控器」│
│ 存在栈上/直接 │              │ 变量存地址,对象在堆上 │
│ 不可能为 null │              │ 可能为 null(没遥控器)│
└─────────────┘              └─────────────┘

int a = 10;                    String s = "hi";
int b = a;   b 复制值 10       String t = s;  t 复制的是地址
改 b 不影响 a                   s 和 t 指向同一个对象

8 大基本类型全家福:

类型字节范围/说明后端常用场景
byte1-128 ~ 127二进制数据处理
short2±3.2 万少用,省内存的旧字段
int4±21 亿普通计数、状态码
long8±922 亿亿数据库主键、雪花 ID
float4约 7 位有效数字基本不用
double8约 15 位有效数字科学计算;金额禁用
char2一个 Unicode 字符单字符处理
boolean—true / false开关、标志位

跟着做

步骤 1:声明各类型变量并打印

新建 TypeDemo.java:

public class TypeDemo {
    public static void main(String[] args) {
        int viewCount = 1024;              // int 字面量直接写
        long userId = 20261006123456789L;  // long 字面量必须带 L 后缀!
        double rating = 9.5;               // 小数字面量默认是 double
        float price = 19.9F;               // float 字面量必须带 F 后缀
        boolean vip = true;
        char grade = 'A';                  // char 用单引号,只装一个字符
        byte level = 3;

        System.out.printf("浏览 %d, 用户 %d, 评分 %.1f, 价格 %.2f, VIP %b, 等级 %c%n",
                viewCount, userId, rating, price, vip, grade);
    }
}

输出:浏览 1024, 用户 20261006123456789, 评分 9.5, 价格 19.90, VIP true, 等级 A。

去掉 L 后缀会编译报错「整数太大」——编译器先按 int 解析字面量,超范围直接拒绝。

步骤 2:亲手触发类型转换的两种情况

public class CastDemo {
    public static void main(String[] args) {
        // 自动提升:小容器倒进大容器,安全
        int i = 100;
        long l = i;            // int -> long 自动完成
        double d = l;          // long -> double 自动完成
        System.out.println(d); // 100.0

        // 强制转换:大容器倒小容器,可能溢出/丢精度
        long bigId = 3_000_000_000L;      // 30 亿,超出 int 范围
        int truncated = (int) bigId;      // 必须写 (int),且结果已失真
        System.out.println(truncated);    // -1294967296,溢出成负数!

        int score = (int) 99.9;           // 浮点转整数直接砍掉小数(不是四舍五入)
        System.out.println(score);        // 99
    }
}

步骤 3:体验浮点精度陷阱(金额问题的根源)

public class FloatTrap {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2);          // 0.30000000000000004
        System.out.println(0.1 + 0.2 == 0.3);   // false!

        // 正确姿势:金额用 BigDecimal,且必须用字符串构造
        java.math.BigDecimal a = new java.math.BigDecimal("0.1");
        java.math.BigDecimal b = new java.math.BigDecimal("0.2");
        System.out.println(a.add(b));           // 0.3
        // 注意:new BigDecimal(0.1)(传 double)照样有精度问题,务必传字符串
    }
}

为什么:二进制无法精确表示 0.1,double 存的是近似值。订单金额差 0.0000001 累积起来就是对账事故。

步骤 4:包装类与自动装箱

每个基本类型都有对应的引用类型「包装类」:

public class WrapperDemo {
    public static void main(String[] args) {
        Integer boxed = 100;      // 自动装箱:int -> Integer
        int unboxed = boxed;      // 自动拆箱:Integer -> int

        // 为什么要包装类?因为泛型集合只能装对象(第 9 课),
        // 数据库字段允许 NULL 时,int 表示不了 null 而 Integer 可以。
        Integer dbAge = null;     // 模拟数据库 age 列为 NULL
        int age = dbAge;          // 运行时炸了:NullPointerException!

        // 经典缓存陷阱
        Integer x = 127, y = 127;
        System.out.println(x == y);   // true:走 IntegerCache 缓存,同一个对象
        Integer m = 128, n = 128;
        System.out.println(m == n);   // false:超出缓存,两个新对象
        System.out.println(m.equals(n)); // true:比较内容必须用 equals
    }
}

步骤 5:var 与常量(JDK 10+)

public class VarDemo {
    public static void main(String[] args) {
        var message = "类型推断";       // 编译器推断为 String,仍是强类型
        var numbers = new int[]{1, 2, 3};
        System.out.println(message + numbers.length);

        final int MAX_USERS = 1000;     // final:只能赋值一次,即常量
        System.out.println(MAX_USERS);
    }
}

var 只能用于局部变量,不能用于字段、参数、返回值。类型还是要明确的,只是让编译器替你写。

讲清楚要点

  1. 基本类型存值,引用类型存地址:= 赋值时,基本类型复制值,引用类型复制地址。这是「改了 b 为何 a 也变」类 bug 的根源。
  2. 整数默认 int、小数默认 double:所以 long 字面量加 L、float 加 F。反过来(大转小)必须显式强制转换。
  3. 整数除法会截断:10 / 3 == 3,10 % 3 == 1。想要小数先把一个操作数转 double:10.0 / 3。
  4. 包装类解决三件事:泛型容器只能装对象;数据库 NULL 语义;提供 parseInt、MAX_VALUE 等工具。
  5. 比较一律用 equals,== 只比地址:对 Integer、String 等引用类型尤其如此。== 偶尔「碰巧对」(小整数缓存),反而最害人。
  6. 拆箱前判空:Integer 为 null 时直接赋给 int 必抛 NPE,这是后端接口 500 的常见元凶。

你可能会懵的地方

  • char 和 String 什么关系? char 是单个 16 位 Unicode 字符('A'),String 是字符序列("ABC")。String 本质是 char 数组的封装(新版本 JDK 用 byte 数组优化了存储),可以做 s.charAt(0) 取出 char。

  • 为什么 Integer 只缓存 -128 到 127? IntegerCache 预创建了这个高频区间的对象,属于 JVM 性能优化。规范(Java 语言规范)允许缓存更宽区间,但超 127 后 == 结果在不同 JVM 上不可预期——所以永远用 equals。

  • boolean 到底占几个字节? 规范没定死,JVM 实现里单个 boolean 通常按 1 字节存,boolean 数组每项也是 1 字节。面试答「规范未规定,实现通常为 1 字节」即可。

  • 什么时候用基本类型,什么时候用包装类? 局部计算、循环计数用基本类型(性能好、不可能 null);实体类字段、集合元素、可空语义(数据库列可 NULL)用包装类。阿里巴巴 Java 开发手册同款结论:POJO 类属性一律用包装类型。

  • long 的 L 后缀小写 l 行不行? 行,但 l 和数字 1 几乎无法区分(101l vs 1011),行业规范强制用大写 L。

错法对照

错法问题正解
long id = 10000000000;字面量超 int 范围,编译错加 L:10000000000L
double money = 0.1 + 0.2; 当金额精度丢失BigDecimal + 字符串构造
if (m == n) 比较 Integer超 127 后恒 falsem.equals(n)
int x = nullableInteger;NPE先判空或用 Objects.requireNonNullElse
(int) 99.9 以为四舍五入得到 99用 Math.round(99.9) 得 100
byte 直接参与运算写回 bytebyte b = b + 1 编译错(结果提升为 int)b = (byte)(b + 1) 或 b += 1

卡点急救

  1. 编译报「不兼容的类型:从 double 转换到 int 可能会有损失」:把小数赋给整数变量了。要么换类型,要么显式 (int),要么改用 Math.round。
  2. 运行报 NullPointerException:打印变量确认哪个引用是 null(报错堆栈第一行会指向具体行号),先判空再拆箱/调用。
  3. 两个 Integer 明明值相等却进了 else 分支:你用了 ==。全部改成 equals,一劳永逸。
  4. 数字太大显示成科学计数法:System.out.println 对大 double 会输出 1.0E10,用 printf("%.2f") 或 BigDecimal.toPlainString()。

面试常问

  • 基本类型和引用类型的区别? 基本类型直接存值、在栈上(或作为对象一部分在堆上)、不能为 null、8 种;引用类型存对象地址、对象在堆、可为 null、有成员方法和继承体系。

  • Integer i = 127 与 Integer j = 127 为什么 == 是 true,128 就不是? 自动装箱走 Integer.valueOf,内部 IntegerCache 缓存 -128~127 的对象,区间内返回同一实例;超出则 new 新对象,地址不同。结论:包装类型比较用 equals。

  • 为什么金额不能用 float/double?怎么处理? 二进制浮点无法精确表示十进制小数(0.1 是无限循环二进制),累积误差会导致对账不平。用 BigDecimal(字符串构造、指定 scale 和舍入模式),或以「分」为单位存 long。

  • int 和 Integer 的区别? 类型本质、默认值(0 vs null)、能否进泛型集合、可否为数据库 NULL 字段做映射。高频追问「Integer 的默认值是 0 吗」——是 null,不是 0。

自测三问

  1. 8 种基本类型中,哪些字面量需要后缀?分别是什么?
  2. System.out.println(0.1 + 0.2 == 0.3) 输出什么?为什么?金额场景怎么写才对?
  3. 数据库 age 列为 NULL,映射到 Integer age = null 后执行 int x = age 会发生什么?

小结一句话

基本类型存值、引用类型存地址;long 加 L、float 加 F、整数除法截断、浮点算钱必翻车;包装类为 null 和泛型而生,比较永远用 equals——这四条能避开 80% 的初级线上事故。

延伸

  • Oracle:原始数据类型
  • 下一课:条件与循环——if/switch/for/while 控制程序走向,用箭头 switch 重构本站的状态机代码。
加载本节练习…
← 上一课基本语法与 main下一课 →条件与循环