适用读者:有别的语言基础、要快速过一遍 Java 语法的人;或复习时总在细节上翻车的人。
前置:S01-B02(环境)、B01(Java 定位)。
覆盖旧文:2702–2721(零基础教程第 1–20 章)+2947–2965(同题重复导出)+4427(java 语句的技巧)→ 40 篇 / 约 42 万字符。
扩展点:E-S04-021(语法速通的三层结构:语法 → 语义 → 陷阱)、E-S04-022(类型转换的六条规则与丢失精度)、E-S04-023(控制流的边界条件清单)。
下一篇:S04-J08 · 面向对象六讲。
1. 一句话结论
Java 语法本身不难,难的是"默认值 + 隐式转换 + 求值顺序"这三类沉默行为。 它们不报错,只在边界上给出错误结果——这就是"语法都会,代码还是错"的根源。
出处:
A-2702–A-2721(教程第 1–20 章)、A-2947–A-2965(同题重复)、A-4427(语句技巧)。
2. 三层结构(E-S04-021)
第一层 语法:怎么写(声明、运算符、控制流)
第二层 语义:为什么这样写(求值顺序、作用域、类型系统)
第三层 陷阱:什么时候会错(默认值、隐式转换、短路、整数溢出)
判据:只看第一层的人写的代码能编译,但过不了边界用例。
3. 类型系统:四个必答点(E-S04-022)
3.1 八种基本类型与默认值
| 类型 | 位宽 | 默认值(仅字段) | 局部变量 |
|---|---|---|---|
byte/short/int/long |
8/16/32/64 | 0 |
必须显式初始化 |
float/double |
32/64 | 0.0f / 0.0d |
同上 |
char |
16(UTF-16 单元) | \u0000 |
同上 |
boolean |
JVM 相关 | false |
同上 |
坑 1(最常见):"局部变量有默认值"是错的——字段有默认值,局部变量必须先赋值,否则编译失败。
3.2 隐式转换与丢失精度
int a = 1_000_000_000;
long b = a * 3; // ❌ 先按 int 计算溢出(-1294967296),再赋给 long
long c = a * 3L; // ✅ 提升为 long 运算
byte d = (byte) 200; // ❌ 200 超出 byte → -56(截断,不报错)
float f = 0.1f + 0.2f; // ❌ 0.30000001(二进制无法精确表示 0.1)
if (0.1 + 0.2 == 0.3) // ❌ false —— 浮点比较要用误差阈值或 BigDecimal
六条转换规则:小 → 大自动(无丢失);大 → 小需强制转换(截断高位);整型 → 浮点可能丢精度;char 与 int 互通;boolean 不参与;装箱/拆箱在 == 与 null 上有坑(见 S04-B02)。
3.3 == / equals / hashCode 的位置
| 比较对象 | 用 == |
用 equals |
|---|---|---|
| 基本类型 | ✅ 比值 | 不存在 |
| 引用(对象身份) | ✅ 比地址 | ✅ 比内容(需重写) |
| 包装类型 | ⚠️ 缓存区间内为 true(Integer −128~127) |
✅ |
String |
⚠️ 常量池内可能为 true | ✅ |
坑 2:Integer a = 127, b = 127; a==b 为 true;128 为 false——这不是 bug,是 Integer.valueOf 缓存。
3.4 整数溢出与位运算
int max = Integer.MAX_VALUE;
max + 1 == Integer.MIN_VALUE // ❌ true(静默回绕)
Math.addExact(max, 1) // ✅ 抛 ArithmeticException
-1 >>> 28 // 无符号右移:15(不是 -1 的符号位扩展)
坑 3:Math.abs(Integer.MIN_VALUE) 仍是负数——边界值不能假设绝对值有效。
4. 控制流:四条边界清单(E-S04-023)
4.1 if / else 与 switch
// 坑 4:switch 漏写 break → 贯穿(fall-through),且不会报错
switch (day) {
case 1: System.out.println("周一"); // ❌ 漏 break,继续执行 case 2
case 2: System.out.println("周二"); break;
}
// Java 14+ 箭头语法无贯穿
switch (day) { case 1 -> System.out.println("周一"); default -> {} }
switch 可用类型:byte/short/int/char、其包装类、String(Java 7+)、枚举、int 兼容常量。long 与 boolean 不支持(long 需转 int 或用 if)。
4.2 循环四件的边界
| 结构 | 边界要点 |
|---|---|
for |
三段都可空(for(;;) = 死循环);循环变量作用域仅在循环内 |
while / do-while |
do-while 至少执行一次 |
for-each |
底层迭代器;遍历中删除元素会 ConcurrentModificationException(见 S04-J01) |
break / continue |
带标签可跳出多层(outer:) |
坑 5:for-each 里 list.remove(x) 抛 ConcurrentModificationException,而 removeIf / 迭代器 remove 才安全。
4.3 短路与求值顺序
if (obj != null && obj.getX() > 0) // ✅ 短路保护
int i = 0; int[] a = new int[3];
a[i] = i++; // ❌ 结果取决于求值顺序:先算左边索引 0,再自增 → a[0]=0
Java 求值顺序:从左到右(JLS §15.7)。a[i] = i++ 中 a[i] 的 i 先求值为 0,右侧 i++ 返回 0 并自增——所以 a[0]=0、i=1。但可读性极差,工程规约上直接禁止。
4.4 输入输出与 Scanner
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 坑 6:混用 nextInt 与 nextLine → 残留换行被读走
sc.nextLine(); // 需额外消费掉换行
判据:Scanner 混用 nextX 与 nextLine 是新手最常踩的 IO 坑;生产代码用 BufferedReader + readLine 更稳。
5. 数组与递归
5.1 数组的四个事实
① 长度固定;length 是字段不是方法
② 下标从 0 开始,越界抛 ArrayIndexOutOfBoundsException(运行时)
③ 数组是对象,引用可 null;默认元素值 0/false/null
④ 多维是"数组的数组"(锯齿可不等长)
坑 7:int[2][3] 的 length 是 2(第一维),[i].length 才是 3。
int[][] g = new int[3][]; // 只分配第一维
g[0] = new int[]{1,2,3}; // 第二维逐个分配(锯齿合法)
int[][] h = new int[3][4]; // 一次性分配(矩形)
5.2 递归的两个必答项
int fib(int n) { return n < 2 ? n : fib(n-1) + fib(n-2); } // ❌ O(2^n) 重复计算
static int fact(int n) { return n <= 1 ? 1 : n * fact(n - 1); } // 深度 = n
| 问题 | 答法 |
|---|---|
| 栈溢出边界 | 默认栈 512 KB~1 MB,深度约 1 万层;深递归改迭代或加大 -Xss |
| 尾递归 | Java 不做尾调用优化(HotSpot 明确不优化),别照搬函数式语言的结论 |
6. 补充:main、注释与包
public static void main(String[] args) // 必须是 public static void + String[]
| 项 | 要点 |
|---|---|
| 注释 | //、/* */、/** */(Javadoc,可被 javadoc 提取) |
| 包 | 目录结构应与包名一致;package 首行;import 不递归 |
| 命名 | 类 UpperCamel、方法与变量 lowerCamel、常量 UPPER_SNAKE |
final |
变量不可重新赋值;引用不可变不等于对象不可变 |
7. 反例:六个"编译通过但行为错"的片段
| 片段 | 结果 | 原因 |
|---|---|---|
long b = a * 3;(a 为 int) |
溢出后再提升 | 运算按最大操作数类型 |
if (0.1+0.2 == 0.3) |
false | 二进制浮点 |
switch 漏 break |
贯穿执行 | fall-through 是语言特性 |
for-each 中 remove |
抛 CME | 迭代器 modCount 校验 |
int[][] m = new int[2][3]; m.length |
2 | 多维是数组的数组 |
Scanner.nextInt() 后 nextLine() |
读到空串 | 换行残留 |
8. 自测
- 局部变量有默认值吗?哪类变量有?
long b = a * 3;(a 是 int)为什么可能错?正确写法?switch支持哪些类型?long可以吗?for-each中删除元素为什么抛异常?安全写法?- Java 做尾递归优化吗?
答案
- 没有;只有字段/数组元素有默认值。
- 先按 int 运算再提升,溢出;写
a * 3L。 - 整型(含包装)、char、String、枚举;
long不支持。 - 迭代器 modCount 与列表修改计数不一致;用
removeIf或迭代器remove。 - 不做(HotSpot 无 TCO)。
9. 下一篇
S04-J08 · 面向对象六讲 —— 类与对象、构造与初始化顺序、this/super、访问修饰符、重写与重载、多态与 final/instanceof、抽象类与接口、内部类。