0%

写java程序应该注意的一些小细节

Java 编程细节:提升代码质量的关键注意事项

在 Java 开发中,一些看似微小的细节往往决定了代码的健壮性、可读性和性能。本文总结了日常编程中容易忽略的关键细节,涵盖方法重写、对象比较、异常处理等核心场景,帮助开发者写出更可靠的代码。

重写 equalshashCode 的注意事项

equalshashCodeObject 类的核心方法,用于对象比较和哈希表操作,重写时需严格遵循规范,否则可能导致集合框架(如 HashMapHashSet)行为异常。

重写 equals 时必须使用 @Override 注解

  • 错误示例:误将参数类型改为具体类(如User),导致实际是重载而非重写。

    1
    2
    3
    4
    // 错误:这是重载(参数类型为 User),而非重写 Object 的 equals
    public boolean equals(User other) {
    return this.id == other.id;
    }
  • 正确做法:参数类型必须为Object,并添加@Override注解,让编译器校验正确性。

    1
    2
    3
    4
    5
    6
    7
    @Override // 强制编译器检查是否正确重写
    public boolean equals(Object obj) {
    if (this == obj) return true; // 自身比较
    if (obj == null || getClass() != obj.getClass()) return false; // 类型不一致
    User other = (User) obj; // 强制转型
    return this.id == other.id; // 核心比较逻辑
    }

重写 equals 必须同时重写 hashCode

  • 规范要求Object 类文档明确规定:

    若两个对象通过 equals 比较相等,则它们的 hashCode 必须返回相同的值。
    若两个对象 hashCode 不同,则 equals 一定返回 false

  • 后果:若仅重写 equals 而忽略 hashCode,会导致对象在哈希集合(如 HashSet)中无法正确去重,或在 HashMap 中作为键时无法被正确查找。

  • 正确示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public boolean equals(Object obj) {
    // 省略 equals 实现...
    }

    @Override
    public int hashCode() {
    // 确保 equals 相等的对象,hashCode 也相等
    return Objects.hash(id, name); // 推荐使用 Objects.hash 组合字段
    }
  • 最佳实践

    • 参与 equals 比较的字段(如 idname),必须全部纳入 hashCode 计算。
    • 避免使用可变字段作为 hashCode 计算依据(对象修改后哈希值变化,导致集合操作异常)。

字符串处理的细节

字符串是 Java 中最常用的数据类型,其不可变性和常量池特性需特别注意。

避免频繁创建字符串对象

  • 问题String 是不可变对象,每次拼接(+)都会创建新对象,性能低下。

  • 优化

    • 少量拼接可用 StringBuilder(非线程安全)或 StringBuffer(线程安全)。
    • 编译期确定的字符串常量,JVM 会自动优化(如 String s = "a" + "b" 等价于 "ab")。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 低效:循环中使用 + 拼接
    String result = "";
    for (int i = 0; i < 1000; i++) {
    result += i; // 每次创建新 String 对象
    }

    // 高效:使用 StringBuilder
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 1000; i++) {
    sb.append(i); // 仅修改内部字符数组
    }
    String result = sb.toString();

字符串比较用 equals,而非 ==

  • ==:比较对象的内存地址(是否为同一实例)。

  • equals:比较字符串内容(需非 null 调用,否则抛 NullPointerException)。

    1
    2
    3
    4
    5
    6
    7
    8
    String s1 = "hello";
    String s2 = new String("hello"); // 新建对象,内存地址不同

    System.out.println(s1 == s2); // false(地址不同)
    System.out.println(s1.equals(s2)); // true(内容相同)

    // 安全比较(避免 null 问题)
    System.out.println(Objects.equals(s1, s2)); // true(内部处理 null 逻辑)

异常处理的规范

异常处理不仅是错误捕获,更关乎代码的可维护性和问题排查效率。

避免捕获 ExceptionThrowable

  • 问题:大范围捕获异常会掩盖真正的错误(如 NullPointerExceptionOutOfMemoryError),导致调试困难。

  • 正确做法:捕获具体异常(如 IOExceptionSQLException),仅处理已知且可恢复的错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 错误:捕获所有异常,无法区分业务异常和系统错误
    try {
    // 业务逻辑
    } catch (Exception e) {
    e.printStackTrace();
    }

    // 正确:捕获具体异常
    try {
    Files.readAllBytes(Paths.get("file.txt"));
    } catch (IOException e) { // 仅处理 IO 相关错误
    log.error("文件读取失败", e); // 详细日志,而非仅打印堆栈
    throw new BusinessException("文件不存在", e); // 包装为业务异常
    }

不要忽略异常(空 catch 块)

  • 问题:空 catch 块会导致错误被隐藏,后续排查时无法追溯原因。

  • 正确做法:至少记录日志,或根据业务逻辑抛出合理异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 错误:异常被忽略,问题无法排查
    try {
    // 可能抛出异常的代码
    } catch (IOException e) {
    // 空块
    }

    // 正确:记录日志
    try {
    // 可能抛出异常的代码
    } catch (IOException e) {
    log.warn("非关键操作失败,不影响主流程", e); // 记录警告日志
    }

集合操作的细节

集合是 Java 编程的基础,不当使用会导致性能问题或逻辑错误。

遍历集合时避免修改结构

  • 问题:使用 for-each 或迭代器遍历集合时,若直接调用 add/remove 修改结构,会抛出 ConcurrentModificationException

  • 解决方案

    • 使用迭代器的 remove 方法(仅 Iterator 支持,ListIterator 还支持 add)。
    • 遍历副本,修改原集合。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

    // 错误:for-each 中修改集合
    for (String s : list) {
    if (s.equals("b")) {
    list.remove(s); // 抛 ConcurrentModificationException
    }
    }

    // 正确:使用迭代器
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    String s = iterator.next();
    if (s.equals("b")) {
    iterator.remove(); // 安全删除
    }
    }

初始化集合时指定初始容量

  • 问题ArrayListHashMap 等集合默认容量较小(如 ArrayList 初始容量为 10),频繁扩容会导致数组复制,影响性能。

  • 优化:根据预估大小设置初始容量,减少扩容次数。

    1
    2
    3
    // 已知需存储 1000 个元素,指定初始容量
    List<String> list = new ArrayList<>(1000); // 避免多次扩容
    Map<String, Integer> map = new HashMap<>(2000); // HashMap 需考虑负载因子(默认 0.75)

其他关键细节

1. 包装类的自动装箱与拆箱

  • 注意:包装类(IntegerLong 等)的 == 比较的是对象地址,而非值(除非是 -128~127 范围内的 Integer,JVM 会缓存)。

  • 推荐:使用 equals 比较值,或通过 intValue() 转为基本类型后比较。

    1
    2
    3
    4
    5
    6
    7
    8
    Integer a = 127;
    Integer b = 127;
    System.out.println(a == b); // true(缓存范围内)

    Integer c = 128;
    Integer d = 128;
    System.out.println(c == d); // false(超出缓存范围)
    System.out.println(c.equals(d)); // true(比较值)

2. 静态常量与枚举

  • 常量:使用 static final 定义常量,避免魔法值(如 if (type == 1) 应改为 if (type == OrderType.NORMAL))。

  • 枚举:多常量场景(如状态、类型)优先使用 enum,而非整数或字符串,增强代码可读性和类型安全。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 推荐:使用枚举
    public enum OrderStatus {
    PENDING, PAID, SHIPPED, DELIVERED
    }

    // 使用
    if (order.getStatus() == OrderStatus.PAID) {
    // 处理已支付订单
    }

3. 资源关闭

  • 必须:文件流、数据库连接、网络连接等资源需手动关闭,推荐使用 try-with-resources 自动管理(Java 7+)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 正确:自动关闭资源
    try (FileInputStream fis = new FileInputStream("file.txt");
    BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line;
    while ((line = br.readLine()) != null) {
    // 处理内容
    }
    } catch (IOException e) {
    log.error("文件处理失败", e);
    }

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10