Java 作为一门强类型、面向对象的编程语言,有许多语法细节容易被忽略或误用。除了转义字符,以下是一些需要重点注意的语法点:
1. 数据类型与类型转换
- 强类型特性:Java 要求变量必须先声明类型再使用,且类型不可隐式转换为不兼容类型(如
int
不能直接转为boolean
)。 - 自动类型转换限制:仅允许“小范围”类型向“大范围”类型转换(如
byte → short → int → long → float → double
),反之必须显式强制转换,可能导致精度丢失:int a = 1000; byte b = (byte) a; // 强制转换,结果为 -24(因 byte 范围是 -128~127)
- 包装类与基本类型:自动装箱(
int → Integer
)和拆箱(Integer → int
)可能隐藏空指针风险:Integer num = null; int n = num; // 运行时抛 NullPointerException(拆箱时null无法转换)
2. 字符串(String)的特殊性
- 不可变性:
String
对象创建后内容不可修改,所有拼接、替换操作都会生成新对象。频繁修改字符串应使用StringBuilder
(非线程安全)或StringBuffer
(线程安全):String s = "a"; s += "b"; // 实际生成新的 String 对象,原对象被废弃
==
与equals()
的区别:==
比较对象地址(引用),equals()
比较内容(需注意null
安全):String s1 = "abc"; String s2 = new String("abc"); System.out.println(s1 == s2); // false(地址不同) System.out.println(s1.equals(s2)); // true(内容相同)
- 常量池缓存:直接赋值的字符串(如
String s = "abc"
)会被缓存到常量池,而new String()
会创建新对象(除非调用intern()
方法)。
3. 数组的语法细节
- 初始化方式:数组必须初始化后才能使用,分为静态初始化(指定元素)和动态初始化(指定长度):
int[] arr1 = {1, 2, 3}; // 静态初始化(长度由元素数决定) int[] arr2 = new int[3]; // 动态初始化(元素默认值为 0)
- 长度固定:数组一旦创建,长度不可修改,访问超出范围的索引会抛
ArrayIndexOutOfBoundsException
。 - 二维数组的不规则性:Java 二维数组的每行可以是不同长度(本质是“数组的数组”):
int[][] arr = new int[2][]; arr[0] = new int[3]; // 第一行长度 3 arr[1] = new int[5]; // 第二行长度 5
4. 面向对象核心语法
- 方法重写(Override)的限制:
- 子类方法的访问权限必须 不低于 父类(如父类
protected
方法,子类可改为public
,但不能改为private
)。 - 返回值类型必须是父类方法返回值的 子类或相同类型(Java 5+ 支持协变返回)。
- 不能重写
final
方法,不能重写父类的private
方法(因子类不可见)。
- 子类方法的访问权限必须 不低于 父类(如父类
this
与super
的区别:this
指代当前对象,用于访问本类成员或调用本类构造方法(this(...)
)。super
指代父类对象,用于访问父类成员或调用父类构造方法(super(...)
),且必须放在构造方法第一行。
- 构造方法特性:
- 无返回值,名称与类名完全一致。
- 若未显式定义构造方法,编译器会生成默认无参构造;若显式定义了构造方法,默认构造会被覆盖。
- 子类构造方法会 默认调用父类无参构造,若父类没有无参构造,子类必须显式用
super(...)
调用父类有参构造,否则编译报错。
5. 异常处理的陷阱
finally
块的执行时机:finally
块 总会执行(除非 JVM 退出),即使try
或catch
中有return
:public static int test() { try { return 1; } finally { return 2; // 最终返回 2(覆盖 try 中的 return) } }
- 异常类型匹配:
catch
块按顺序匹配异常,子类异常必须放在父类异常之前,否则子类异常块会被屏蔽:try { // ... } catch (Exception e) { // 父类异常放前面,会屏蔽子类异常 // ... } catch (NullPointerException e) { // 永远不会执行 // ... }
- checked 与 unchecked 异常:
- checked 异常(如
IOException
)必须显式处理(try-catch
)或声明抛出(throws
)。 - unchecked 异常(如
NullPointerException
、IndexOutOfBoundsException
)继承自RuntimeException
,可不必显式处理。
- checked 异常(如
6. 泛型的限制
- 类型擦除:泛型信息在编译后会被擦除,运行时无法获取泛型的具体类型:
List<String> list = new ArrayList<>(); System.out.println(list.getClass() == ArrayList.class); // true(擦除后都是 ArrayList)
- 不能使用基本类型:泛型参数必须是引用类型,如
List<int>
错误,需用List<Integer>
。 - 通配符的区别:
<? extends T>
表示“T 及其子类”(上限),<? super T>
表示“T 及其父类”(下限),使用时需注意读写限制。
7. 关键字的特殊用法
final
:- 修饰变量:值不可修改(引用类型则引用不可变,对象内容可改)。
- 修饰方法:不可被重写。
- 修饰类:不可被继承(如
String
、Integer
都是 final 类)。
static
:- 静态成员(变量/方法)属于类,而非实例,可通过
类名.成员
直接访问。 - 静态方法中不能访问非静态成员(因无实例对象),也不能使用
this
或super
。
- 静态成员(变量/方法)属于类,而非实例,可通过
transient
:修饰的变量不会被序列化(如敏感信息可标记为 transient)。volatile
:保证变量的可见性(多线程中一个线程修改后,其他线程能立即看到),但不保证原子性。
8. 流程控制的细节
switch
语句:- Java 7+ 支持
String
作为switch
表达式,但case
标签必须是常量(字面量或final
变量)。 - 缺少
break
会导致“穿透现象”(执行当前case
后继续执行下一个case
)。
- Java 7+ 支持
for-each
循环:只能遍历元素,不能修改数组/集合的长度或通过索引访问,且对null
数组/集合会抛NullPointerException
。
9. 访问修饰符的范围
修饰符 | 本类 | 同包 | 子类(不同包) | 其他包 |
---|---|---|---|---|
private |
√ | × | × | × |
默认(无修饰) | √ | √ | × | × |
protected |
√ | √ | √ | × |
public |
√ | √ | √ | √ |
- 注意:
protected
修饰的成员在不同包子类中,只能通过子类实例访问,不能通过父类实例访问。
这些语法点是 Java 中最容易出错的地方,理解并掌握它们能有效减少编译错误和运行时异常,提升代码的健壮性。