1. Java基础语法概述
Java作为一门面向对象的编程语言,其基础语法是每个开发者必须掌握的基石。虽然Java语法相对规范统一,但在实际开发中,我们经常会遇到一些看似简单却容易遗忘的语法细节。这些细节往往会导致编译错误、运行时异常或逻辑错误,特别是在面试和代码审查时,对基础语法的掌握程度直接体现了开发者的专业素养。
我在多年的Java开发中发现,即使是经验丰富的工程师,偶尔也会在基础语法上犯迷糊。比如自动装箱拆箱的陷阱、字符串比较的误区、静态代码块的执行时机等。这些问题看似微不足道,却可能在实际项目中引发难以排查的bug。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据类型与运算符的易错点
2.1 基本数据类型与包装类
Java的8种基本数据类型和对应的包装类是最基础的概念,但其中隐藏着不少陷阱:
java复制Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false
这是因为Integer在-128到127之间的值会被缓存,超出这个范围则会创建新对象。建议在比较包装类时总是使用equals()方法。
2.2 浮点数精度问题
java复制System.out.println(0.1 + 0.2 == 0.3); // false
这是由于浮点数在计算机中的表示方式决定的。对于需要精确计算的场景(如金融),应该使用BigDecimal类。
2.3 运算符优先级
java复制int result = 5 + 2 * 3; // 11 不是21
常见的运算符优先级从高到低为:括号 > 单目运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 三元运算符 > 赋值运算符。
3. 流程控制中的注意事项
3.1 switch语句的细节
java复制String day = "MONDAY";
switch (day) {
case "MONDAY":
System.out.println("工作日");
// 缺少break会导致case穿透
case "SATURDAY":
System.out.println("周末");
break;
}
从Java 7开始支持String类型,但要注意case穿透问题。Java 12引入了更简洁的switch表达式语法。
3.2 for循环的陷阱
java复制for (int i = 0; i < 10; i++) {
System.out.println(i);
i = i + 2; // 修改循环变量可能导致意外行为
}
在循环体内修改循环变量通常是不推荐的,这会导致循环次数与预期不符。
4. 面向对象特性的易忘点
4.1 方法重载与重写
方法重载(Overload)的规则:
- 方法名相同
- 参数列表不同(类型、顺序、数量)
- 与返回值类型无关
方法重写(Override)的规则:
- 方法名和参数列表必须相同
- 返回值类型相同或是子类
- 访问权限不能比父类更严格
- 不能抛出比父类更宽泛的异常
4.2 构造函数的执行顺序
java复制class Parent {
{
System.out.println("父类实例代码块");
}
static {
System.out.println("父类静态代码块");
}
Parent() {
System.out.println("父类构造器");
}
}
class Child extends Parent {
{
System.out.println("子类实例代码块");
}
static {
System.out.println("子类静态代码块");
}
Child() {
System.out.println("子类构造器");
}
}
执行顺序为:父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。
4.3 final关键字的多种用法
- final变量:必须初始化且不能修改
- final方法:不能被子类重写
- final类:不能被继承
- final参数:方法内不能修改参数值
5. 字符串处理的常见误区
5.1 字符串拼接的性能
java复制String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都会创建新String对象
}
应该使用StringBuilder来提高性能:
java复制StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
5.2 字符串比较
java复制String s1 = "hello";
String s2 = new String("hello");
String s3 = "hello";
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true
System.out.println(s1.equals(s2)); // true
==比较的是引用地址,equals比较的是内容。字符串常量会被放入字符串常量池。
6. 集合框架的易忘细节
6.1 List的subList方法
java复制List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
List<Integer> subList = list.subList(1, 3);
subList.add(6); // 会修改原list
System.out.println(list); // [1, 2, 3, 6, 4, 5]
subList返回的是原List的视图,对子列表的修改会影响原列表。
6.2 Map的keySet与values
java复制Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
Set<String> keys = map.keySet();
Collection<Integer> values = map.values();
map.put("c", 3); // 修改会影响已获取的keySet和values
System.out.println(keys); // 包含"c"
System.out.println(values); // 包含3
keySet()和values()返回的是Map的视图,会反映Map的后续修改。
7. 异常处理的关键点
7.1 try-with-resources语法
java复制try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
实现了AutoCloseable接口的资源可以在try语句中自动关闭,无需finally块。
7.2 异常捕获顺序
java复制try {
// 可能抛出异常的代码
} catch (FileNotFoundException e) {
// 处理特定异常
} catch (IOException e) {
// 处理更一般的异常
} finally {
// 总是执行的代码
}
应该先捕获更具体的异常,再捕获更一般的异常,否则编译器会报错。
8. Java 8+新特性的易忘点
8.1 Lambda表达式作用域
java复制int num = 1;
Consumer<Integer> consumer = (num) -> { // 编译错误
System.out.println(num);
};
Lambda表达式中的参数不能与局部变量同名。
8.2 Optional的正确用法
java复制Optional<String> optional = Optional.ofNullable(getString());
String result = optional.orElse("default");
// 不要这样用:
if (optional.isPresent()) {
result = optional.get();
}
应该充分利用Optional提供的orElse、orElseGet、map等方法,避免直接使用isPresent和get。
9. 输入输出的常见问题
9.1 字节流与字符流的区别
- 字节流:InputStream/OutputStream,处理二进制数据
- 字符流:Reader/Writer,处理文本数据,有编码
