2.3 数值转换、提升与精度
前置知识:基本类型的范围与边界
预计时间:30 分钟
完成标志:能区分拓宽、窄化、二元数值提升和字符串解析
拓宽基本类型转换
拓宽转换不需要显式 cast:
byte small = 100;
int count = small;
long total = count;
double measurement = total;常用合法方向:
byte → short → int → long → float → double
↑
char ───────────┘这张图是速记,不是“每一步都无损”的承诺:
byte、short、char到int能精确表示;int到long能精确表示;float到double能精确保存原有float值;int到float、long到float,以及long到double可能丢失低位精度;- 拓宽转换不会因目标范围太小而溢出,但浮点结果可能只保留近似值。
实际验证:
long original = 9_007_199_254_740_993L;
double widened = original;
long roundTrip = (long) widened;
System.out.println(original);
System.out.println(widened);
System.out.println(roundTrip);
System.out.println(original == roundTrip);在这个例子中,roundTrip 会变成 9_007_199_254_740_992,最终比较为 false。
窄化基本类型转换
目标类型不能表示所有源值时,通常需要显式 cast:
double weight = 85.7;
int kilograms = (int) weight;
System.out.println(kilograms); // 85浮点转整数向零截断,不是四舍五入:
System.out.println((int) 3.9); // 3
System.out.println((int) -3.9); // -3转为 int 或 long 时,还有三个边界:
NaN转整数得到 0;- 超过目标整数最大值时,结果钳制到最大值;
- 小于目标整数最小值时,结果钳制到最小值。
转成 byte、short 或 char 要多走一步:先按上述规则转为 int,再执行整数窄化。因此 (byte) 1000.0 是 -24,并不会钳制为 Byte.MAX_VALUE;(byte) Double.POSITIVE_INFINITY 则是 -1。
整数窄化只保留目标宽度的低位:
int value = 1000;
byte narrowed = (byte) value;
System.out.println(narrowed); // -24cast 表示“允许按规范执行转换”,不表示结果符合业务意图。转换前应做范围检查:
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new IllegalArgumentException("out of byte range: " + value);
}
byte safe = (byte) value;常量表达式的特殊窄化
下面能编译:
byte level = 100;
char marker = 65;因为右侧是编译期常量表达式,值落在目标类型范围内。换成普通 int 变量则需要 cast:
int source = 100;
// byte level = source; // 编译错误
byte level = (byte) source;常量超范围仍会被拒绝:
// byte invalid = 200;一元与二元数值提升
byte、short、char 参与大多数算术运算时会先提升为 int:
byte left = 10;
byte right = 20;
// byte sum = left + right; // 编译错误:结果是 int
int sum = left + right;复合赋值包含隐式转换:
byte count = 10;
count += 1; // 近似等价于 count = (byte) (count + 1)
// count = count + 1; // 右侧是 int,不能直接赋给 byte“近似等价”很重要:左侧表达式只求值一次,后面学习数组和副作用时会看到差别。
二元数值提升大体遵循:
- 有
double,另一方转为double; - 否则有
float,另一方转为float; - 否则有
long,另一方转为long; - 否则双方提升为
int。
int units = 5;
double factor = 2.0;
double result = units * factor;不要用“结果永远是最大的类型”代替正式规则;字符串连接、移位、条件表达式和常量表达式还有各自规则。
数值转换不等于解析文本
cast 只处理兼容的基本类型或引用类型关系,不能把字符串直接 cast 成整数:
String text = "42";
int value = Integer.parseInt(text);无效文本会在运行时抛出 NumberFormatException:
try {
int parsed = Integer.parseInt("forty-two");
System.out.println(parsed);
} catch (NumberFormatException error) {
System.out.println("not an integer");
}数字转字符串可以使用 Integer.toString(value) 或 String.valueOf(value)。
装箱与拆箱边界
Integer boxed = 100; // 自动装箱
int plain = boxed; // 自动拆箱包装类型可以是 null,拆箱 null 会抛 NullPointerException:
Integer missing = null;
// int value = missing; // 运行时失败这不是基本类型数值拓宽或窄化,排错时要分开判断。集合章节会进一步处理包装类型。
Python 与 C++ 对照
Python 的 int(3.9) 是运行时调用,用于构造整数值,结果向零截断;int("42") 还承担文本解析。普通 Python int 支持任意精度,但转成固定宽度的库类型时仍可能出现范围问题。
C++ 提供 static_cast 等多种显式转换形式,也保留 C 风格 cast。Java 整数运算的回绕由语言规范定义;C++ 有符号整数溢出通常是未定义行为,无符号整数按模运算。不能把 Java 实验结果直接移植为 C++ 推理。
专业速查
| 任务 | 做法 |
|---|---|
小整数到 int/long | 拓宽转换,通常无须 cast |
long 到 double | 无须 cast,但检查精度要求 |
| 浮点到整数 | 显式 cast;向零截断并处理 NaN/越界 |
int 到 byte | 先检查范围,再 cast |
byte + byte | 结果通常是 int |
| 文本到整数 | Integer.parseInt,处理格式错误 |
Integer 到 int | 注意 null 拆箱风险 |
离开这一页前
拿到一次转换,依次问范围、精度和失败方式。能解释 byte + byte 为什么得到 int,知道浮点转整数向零截断,并把 cast、文本解析与拆箱分开判断,就可以进入实验。
规范入口:JLS §5.1:转换的种类、§5.6:数值上下文与提升。