Appearance
最佳实践
Java 开发最佳实践,涵盖命名、方法设计、异常处理、空值处理、集合使用、并发编程和日志记录等核心领域。
一、命名规范
1.1 使用有意义的名称
名称应该回答:它为什么存在、做什么事、怎么用。好的命名可以大幅减少注释的需求。
java
// ❌ 糟糕的命名
int d; // 经过多少天?
List<String> list; // 什么类型的列表?
void process(); // 处理什么?
// ✅ 好的命名
int elapsedTimeInDays;
List<String> activeUserNames;
void sendEmailNotification();1.2 避免缩写
缩写会降低可读性,增加理解成本。除非是公认的缩写(如 id、url、http),否则应使用完整单词。
java
// ❌ 滥用缩写
String custAddr; // customerAddress
String prodCat; // productCategory
void calTax(); // calculateTax
// ✅ 完整单词
String customerAddress;
String productCategory;
void calculateTax();1.3 命名风格速查
| 元素 | 风格 | 示例 |
|---|---|---|
| 类名 | 大驼峰 (UpperCamelCase) | OrderService, UserController |
| 方法/变量 | 小驼峰 (lowerCamelCase) | getOrderById, userName |
| 常量 | 大写下划线 (UPPER_SNAKE) | MAX_RETRY_COUNT, DEFAULT_TIMEOUT |
| 包名 | 全小写 | com.example.order.service |
| 布尔变量 | is/has/can 开头 | isActive, hasPermission, canExecute |
二、方法设计
2.1 单一职责原则
一个方法只做一件事,做好一件事。这有利于测试、维护和复用。
java
// ❌ 一个方法做了太多事
public void processOrder(Order order) {
// 校验订单
// 计算价格
// 扣减库存
// 发送通知
// 记录日志
}
// ✅ 拆分职责
public void processOrder(Order order) {
validateOrder(order);
calculatePrice(order);
deductInventory(order);
notifyUser(order);
logOrder(order);
}2.2 控制方法长度
理想情况下,一个方法不超过 50 行。如果超过,说明职责过多,需要拆分。
2.3 避免布尔参数
布尔参数使方法调用含义模糊,且通常意味着方法做了两件事。
java
// ❌ 布尔参数 -- 调用方看不懂
orderService.process(order, true, false);
// ✅ 拆分为独立方法
orderService.processWithNotification(order);
orderService.processWithoutNotification(order);
// ✅ 或使用枚举
orderService.process(order, ProcessingMode.ASYNC);2.4 参数数量控制
方法参数不宜超过 4 个。超过时考虑封装为参数对象。
java
// ❌ 参数过多
public User createUser(String name, String email, String phone,
String address, int age, String department) { }
// ✅ 封装为对象
public User createUser(CreateUserRequest request) { }
@Data
class CreateUserRequest {
private String name;
private String email;
private String phone;
private String address;
private int age;
private String department;
}三、异常处理最佳实践
3.1 永远不要吞掉异常
java
// ❌ 吞掉异常 -- 问题无法追踪
try {
doSomething();
} catch (Exception e) {
// 空 catch 块
}
// ❌ 只打印不处理
try {
doSomething();
} catch (Exception e) {
e.printStackTrace();
}
// ✅ 正确处理
try {
doSomething();
} catch (Exception e) {
log.error("Failed to do something, userId={}", userId, e);
throw new ServiceException("操作失败", e);
}3.2 使用具体的异常类型
java
// ❌ 捕获所有异常
try {
readFile();
} catch (Exception e) { }
// ✅ 捕获具体异常
try {
readFile();
} catch (FileNotFoundException e) {
log.warn("文件不存在: {}", filePath);
} catch (IOException e) {
log.error("读取文件异常", e);
throw new UncheckedIOException(e);
}3.3 try-with-resources 自动关闭资源
java
// ❌ 手动关闭 -- 繁琐且容易遗漏
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(path));
// 使用 reader
} catch (IOException e) {
log.error("读取失败", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("关闭失败", e);
}
}
}
// ✅ try-with-resources -- JDK 7+ 自动关闭
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
// 使用 reader
} catch (IOException e) {
log.error("读取失败", e);
}3.4 异常分层处理
java
// Controller 层:统一异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public Result<?> handleServiceException(ServiceException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后重试");
}
}3.5 异常处理核心原则
| 原则 | 说明 |
|---|---|
| 不吞异常 | 异常必须被记录或传播,不能静默忽略 |
| 具体异常 | 捕获特定异常类型,不要用 catch(Exception) |
| 早抛晚捕 | 尽早抛出异常,在合适层级统一捕获处理 |
| 转换异常 | 底层异常转换为业务异常,保护调用方 |
| 日志完整 | 记录异常时带上关键上下文信息 |
四、空值处理
4.1 Optional 的使用
java
// ❌ 防御式 null 检查
public String getCity(User user) {
if (user != null) {
Address address = user.getAddress();
if (address != null) {
return address.getCity();
}
}
return "未知";
}
// ✅ Optional 链式处理
public String getCity(User user) {
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
}4.2 Optional 使用原则
java
// ❌ 不要将 Optional 作为字段或参数
public class User {
private Optional<String> email; // 错误
}
public void process(Optional<String> param) { } // 错误
// ✅ Optional 仅用于返回值
public Optional<User> findById(Long id) {
return Optional.ofNullable(userMap.get(id));
}4.3 Objects.requireNonNull
java
// ✅ 在构造函数和方法中对参数进行非空校验
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = Objects.requireNonNull(userRepository,
"userRepository must not be null");
}
public User findById(Long id) {
Objects.requireNonNull(id, "id must not be null");
return userRepository.findById(id);
}
}4.4 @Nullable 和 @NonNull 注解
java
// ✅ 使用注解明确空值语义
public interface UserRepository {
@Nullable
User findByName(String name);
@NonNull
User findById(@NonNull Long id);
}五、集合最佳实践
5.1 初始化时指定容量
java
// ❌ 不指定容量 -- 可能多次扩容
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add("item" + i);
}
// ✅ 指定容量 -- 避免扩容开销
List<String> list = new ArrayList<>(10000);5.2 使用 isEmpty() 而非 size() == 0
java
// ❌ size() == 0
if (list.size() == 0) { }
// ✅ isEmpty() -- 更语义化,某些集合实现可能更高效
if (list.isEmpty()) { }5.3 返回空集合而非 null
java
// ❌ 返回 null -- 调用方必须做 null 检查
public List<Order> getOrders() {
if (noOrders) {
return null;
}
return orders;
}
// ✅ 返回空集合
public List<Order> getOrders() {
return orders != null ? orders : Collections.emptyList();
}5.4 选择合适的集合类型
| 场景 | 推荐集合 | 原因 |
|---|---|---|
| 频繁随机访问 | ArrayList | O(1) 索引访问 |
| 频繁增删 | LinkedList | O(1) 头尾操作 |
| 去重 | HashSet | O(1) 查找 |
| 排序去重 | TreeSet | 有序 |
| 键值对 | HashMap | O(1) 查找 |
| 排序键值对 | TreeMap | 有序 |
| 线程安全 | ConcurrentHashMap | 分段锁 |
5.5 使用增强 for 和 Stream API
java
// ❌ 传统 for 循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// ✅ 增强 for 循环
for (String item : list) {
System.out.println(item);
}
// ✅ Stream API 批量操作
List<String> upperNames = names.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.toList());六、并发编程最佳实践
6.1 使用并发集合
java
// ❌ 非线程安全的集合
Map<String, String> map = new HashMap<>(); // 并发问题
List<String> list = new ArrayList<>(); // 并发问题
// ✅ 并发集合
Map<String, String> map = new ConcurrentHashMap<>();
List<String> list = new CopyOnWriteArrayList<>();6.2 使用 ExecutorService 而非手动创建线程
java
// ❌ 手动创建线程 -- 不可控
new Thread(() -> {
doWork();
}).start();
// ✅ 使用线程池 -- 可管理、可复用
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("order-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
executor.submit(() -> doWork());6.3 始终设置线程名称
java
// ✅ 使用 ThreadFactory 统一命名
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("async-task-%d")
.setDaemon(true)
.build();
// ✅ 或使用线程池自定义工厂
new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime, unit,
workQueue,
r -> new Thread(r, "biz-thread-" + threadNumber.getAndIncrement())
);6.4 使用 volatile 保证可见性
java
public class TaskRunner {
// ✅ volatile 保证多线程可见性
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// 执行任务
}
}
}6.5 锁的粒度要细
java
// ❌ 方法级别的锁 -- 粒度太粗
public synchronized void processOrder(Order order) {
// 大量非临界区代码
validateOrder(order);
// 只有这里有竞态条件
updateInventory(order);
sendNotification(order);
}
// ✅ 细粒度锁
public void processOrder(Order order) {
validateOrder(order);
synchronized (inventoryLock) {
updateInventory(order);
}
sendNotification(order);
}七、日志记录最佳实践
7.1 使用参数化日志
java
// ❌ 字符串拼接 -- 即使日志级别不满足,也会执行拼接
log.debug("User " + user.getName() + " logged in at " + now);
// ✅ 参数化日志 -- 只有需要时才进行字符串拼接
log.debug("User {} logged in at {}", user.getName(), now);7.2 合适的日志级别
| 级别 | 使用场景 | 示例 |
|---|---|---|
| ERROR | 系统错误,需要人工介入 | 数据库连接失败、第三方接口不可用 |
| WARN | 潜在问题,可以继续运行 | 配置缺失使用默认值、重试成功 |
| INFO | 重要业务流程节点 | 订单创建、用户登录、定时任务开始/结束 |
| DEBUG | 调试信息,生产环境默认关闭 | 方法入参出参、中间变量值 |
| TRACE | 非常详细的追踪信息 | 逐行执行路径、SQL 参数绑定 |
7.3 不要记录敏感数据
java
// ❌ 记录敏感信息
log.info("用户登录成功,用户名={},密码={}", username, password);
// ✅ 脱敏处理
log.info("用户登录成功,用户名={},密码={}", username, maskPassword(password));
// ✅ 或直接不记录
log.info("用户登录成功,用户名={}", username);7.4 异常日志必须记录堆栈
java
// ❌ 只记录了异常信息
catch (Exception e) {
log.error("处理失败: {}", e.getMessage()); // 没有堆栈
}
// ✅ 记录完整堆栈
catch (Exception e) {
log.error("处理失败,orderId={}", orderId, e); // 最后一个参数是异常对象
}八、可读性最佳实践
8.1 使用枚举替代魔法数字
java
// ❌ 魔法数字
if (order.getStatus() == 1) { // 1 是什么?
cancelOrder(order);
}
// ✅ 枚举
public enum OrderStatus {
CREATED(1), PAID(2), SHIPPED(3), CANCELLED(4);
private final int code;
// ...
}
if (order.getStatus() == OrderStatus.PAID) {
cancelOrder(order);
}8.2 避免深层嵌套 -- 使用卫语句
java
// ❌ 深层嵌套
public void process(User user) {
if (user != null) {
if (user.isActive()) {
if (user.hasPermission("admin")) {
// 核心逻辑
}
}
}
}
// ✅ 卫语句 -- 提前返回
public void process(User user) {
if (user == null) {
return;
}
if (!user.isActive()) {
return;
}
if (!user.hasPermission("admin")) {
return;
}
// 核心逻辑
}8.3 使用 Lombok 减少样板代码
java
// ❌ 大量样板代码
public class User {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
// equals, hashCode, toString...
}
// ✅ Lombok 注解
@Data
@AllArgsConstructor
@Builder
public class User {
private String name;
private int age;
}面试常见问题
Q1: 为什么推荐使用 try-with-resources?
A: try-with-resources 是 Java 7 引入的语法糖,实现了 AutoCloseable 接口的资源会在 try 块结束后自动调用 close() 方法。它避免了手动在 finally 块中关闭资源可能导致的异常覆盖问题,代码更简洁且不易遗漏资源关闭。
Q2: Optional 可以替代 null 检查吗?使用时有什么注意事项?
A: Optional 可以优雅地处理可能为 null 的返回值,但不能完全替代所有 null 检查。注意事项:(1) 不要将 Optional 作为类的字段或方法参数;(2) 不要调用 Optional.get() 而不先检查 isPresent();(3) 优先使用 orElse()、orElseGet()、orElseThrow() 等安全方法;(4) 序列化场景(如 RPC、JSON)中 Optional 可能不兼容。
Q3: 为什么不推荐在循环中拼接字符串?
A: String 是不可变对象,每次拼接都会创建新的 String 对象,导致大量临时对象和 GC 压力。循环中应使用 StringBuilder(线程不安全,性能好)或 StringBuffer(线程安全)。
Q4: ExecutorService 相比直接 new Thread 有什么优势?
A: (1) 线程复用,减少创建/销毁开销;(2) 可控制最大并发数,防止资源耗尽;(3) 提供任务队列、拒绝策略等管理能力;(4) 支持定时任务和有返回值的任务(Future/Callable);(5) 便于统一监控和管理线程生命周期。
Q5: 日志输出为什么推荐使用参数化占位符?
A: 参数化占位符(如 {})可以实现延迟求值——只有当对应日志级别被启用时,才会执行字符串拼接。如果直接使用 + 拼接,无论日志是否输出,拼接操作都会执行,造成不必要的性能开销,尤其在循环或高频调用场景中。
