Java 最佳实践

FreeGuideOnline 最新 2026-07-15

Java 最佳实践:写出专业、可维护的高质量代码

Java 作为企业级开发的主流语言,仅仅是“代码能跑”是远远不够的。本教程将带你系统性地掌握从编码规范到性能优化的全套 Java 最佳实践,教你写出同事愿意维护、测试友好、生产稳定的代码。本文内容由浅入深,特别适合已掌握 Java 基础,希望提升工程素养的开发者。

一、编码规范与可读性

代码是写给人看的,只是顺便让机器执行。在团队协作和长期维护中,统一、清晰的编码风格比精妙的算法更重要。

1.1 命名约定

  • 类与接口:使用大驼峰命名法(PascalCase),名词或名词短语。如 CustomerService, OrderRepository
  • 方法与变量:使用小驼峰命名法(camelCase),动词或动词短语。如 calculateTotalPrice(), isAvailable
  • 常量:全大写,单词间用下划线分隔。如 MAX_RETRY_COUNT
  • 避免缩写:除非是业内通用缩写(如 IdUrl),否则使用完整单词。calculateAverage() 永远比 calcAvg() 更易理解。

1.2 代码格式

  • 缩进统一使用 4 个空格,避免 Tab 混用。
  • 单行长度建议不超过 120 字符,IDE 配置参考线。
  • 操作符两侧加空格,方法参数逗号后加空格。
  • 大括号采用 K&R 风格(左大括号不换行),与 Java 官方风格保持一致。

1.3 注释的正确姿势

  • 注释解释“为什么”,而不是“做什么”。代码本身应能自描述做什么。
  • 及时更新注释,过期的注释比没有注释危害更大。
  • 使用 Javadoc 为公开 API 编写文档,包括 @param, @return, @throws 等标签。
  • 避免大段废弃代码被注释掉,应直接删除(版本管理工具会记住它)。

二、防御性编程与异常处理

健壮的代码永远假设输入可能非法、外部资源可能失效、依赖的模块可能崩溃。

2.1 参数校验

在每个方法的入口处对关键参数进行非空、合法范围等判断。善用 java.util.Objects 工具类。

public void processOrder(Order order) {
    Objects.requireNonNull(order, "order must not be null");
    if (order.getItems() == null || order.getItems().isEmpty()) {
        throw new IllegalArgumentException("Order must contain at least one item");
    }
    // 业务逻辑
}

尽早抛出异常,并附带上下文信息,方便定位问题。

2.2 异常的正确使用

  • 只捕获你能处理的异常。不要为了“不让程序崩溃”而使用空的 catch 块。
  • 如果当前层无法处理,将原始异常包装后重新抛出,保留异常链:
    catch (IOException e) {
        throw new UncheckedIOException("Failed to read config file", e);
    }
    
  • 别用异常控制正常业务流程。先检查条件(如文件是否存在),而不是依赖捕获 FileNotFoundException
  • 在顶层框架或统一的异常处理器中记录并转换异常,避免向用户暴露堆栈信息。

2.3 资源管理

所有实现了 AutoCloseable 接口的资源(流、数据库连接、文件句柄等)必须使用 try-with-resources 语句确保关闭:

try (FileInputStream fis = new FileInputStream("data.txt");
     BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 处理
    }
}

这比在 finally 块中手动关闭更简洁且安全,避免资源泄漏。

三、集合与泛型的最佳使用

Java 集合框架使用频率极高,不恰当的选择和用法会引发性能瓶颈和隐蔽的 bug。

3.1 选择合适的集合类型

  • 需要快速随机访问:ArrayList(O(1))优于 LinkedList
  • 频繁在头部插入删除:LinkedListArrayDeque
  • 需要去重或集合运算:HashSet(无序)或 TreeSet(排序)或 LinkedHashSet(保留插入顺序)。
  • 键值对映射:HashMap(通用)、TreeMap(排序)、LinkedHashMap(保留插入顺序),并发场景用 ConcurrentHashMap

3.2 使用泛型提升类型安全

永远不要使用原始类型(raw type),使用具体类型参数:

List<String> names = new ArrayList<>();  // 正确
List list = new ArrayList();            // 危险,丧失类型检查

对于泛型方法,利用 PECS 原则(Producer Extends, Consumer Super)来定义通配符边界,使 API 更加灵活:

// 能读取 Number 及其子类的集合
public void readNumbers(List<? extends Number> list) { ... }
// 能存放 Integer 及其父类的集合
public void addIntegers(List<? super Integer> list) { ... }

3.3 不可变集合

当集合不应被修改时,使用 Collections.unmodifiableList() 或 Java 9+ 的 List.of(), Set.of(), Map.of()。这能防止意外修改并提升线程安全性。

3.4 避免常见的性能坑

  • 在循环中拼接字符串使用 StringBuilder,不要用 + 操作符。
  • 遍历 Map 需要键和值时,使用 entrySet() 而不是先遍历 keySet()get()
  • 使用 Collections.emptyList() 等返回空集合而不是 null,省去调用方的判空负担。

四、并发与多线程实践

写出正确的并发代码难度很高,遵守一些原则可以有效减少错误。

4.1 优先使用高级并发工具

不要直接操作 Thread 和原始的 wait/notify。应该使用 java.util.concurrent 包下的:

  • ExecutorService 管理线程生命周期。
  • CompletableFuture 组合异步任务。
  • CountDownLatch, CyclicBarrier 等同步辅助类。
  • 线程安全集合如 ConcurrentHashMap, CopyOnWriteArrayList

4.2 确保共享变量的可见性

对多个线程间共享的变量,必须使用 volatile 关键字保证可见性,或使用 synchronized/Lock 保证原子性和可见性。绝不要依赖“感觉上”运行正常而忽视 happens-before 原则。

4.3 锁的使用原则

  • 仅在有可变状态共享时才使用锁。
  • 同步块要尽可能小,避免持有锁时进行耗时操作(I/O、网络调用)。
  • 使用 ReentrantLock 可实现更灵活的锁控制,如超时尝试、可中断获取锁。
  • 避免嵌套锁,如无法避免,必须保持锁顺序一致,防止死锁。

4.4 线程安全的编码模式

  • 尽量设计不可变对象(final 类、所有字段 final、无修改方法),天然线程安全。
  • 使用线程封闭(ThreadLocal)将对象限制在单个线程内。
  • 使用 volatile 的布尔标志来安全停止线程,而不是过时的 Thread.stop()

五、性能与内存优化

不要过早优化,但应该有意识地写出高性能的代码,避免明显低效的实现。

5.1 字符串与正则优化

  • 编译一次可复用的正则对象:Pattern.compile() 后复用 Pattern 实例。
  • 大批量格式化操作考虑使用 MessageFormatString.format(),但注意其开销,对极致性能场景可用手动拼接。

5.2 缓存与懒加载

  • 对于计算成本高的数据,使用本地缓存(如 HashMap 作为简单缓存,或用 Caffeine 等成熟库)。
  • 注意缓存失效策略和内存占用,避免缓存泄漏。
  • 懒加载(如单例的延迟初始化)可加快启动速度,但要确保线程安全(双检锁或静态内部类)。

5.3 避免创建不必要的对象

  • 优先使用 Long.valueOf() 等静态工厂方法,可能利用缓存。
  • 对于频繁使用的同一逻辑,重用对象(如 Boolean.TRUE 而不是 new Boolean(true))。
  • 注意自动装箱带来的性能开销,尤其是在循环中。

5.4 IO 优化

  • 使用缓冲流(BufferedInputStream/BufferedOutputStream)包装文件流以减少系统调用。
  • 处理文本时指定字符集,如 StandardCharsets.UTF_8,避免依赖平台默认字符集。
  • 大文件操作使用 NIO (FileChannel, MappedByteBuffer) 获得更好的性能。

六、测试与代码质量保障

高质量代码必须可测试,并且被自动化测试覆盖。

6.1 单元测试最佳实践

  • 使用 JUnit 5 编写测试,测试类名以 Test 结尾,方法名清晰表达测试意图(如 shouldThrowExceptionWhenInputInvalid)。
  • 遵循 AAA 模式:Arrange(准备数据)、Act(执行被测方法)、Assert(验证结果)。
  • 使用 Mockito 等框架隔离外部依赖,只测试当前单元逻辑。
  • 测试覆盖率追求有意义的分支覆盖,而不是 100% 数字目标。

6.2 编写可测试代码

  • 依赖注入:通过构造函数或 setter 注入依赖,而不是在方法内部 new 具体类,方便替换为 mock。
  • 避免过度使用静态方法和静态状态,它们难以 mock 和隔离。
  • 让类尽量小且职责单一,便于编写针对性的测试。

6.3 静态代码分析与代码审查

  • 集成静态分析工具,如 SonarQube、Checkstyle、SpotBugs,在 CI 阶段自动检查代码规范、潜在 bug 和安全漏洞。
  • 坚持代码审查(Code Review),关注逻辑正确性、可读性、性能和安全问题。

七、安全编程实践

Java 应用经常面临安全威胁,基础的安全习惯能堵住大部分漏洞。

7.1 防范 SQL 注入

  • 永远使用 PreparedStatement 参数化查询,绝不用字符串拼接 SQL 语句。
  • 对于动态表名或列名,使用白名单校验,不能直接将用户输入拼入。

7.2 跨站脚本(XSS)防范

  • 在 Web 应用中对输出到页面的内容进行 HTML 实体编码。
  • 使用成熟的模板引擎(如 Thymeleaf)的自动转义功能。

7.3 敏感信息处理

  • 不在日志中打印密码、Token、身份证号等敏感数据。
  • 内存中的敏感数据使用 char[] 而不是 String,因为字符串不可变无法主动清除。
  • 使用专业的加密库和算法(如 AES),切忌自己实现加密。

7.4 输入验证与输出编码

所有来自外部的输入都需要验证:长度、格式、范围。输出到不同上下文(HTML、JavaScript、SQL)时分别编码。这是安全开发的核心原则。


遵循这些最佳实践并非一日之功,但每掌握一条,你的 Java 代码质量就会提升一个台阶。将它们融入日常开发习惯,长期坚持,便能自然写出健壮、可维护且优雅的 Java 程序。