【JAVA】【BUG】经常出现的典型 bug 及解决办法

38 阅读5分钟

前言

偶然想到,作为每个入门后端 java 程序员,都会经历各种各样的 bug 问题,那么收集了一下比较经常出现的典型 bug 及其解决方法,供给想入门的新友作为参考与学习,老友呢,加深印象更加熟悉!~ 我们开始吧!

目录序号

  1. 空指针异常(NullPointerException)
  2. 数据库连接未关闭
  3. 线程安全问题
  4. 内存泄漏
  5. 字符串拼接性能问题
  6. 未处理异常
  7. 不当使用集合
  8. 无限递归
  9. 日期格式化线程安全问题
  10. 序列化问题
  11. SQL 注入
  12. 锁粒度问题

1. 空指针异常(NullPointerException)

这是最为常见的错误。当对值为null的对象调用方法、访问属性或者进行其他操作时,就会触发该异常。

String str = null;
int length = str.length(); // 此处会抛出NPE

解决方法

  • 运用Objects.requireNonNull()方法。
  • 借助Optional类来处理可能为null的情况。
  • 进行null值检查。

2. 数据库连接未关闭

要是数据库连接使用完毕后没有及时关闭,会造成连接资源的浪费,严重时可能引发连接池耗尽的问题。

Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 缺少关闭资源的代码

解决方法

  • 采用try-with-resources语句,它能够自动关闭实现了AutoCloseable接口的资源。
  • finally块中关闭资源。

3. 线程安全问题

在多线程环境下,对共享变量进行非原子性操作,或者使用了非线程安全的类,都容易引发数据不一致的情况。

public class Counter {
    private int count = 0;
    public void increment() {
        count++; // 非原子操作,存在竞态条件
    }
}

解决方法

  • 运用synchronized关键字、ReentrantLock或者原子类(如AtomicInteger)。
  • 优先使用线程安全的集合,像ConcurrentHashMap

4. 内存泄漏

长生命周期的对象持有短生命周期对象的引用,会使短生命周期对象无法被垃圾回收,从而造成内存泄漏。

public class Cache {
    private static final Map<String, Object> cache = new HashMap<>();
    public static void add(String key, Object value) {
        cache.put(key, value);
        // 缺少移除机制
    }
}

解决方法

  • 为缓存设置合理的过期策略,例如使用LinkedHashMap实现 LRU 缓存。
  • 及时释放无用的资源,如监听器、连接等。

5. 字符串拼接性能问题

在循环中使用+进行字符串拼接,会产生大量的临时对象,降低性能。

String result = "";
for (int i = 0; i < 1000; i++) {
    result += i; // 性能较差
}

解决方法

  • 使用StringBuilder(非线程安全场景)或者StringBuffer(线程安全场景)。

6. 未处理异常

捕获异常后不进行任何处理,或者直接吞掉异常,会掩盖潜在的问题。

try {
    // 可能抛出异常的代码
} catch (IOException e) {
    // 空实现,未记录日志或处理异常
}

解决方法

  • 记录异常日志。
  • 向上抛出合适的异常。
  • 提供默认处理逻辑。

7. 不当使用集合

在需要唯一元素的场景中使用ArrayList,导致重复元素出现;或者在需要有序集合时使用HashMap

List<String> list = new ArrayList<>();
list.add("a");
list.add("a"); // 允许重复元素

解决方法

  • 根据具体需求选择合适的集合类型,如SetTreeMap等。

8. 无限递归

递归方法缺少终止条件,或者终止条件不满足要求,会导致栈溢出错误(StackOverflowError)。

public void recursiveMethod() {
    recursiveMethod(); // 缺少终止条件
}

解决方法

  • 确保递归方法有明确的终止条件。
  • 考虑使用迭代替代递归。

9. 日期格式化线程安全问题

SimpleDateFormat不是线程安全的类,在多线程环境下共享使用会导致日期解析错误。

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static Date parse(String dateStr) {
    return sdf.parse(dateStr); // 多线程下不安全
}

解决方法

  • 在每个线程中创建独立的SimpleDateFormat实例。
  • 使用线程安全的DateTimeFormatter(Java 8 及以后版本)。

10. 序列化问题

实现Serializable接口的类没有声明serialVersionUID,可能会导致反序列化失败。

public class User implements Serializable {
    private String name;
    // 缺少serialVersionUID
}

解决方法

  • 显式声明serialVersionUID
  • 保持序列化前后类的结构兼容。

11. SQL 注入

直接将用户输入拼接 SQL 语句,会导致 SQL 注入攻击。

String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql); // 存在SQL注入风险

解决方法

  • 使用预编译语句(PreparedStatement)。
  • 对用户输入进行严格的参数校验。

12. 锁粒度问题

同步块范围过大,会影响系统性能;而锁的粒度太小,又可能无法保证线程安全。

public synchronized void process() {
    // 包含大量非关键操作,锁粒度太粗
}

解决方法

  • 缩小同步块的范围。
  • 采用细粒度的锁,如ReentrantLock

总结

要减少 Java 后端开发中的错误,关键在于养成良好的编程习惯,比如:

  • 进行严格的输入校验
  • 合理处理异常
  • 做好资源管理
  • 重视代码审查
  • 编写全面的单元测试

文章小尾巴

文章小尾巴(点击展开)

文章写作、模板、文章小尾巴可参考:《写作“小心思”》
  感谢你看到最后,最后再说两点~
  ①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
  ②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
   我是南方者,一个热爱计算机更热爱祖国的南方人。
  (文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)