
同样是加1为什么byte的三种写法结果不同旧的“数据类型与变量”笔记从字面量、类型分类和变量定义开始但还没有走到真正容易出错的地方。与其补一张背诵表不如先看三个很短的片段byte b 126; b b 1; // 编译失败 byte b 126; b 1; // 得到127 byte b 127; b 1; // 得到-128不抛异常为什么明明能算出127第一种写法却过不了编译为什么第二种允许第三种又不帮我们检查越界这三个现象分别涉及表达式类型、赋值上下文和窄化转换。本文用Corretto JDK 17.0.12编译和运行附录是完整程序编译失败的片段各自放进完整类不混进正常程序。1. 结果在范围内不等于表达式类型就是byte普通变量b参加b 1运算时发生数值提升表达式结果类型是int。再把它赋给byte需要窄化。public class BadSum { public static void main(String[] args) { byte b 126; b b 1; } }这段编译失败。编译器不会因为我们目测这一刻的b是126就为一般变量表达式自动做范围证明。对比以下常量byte b 126 1; // 可以常量表达式的值127可表示 byte b 127 1; // 不可以128超出范围所以“右边是int就永远不能赋给byte”也不准确。赋值上下文允许部分可表示的整型常量表达式隐式窄化。JLS 17类型转换与赋值上下文。2. 允许的不是“安全加法”而是隐含转换对于这里的简单变量b 1的作用可以理解成b (byte)(b 1)先按提升后的类型计算再转换回左边的类型。它能编译不承诺转换后保留原来的数学数值。当b是127int结果为128窄化到有符号8位byte后得到-128。图表示转换路径不是JVM对象内存布局。更精确地说复合赋值的左边只求值一次。若左边包含数组下标自增不能机械改写成两个i。完整程序也检查了这一点。JLS复合赋值。业务里要限制取值应该先在较宽类型里计算并检查范围而不是见到能编译就认为数据安全int next b 1; 先检查 next 是否在允许范围内检查通过才转换回 byte。这里的业务范围可能比byte范围更窄。例如音量只允许0到100仅防止byte溢出还不够。3. final也不等于编译期常量final int n 126; byte b n 1;可以编译因为这里n是常量变量其初始化来自常量表达式。但以下完整程序不能编译。即使函数这次总返回126方法调用也不是这里所需的常量表达式public class BadFinal { static int initial() { return 126; } public static void main(String[] args) { final int n initial(); byte b n 1; } }需要区分“赋值后不再改变”与“编译阶段符合常量表达式规则”。同理128本身也不会因为是字面量就获得豁免public class BadRange { public static void main(String[] args) { byte b 128; } }4. 赋值能接受127为什么传参不行public class BadArgument { static void take(byte value) { } public static void main(String[] args) { take(127); } }它也编译失败。127是int字面量调用上下文并不照搬赋值上下文的常量窄化规则。先用byte b 127; take(b);则可以。这是规则所在上下文不同不是数值突然变了。JLS调用上下文。5. 怎样验证而不是只贴几行“预期”验证对象检查方式期望普通变量相加后赋回byte单独编译BadSum类型不兼容final由方法调用初始化单独编译BadFinal类型不兼容128赋给byte单独编译BadRange类型不兼容int常量直接传byte参数单独编译BadArgument类型不兼容正常程序编译后运行并核对输出五项检查通过错误的范围判断把127加1仍期待127断言失败本地验证保存了各次javac诊断不把“进程失败”直接当成预期成立找不到JDK、文件名不匹配或语法写坏都不是我们要验证的失败原因。测试也对照成功程序和具体类型转换诊断。下面是正常程序的实际输出失败片段不会产生对应运行输出promoted127 compound127 narrowed-128 constant127 index1 value127 CHECKS_PASSED56. 回头看旧笔记字面量是源码里的写法类型是表达式与值的规则变量则承载值。不要把“字符串常量、空常量”这种入门分类直接写成基本类型清单String不是基本类型null也不是第九种基本类型。Java输出方法是System.out.println不是System.Out.println。这次不试图一篇讲完全部Java类型。读者真正需要带走的是判断一个转换先问表达式是什么类型再问处于赋值、调用还是复合赋值上下文最后检查窄化会不会丢失需要的值。附录完整可运行程序将以下类作为ByteConversionLab.java用JDK 17执行javac ByteConversionLab.java再执行java ByteConversionLab。它使用显式断言方法不依赖-ea。public class ByteConversionLab { private static int checks; private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); checks; } public static void main(String[] args) { byte first 126; int promoted first 1; check(promoted 127, promoted sum); System.out.println(promoted promoted); byte compound 126; compound 1; check(compound 127, compound within range); System.out.println(compound compound); byte edge 127; edge 1; check(edge -128, narrowed byte wraps); System.out.println(narrowed edge); final int n 126; byte constant n 1; check(constant 127, constant assignment); System.out.println(constant constant); byte[] cells {126, 10}; int index 0; cells[index] 1; check(index 1 cells[0] 127 cells[1] 10, compound evaluates left side once); System.out.println(index index value cells[0]); System.out.println(CHECKS_PASSED checks); } }