Java 易错题及答案详解:避开 90% 开发者踩过的坑,附错误原因分析

在 Java 开发中,许多开发者经常掉入看似简单的陷阱,导致程序崩溃或行为异常。这些错误往往源于对语言特性的误解,而非复杂逻辑问题。本文将精选 5 个高频易错题,通过真实代码示例、错误原因深度分析及正确答案详解,帮助您快速识别并避免这些坑。每个案例都基于实际开发场景,确保您能举一反三,提升代码健壮性。


易错题 1:字符串比较的陷阱

题目描述:开发者使用 == 比较两个字符串内容是否相等,结果出现意外错误。
错误代码示例

String str1 = new String("hello");
String str2 = new String("hello");
if (str1 == str2) { // 错误点
    System.out.println("字符串相等");
} else {
    System.out.println("字符串不相等"); // 实际输出
}

错误原因分析

  • == 在 Java 中比较的是对象引用(内存地址),而非内容。当使用 new String() 创建对象时,即使内容相同,也会分配不同内存地址。
  • 开发者误以为 == 能比较字符串值,忽略了字符串池机制。这会导致逻辑错误,比如在用户输入验证中误判。

正确答案详解
使用 equals() 方法比较内容,确保值相等。

String str1 = new String("hello");
String str2 = new String("hello");
if (str1.equals(str2)) { // 正确方式
    System.out.println("字符串相等"); // 正确输出
}

  • 关键点equals() 检查内容是否相同,适用于所有对象比较。对于字符串字面量(如 "hello"),Java 会复用字符串池,但依赖 == 仍不可靠。

易错题 2:空指针异常(NullPointerException)的常见源头

题目描述:调用对象方法时,对象为 null,导致程序崩溃。
错误代码示例

public class User {
    private String name;
    public String getName() { return name; }
}

public static void main(String[] args) {
    User user = null;
    System.out.println(user.getName().length()); // 抛出 NullPointerException
}

错误原因分析

  • 开发者未对对象进行空值检查,直接调用方法。在复杂业务中,对象可能因未初始化或外部输入为 null
  • 错误常发生在链式调用(如 user.getName().length()),一旦中间环节为 null,整个链条崩溃。

正确答案详解
添加空值检查或使用 Java 8 的 Optional 避免异常。

User user = null;
if (user != null && user.getName() != null) { // 防御性检查
    System.out.println(user.getName().length());
} else {
    System.out.println("对象或名称为空");
}

// 或使用 Optional(更简洁)
Optional.ofNullable(user)
    .map(User::getName)
    .ifPresent(name -> System.out.println(name.length()));

  • 关键点:养成空值检查习惯,或在设计时使用 Optional 封装可能为 null 的对象。

易错题 3:集合遍历中的并发修改异常

题目描述:在遍历 ArrayList 时修改集合,抛出 ConcurrentModificationException
错误代码示例

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String item : list) {
    if (item.equals("B")) {
        list.remove(item); // 错误点:遍历中删除元素
    }
}

错误原因分析

  • Java 的 for-each 循环隐式使用迭代器(Iterator),修改集合(如 remove())会改变迭代器状态,触发异常。
  • 开发者误以为集合操作是线程安全的,或在单线程中忽略修改风险。

正确答案详解
使用迭代器的 remove() 方法或复制集合来安全修改。

// 方法 1:使用迭代器显式删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove(); // 安全删除
    }
}

// 方法 2:创建新集合(避免修改原集合)
List<String> newList = new ArrayList<>(list);
newList.removeIf(item -> item.equals("B"));

  • 关键点:在遍历中修改集合时,必须通过迭代器操作或使用线程安全集合(如 CopyOnWriteArrayList)。

易错题 4:基本类型与包装类的自动装箱陷阱

题目描述:比较基本类型和包装类时,因自动装箱导致逻辑错误。
错误代码示例

Integer a = 100;
Integer b = 100;
if (a == b) { // 可能输出 true
    System.out.println("相等");
} else {
    System.out.println("不相等");
}

Integer c = 200;
Integer d = 200;
if (c == d) { // 输出 false
    System.out.println("相等");
} else {
    System.out.println("不相等"); // 实际输出
}

错误原因分析

  • Java 的自动装箱缓存范围是 $-128$ 到 $127$(即 $ \text{Integer} $ 值在 $[-128, 127]$ 内时复用对象),超出范围则创建新对象。
  • 开发者误用 == 比较包装类,未意识到缓存机制,导致值相等时引用不同。

正确答案详解
始终用 equals() 比较包装类,或直接使用基本类型。

Integer a = 100;
Integer b = 100;
if (a.equals(b)) { // 正确比较内容
    System.out.println("相等"); // 正确输出
}

Integer c = 200;
Integer d = 200;
if (c.equals(d)) { // 输出 true
    System.out.println("相等");
}

  • 关键点:包装类比较应避免 ==;对于数值操作,优先使用基本类型(如 int)。

易错题 5:重写 equals 和 hashCode 的不一致

题目描述:自定义类中重写 equals() 但未重写 hashCode(),导致在集合中使用时行为异常。
错误代码示例

public class Person {
    private String name;
    public Person(String name) { this.name = name; }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person) {
            return this.name.equals(((Person) obj).name);
        }
        return false;
    }
    // 未重写 hashCode()
}

public static void main(String[] args) {
    Set<Person> set = new HashSet<>();
    set.add(new Person("Alice"));
    set.add(new Person("Alice"));
    System.out.println(set.size()); // 输出 2,而非预期的 1
}

错误原因分析

  • Java 要求:如果两个对象 equals() 返回 true,则它们的 hashCode() 必须相同。否则,在 HashSetHashMap 中,对象会被视为不同条目。
  • 开发者忽略这一契约,导致集合无法正确去重或检索。

正确答案详解
同时重写 equals()hashCode(),确保一致性。

public class Person {
    private String name;
    public Person(String name) { this.name = name; }
    
    @Override
    public boolean equals(Object obj) {
        // 省略 equals 实现(同上)
    }
    
    @Override
    public int hashCode() {
        return name.hashCode(); // 基于 name 生成哈希码
    }
}

// 测试:set.size() 输出 1

  • 关键点:遵循 equals-hashCode 契约;使用 IDE 自动生成两者(如 IntelliJ 的 Generate 功能)。

总结

通过以上 5 个易错题,您可以看到 Java 开发中的坑多源于细节疏忽:字符串比较误用 ==、空指针未检查、集合遍历并发修改、包装类缓存机制、以及 equals/hashCode 契约。避开这些错误的关键在于:

  1. 理解语言机制:如字符串池、自动装箱范围 $[-128, 127]$。
  2. 防御性编程:始终检查 null,使用迭代器安全修改集合。
  3. 遵循最佳实践:重写 equals() 时必重写 hashCode()

掌握这些点,您能显著减少程序错误率。建议在日常编码中多写单元测试,验证边界情况。Java 世界博大精深,持续学习核心概念是避免陷阱的根本之道。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐