Java 为何 long a = 999999999 能过;long a = 9999999999 报错?一文讲透“宽化转换”

目录

  • [1. 引言与案例](#1. 引言与案例 "#sec-1")
  • [2. 什么是宽化转换](#2. 什么是宽化转换 "#sec-2")
  • [3. 字面量的默认类型](#3. 字面量的默认类型 "#sec-3")
  • [4. 算术运算中的提升](#4. 算术运算中的提升 "#sec-4")
  • [5. 与装箱拆箱及重载决议](#5. 与装箱拆箱及重载决议 "#sec-5")
  • [6. 常见坑与实用建议](#6. 常见坑与实用建议 "#sec-6")
  • [7. 小测验](#7. 小测验 "#sec-7")
  • [8. 一句话记忆](#8. 一句话记忆 "#sec-8")

1. 引言与案例

你在代码中写下:

java 复制代码
long a = 999999999;   // 正常编译运行

为什么可行?因为:

  • 整数字面量默认类型是 int
  • 999999999 位于 int 的取值范围内(-2147483648 ~ 2147483647);
  • int 赋给 long 属于宽化转换 (从较小范围到较大范围),是安全且无需强转的。

对比:如果字面量超出 int 范围,就必须显式标记为 long

java 复制代码
long x = 2147483647;    // OK(仍在 int 范围内)
long y = 2147483648;    // 编译错误(默认按 int 解析,已溢出)
long z = 2147483648L;   // OK(L 表示 long 字面量)
long w = 3000000000;    // 编译错误
long v = 3000000000L;   // OK

2. 什么是宽化转换

  • 定义:把类型"向更宽的表示能力"转换的过程,通常是隐式且安全的,不需要强制类型转换。
  • 原始类型宽化链路
    • byte → short → int → long → float → double
    • char → int → long → float → double
  • 引用类型宽化 :子类到父类,或实现类到接口,例如 ArrayList → List → Collection → Object

注意:虽然"整数 → 浮点"也被视为宽化(比如 int → floatlong → double),但它可能带来精度表示差异(浮点无法精确表示所有大整数)。

3. 字面量的默认类型

  • 整数字面量默认 int10xFF0b101007 等。
  • 浮点字面量默认 double1.03.141e3 等。
  • 需要 long:在整数字面量后加 L(推荐大写 L,避免和数字 1 混淆)。
  • 需要 float:在浮点字面量后加 F
  • 字符字面量如 'a' 的类型是 char

4. 算术运算中的提升

在算术运算中,较小整数类型(byteshortchar)会先被提升为 int,再参与计算,结果至少是 int

java 复制代码
byte p = 1, q = 2;
var r = p + q;        // r 的类型是 int
// byte s = p + q;    // 编译错误:需要强制转换
byte s = (byte) (p + q);   // 显式强转后 OK

byte a1 = 1 + 2;      // OK:编译期常量折叠,且结果在 byte 范围内
byte a2 = 1 + p;      // 编译错误:含变量参与,结果提升为 int

混合运算遵循"向更宽类型靠拢"的规则:int → long → float → double。例如:

java 复制代码
double d = 1L + 1.0F;   // 结果是 double

5. 与装箱拆箱及重载决议

它们是两套不同机制

  • 宽化转换:原始类型之间或引用类型继承层次间的"变宽"。不创建对象,编译期通常可判定。
  • 自动装箱/拆箱 :原始类型与包装类型之间的自动转换(int ↔ Integerlong ↔ Long 等)。可能创建对象,拆箱时还可能触发 NullPointerException

在方法重载选择上,优先级通常为:原始类型宽化 > 自动装箱/拆箱 > 可变参数 。并且,编译器不会 做"装箱再宽化"的组合步(例如不存在 int → Integer → Long 的路径)。

示例:

java 复制代码
void f(long x) {}
void f(Integer x) {}
void f(int... xs) {}

f(1); // 调用 f(long) ------ 原始类型宽化优先

void g(Long x) {}
// g(1); // 编译错误:不能从 int 直接变为 Long(不会先装箱为 Integer 再"宽化"为 Long)

6. 常见坑与实用建议

  • 超出 int 范围的整数字面量必须加 L ,否则以 int 解析会编译失败。

  • 整数除法会截断小数1/2 == 0。若要小数结果,用 1.01d1F 触发浮点运算:

    java 复制代码
    double r1 = 1 / 2;     // 0.0
    double r2 = 1.0 / 2;   // 0.5
  • 整数 → 浮点虽属"宽化",但可能有精度差异 (尤其是 long → floatlong → double)。对精度敏感的数量(如金额)请使用 BigDecimal

  • 拆箱可能 NPEInteger n = null; int x = n; // NPE

7. 小测验

  1. 下列哪行会编译失败?为什么?

    java 复制代码
    long a = 2147483647;
    long b = 2147483648;
    long c = 2147483648L;
  2. 下面的调用会选哪个重载?

    java 复制代码
    void h(long x) {}
    void h(Integer x) {}
    h(1);
  3. 下面的结果分别是什么?

    java 复制代码
    double x = 1 / 2;
    double y = 1.0 / 2;
  4. 说明下面为何编译错误,如何修正?

    java 复制代码
    byte b = 1;
    byte c = 2;
    byte d = b + c;

答案:

  1. long b = 2147483648; 失败。2147483648 默认按 int 解析且超范围;写成 2147483648L 即可。
  2. 选择 h(long),因原始类型宽化优先于装箱。
  3. x == 0.0(整数除法先算出 0 再宽化),y == 0.5
  4. b + c 提升为 int,赋给 byte 需强转:byte d = (byte) (b + c);

8. 一句话记忆

  • 整数字面量默认 int,浮点字面量默认 double;超出范围要用后缀(L/F)。
  • "小到大"的隐式转换叫宽化转换;运算时小整型先提升为 int
  • 重载决议:原始宽化 > 装箱/拆箱 > 可变参数;不做"装箱再宽化"。
相关推荐
佐杰2 小时前
Jenkins使用指南1
java·运维·jenkins
dllxhcjla2 小时前
三大特性+盒子模型
java·前端·css
Acrelhuang2 小时前
筑牢用电防线:Acrel-1000 自动化系统赋能 35kV 园区高效供电-安科瑞黄安南
java·大数据·开发语言·人工智能·物联网
脸大是真的好~2 小时前
黑马JAVAWeb-10 文件上传-文件存储到服务器本地磁盘-文件存储在阿里云OSS-@Value属性注入
java
大G的笔记本3 小时前
算法篇常见面试题清单
java·算法·排序算法
亚林瓜子3 小时前
Spring中的异步任务(CompletableFuture版)
java·spring boot·spring·async·future·异步
MuYiLuck3 小时前
redis持久化与集群
java·数据库·redis
一叶飘零_sweeeet3 小时前
Java 项目 HTTP+WebSocket 统一权限控制实战
java·websocket·http·权限控制
7澄13 小时前
深入解析 LeetCode 数组经典问题:删除每行中的最大值与找出峰值
java·开发语言·算法·leetcode·intellij idea