类型转换
本章定位:类型转换是Java基本类型之间的"交通规则"——自动拓宽(widening)让你安全地把小类型装进大类型,强制窄化(narrowing)则需要你签"后果自负"的知情同意书。小崔曾因表达式中的自动提升规则踩坑,把
byte+byte的结果赋给byte导致编译失败。白歌画了一张类型转换全景图,从此小崔再也没犯过这类错误。
类型转换全景图
核心记忆:
字节型 byte → 短整型 short → 整型 int → 长整型 long → 单精度浮点型 float → 双精度浮点型 double,从左到右自动,从右到左强转。字符型 char 可以自动转为整型 int 但反之需要强转。
一、自动类型转换(Widening)
定义与规则
自动类型转换(又称拓宽转换)发生在将取值范围小的类型赋值给取值范围大的类型时,编译器会自动完成,无需任何额外代码。
自动转换的触发场景
| 场景 | 示例 | 说明 |
|---|---|---|
| 赋值 | long l = 100; | int字面量→long自动 |
| 方法参数 | method(byteValue) 接受int参数 | 实参自动提升 |
| 算术运算 | byte + short → int | 二元数值提升 |
| 返回语句 | return byteValue; 方法返回long | 返回值自动提升 |
自动转换规则表
| 源类型 | 可自动转换为 |
|---|---|
byte(字节型) | short, int, long, float, double |
short(短整型) | int, long, float, double |
char(字符型) | int, long, float, double |
int(整型) | long, float, double |
long(长整型) | float, double |
float(单精度浮点型) | double |
注意:
long→float是自动的,但可能有精度损失!float只有23位尾数,而long有64位,转换时可能丢失低位。
二、强制类型转换(Narrowing)
定义与语法
强制类型转换(又称窄化转换)将取值范围大的类型转为取值范围小的类型,必须显式使用(目标类型)语法。
目标类型 变量名 = (目标类型) 表达式;
强制转换的后果
| 情况 | 结果 |
|---|---|
| 目标值在范围内 | 值不变 |
| 超出范围(整数) | 溢出回绕:高位截断,得到意想不到的值 |
| 浮点→整数 | 截断小数:直接丢弃小数部分(不是四舍五入) |
| double→float | 可能丢失精度,超出范围则变为±Infinity |
三、表达式中的自动提升
这是面试最爱考、实战最容易踩的坑!
规则
- 单操作数提升:
byte、short、char在运算前自动提升为int - 双操作数提升:两个操作数中,向更高的类型看齐
- 提升层级:
int → long → float → double
完整代码示例
示例一:飞翔科技——购物车金额的类型转换
/**
* 飞翔科技 - 类型转换综合演示
* 场景:飞翔商城购物结算,涉及多种类型的自动与强制转换
*/
public class TypeConversionDemo {
public static void main(String[] args) {
// 原始数据
byte vipLevel = 3; // VIP等级
short quantity = 2; // 购买数量
int productId = 10086; // 商品ID
long orderId = 20251201123456L; // 订单号
float price = 188.88f; // 单价
double taxRate = 0.06; // 税率6%
// === 自动类型转换 ===
// 1. byte → int(小崔的VIP等级判断用int接收)
int vipInt = vipLevel; // 自动
System.out.println("VIP等级(byte→int):" + vipInt);
// 2. short → float(数量参与浮点计算)
float totalPrice = price * quantity; // quantity自动提升为float
System.out.println("总价(short→float):" + totalPrice);
// 3. int → double(工号变成double)
double productIdDouble = productId; // 自动
System.out.println("商品ID(int→double):" + productIdDouble);
// 4. long → float(订单号当作浮点数?精度损失演示)
float orderIdFloat = orderId; // 自动但有精度损失
System.out.println("订单号(long→float):" + orderIdFloat
+ " (精度损失:" + (orderId - (long)orderIdFloat) + ")");
// === 强制类型转换 ===
// 5. double → float(税率)
float taxRateFloat = (float) taxRate;
System.out.println("\n税率(double→float):" + taxRateFloat);
// 6. float → int(截断小数)
int totalPriceInt = (int) totalPrice; // 截断,不是四舍五入
System.out.println("总价截断(float→int):" + totalPriceInt);
// 7. double → long(地月距离取整)
double moonDistance = 384400.12345;
long moonDistanceKm = (long) moonDistance; // 截断小数
System.out.println("地月距离取整(double→long):" + moonDistanceKm + " km");
// === 表达式中的自动提升 ===
byte b1 = 10, b2 = 20;
// byte b3 = b1 + b2; // 编译错误!b1+b2提升为int
int b3 = b1 + b2; // 必须用int接收
System.out.println("\nbyte+byte=int:" + b1 + "+" + b2 + "=" + b3);
}
}
运行输出:
VIP等级(byte→int):3
总价(short→float):377.76
商品ID(int→double):10086.0
订单号(long→float):2.02512007E13 (精度损失:-65536)
税率(double→float):0.06
总价截断(float→int):377
地月距离取整(double→long):384400 km
byte+byte=int:10+20=30
示例二:飞翔科技——工资计算的类型转换实战
/**
* 飞翔科技 - 表达式自动提升演示
* 场景:大翔的工资计算,涉及多种类型的混合运算
*/
public class SalaryTypeConversionDemo {
public static void main(String[] args) {
// 基础数据(多种类型混合)
int workDays = 22; // 出勤天数
float dailyWage = 404.00f; // 日薪
float basicSalary = 8888.00f; // 基本工资
double performanceBonus = 18888.00; // 绩效奖金(double)
long yearBonus = 10000L; // 年终奖基数(long)
// 混合运算:int * float → float
float monthSalary1 = workDays * dailyWage;
System.out.println("月薪1(int*float):" + monthSalary1);
// 混合运算:float + double → double
double totalSalary1 = basicSalary + performanceBonus;
System.out.println("总薪1(float+double→double):" + totalSalary1);
// 混合运算:float + double + long → double(向最高看齐)
double totalSalary2 = basicSalary + performanceBonus + yearBonus;
System.out.println("总薪2(float+double+long→double):" + totalSalary2);
// 强制转换:double → float
float totalSalaryFloat = (float) (basicSalary + performanceBonus);
System.out.println("总薪(double→float):" + totalSalaryFloat);
// 经典陷阱:两个int相除
int total = 100;
int count = 3;
double average1 = total / count; // 先int/int=33,再33→33.0
double average2 = (double) total / count; // 先100.0/3=33.333...
System.out.println("\n【int除法陷阱】");
System.out.println("total/count = " + average1 + " ← 错误!先整除再转double");
System.out.println("(double)total/count = " + average2 + " ← 正确");
// 大翔的实发工资:多种类型混合 → 最终double
float socialSecurity = 2320.00f;
float personalTax = 1666.66f;
double actualSalary = basicSalary + performanceBonus
- socialSecurity - personalTax + yearBonus;
System.out.println("\n实发工资:" + String.format("%.2f", actualSalary));
}
}
运行输出:
月薪1(int*float):8888.0
总薪1(float+double→double):27776.0
总薪2(float+double+long→double):37776.0
总薪(double→float):27776.0
【int除法陷阱】
total/count = 33.0 ← 错误!先整除再转double
(double)total/count = 33.333333333333336 ← 正确
实发工资:33789.34
易错场景
反例一:byte/short/char 运算必须用int接收
// ❌ 经典错误
byte a = 10;
byte b = 20;
byte sum = a + b; // 编译错误!a+b 结果是 int
short s1 = 100;
short s2 = 200;
short s3 = s1 + s2; // 编译错误!s1+s2 结果是 int
char c1 = 'A';
char c2 = 'B';
char c3 = c1 + c2; // 编译错误!c1+c2 结果是 int
// ✅ 正确做法
int sum = a + b; // 用 int 接收
byte sumByte = (byte)(a+b); // 或强转(需确保不溢出)
反例二:int除法先于类型转换
// ❌ 错误:先int除法,再赋值给double
double price = 100 / 3; // 结果是 33.0,不是 33.333...
double discount = 3 / 10; // 结果是 0.0,不是 0.3!
// ✅ 正确:先转换类型,再做除法
double price2 = 100.0 / 3; // 33.333...
double discount2 = (double) 3 / 10; // 0.3
double discount3 = 3.0 / 10; // 0.3
反例三:复合赋值运算符内置强转
// 这个行为很多人不知道
byte b = 10;
b += 5; // 编译通过!等价于 b = (byte)(b + 5)
b = b + 5; // 编译错误!b+5 是 int,不能赋给 byte
short s = 100;
s *= 2; // 编译通过!等价于 s = (short)(s * 2)
// 结论:复合赋值运算符(+=, -=, *=, /=, %=)内置了强制类型转换
反例四:long转float的精度损失
// ❌ 隐式精度损失:long有64位有效数字,float只有23位尾数
long bigValue = 123456789012345L;
float floatValue = bigValue; // 自动转换(允许),但有精度损失!
System.out.println(bigValue); // 123456789012345
System.out.println(floatValue); // 1.23456794E14(损失了精度)
System.out.println(bigValue - (long)floatValue); // 差值非零!
面试考点
Q1:short s1 = 1; s1 = s1 + 1; 和 short s1 = 1; s1 += 1; 哪个会编译错误?为什么?
s1 = s1 + 1编译错误。因为s1 + 1中s1先提升为int,1也是int,结果为int,不能自动窄化赋给short。s1 += 1编译通过。复合赋值运算符+=内置了隐式强制转换,等价于s1 = (short)(s1 + 1)。
Q2:long l = 100; 不需要加L,为什么float f = 3.14; 却需要加f?
100是int字面量,在int范围内,赋值给long是拓宽转换(自动),安全。3.14是double字面量,赋值给float是窄化转换,可能有精度损失,编译器要求显式加f后缀。数字后的小数点改变了默认类型。
Q3:double d = 5 / 2; 结果是多少?
结果是
2.0,不是2.5。因为5/2先以int执行整数除法得2,然后2自动拓宽为2.0赋给d。类型转换发生在除法完成之后。正确写法:double d = 5.0 / 2;或double d = (double)5 / 2;。
Q4:为什么char可以自动转为int,但int不能自动转为char?
char是无符号16位(0~65535),int是有符号32位(-21亿~21亿)。char→int是拓宽:所有char值都在int范围内。int→char是窄化:int可能为负数或超过65535,转为char会丢失信息。因此char→int自动,反之需强转。
Q5:long→float的自动转换会丢失精度吗?为什么还允许自动?
会丢失精度。float只有23位尾数(约7位有效数字),long有64位(约19位有效数字)。但float的范围(约±3.4×10³⁸)远大于long(约±9×10¹⁸),所以从"范围"角度是拓宽,允许自动。精度损失是性能与便利之间的权衡——Java继承了C的这条规则。