java-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJava Best Practices
Java最佳实践
Purpose
目的
This skill provides comprehensive best practices for Java development, serving as a reference guide during code reviews and architectural decisions. It covers SOLID principles, DRY, Clean Code, Java-specific patterns, testing strategies, and common anti-patterns.
When to use this skill:
- Conducting code reviews of Java projects
- Writing new Java code
- Refactoring existing Java code
- Evaluating architecture and design decisions
- Teaching Java best practices to team members
- Working with Spring Framework applications
本技能提供全面的Java开发最佳实践,作为代码审查和架构决策过程中的参考指南。内容涵盖SOLID原则、DRY、简洁代码、Java特定模式、测试策略以及常见反模式。
何时使用本技能:
- 进行Java项目的代码审查时
- 编写新的Java代码时
- 重构现有Java代码时
- 评估架构和设计决策时
- 向团队成员传授Java最佳实践时
- 处理Spring Framework应用时
Context
背景
High-quality Java code is essential for building maintainable, scalable, and robust applications. This skill documents industry-standard practices that emphasize:
- SOLID Principles: Foundation for well-designed object-oriented code
- Clean Code: Readable, maintainable, and self-documenting code
- Java-Specific Features: Proper use of modern Java features (8+)
- Testability: Code that's easy to test and verify
- Performance: Efficient use of Java language and JVM features
- Spring Framework: Best practices for Spring-based applications
This skill is designed to be referenced by the agent during code reviews and by developers when writing Java code.
uncle-duke-java高质量的Java代码对于构建可维护、可扩展且健壮的应用至关重要。本技能记录了行业标准实践,重点强调:
- SOLID原则:设计良好的面向对象代码的基础
- 简洁代码:可读、可维护且自文档化的代码
- Java特定特性:正确使用现代Java特性(8+版本)
- 可测试性:易于测试和验证的代码
- 性能:高效利用Java语言和JVM特性
- Spring Framework:基于Spring的应用最佳实践
本技能供代理在代码审查时参考,也可供开发人员编写Java代码时使用。
uncle-duke-javaPrerequisites
前置条件
Required Knowledge:
- Java fundamentals (Java 8+)
- Object-oriented programming concepts
- Basic understanding of design patterns
- Familiarity with Spring Framework (for Spring-specific sections)
Required Tools:
- JDK 8 or higher (11, 17, or 21 recommended)
- Maven or Gradle for build management
- JUnit 5 for testing
- Mockito for mocking
- IDE with Java support (IntelliJ IDEA, Eclipse, VS Code)
Expected Project Structure:
project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── model/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── controller/
│ │ │ └── util/
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── com/example/
├── pom.xml (or build.gradle)
└── README.md必备知识:
- Java基础知识(Java 8+)
- 面向对象编程概念
- 设计模式的基本理解
- 熟悉Spring Framework(针对Spring特定章节)
必备工具:
- JDK 8或更高版本(推荐11、17或21)
- Maven或Gradle用于构建管理
- JUnit 5用于测试
- Mockito用于模拟
- 支持Java的IDE(IntelliJ IDEA、Eclipse、VS Code)
预期项目结构:
project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── model/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── controller/
│ │ │ └── util/
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── com/example/
├── pom.xml (or build.gradle)
└── README.mdSOLID Principles in Java
Java中的SOLID原则
Single Responsibility Principle (SRP)
单一职责原则(SRP)
Rule: A class should have only one reason to change. Each class should have a single, well-defined responsibility.
Why it matters: Classes with multiple responsibilities are harder to understand, test, and maintain. Changes to one responsibility can affect the others.
规则: 一个类应该只有一个修改的理由。每个类应该有单一、明确的职责。
重要性: 具有多个职责的类更难理解、测试和维护。对一个职责的修改可能会影响其他职责。
SRP in Practice
SRP实践
❌ Bad - Multiple Responsibilities:
java
// This class violates SRP: it handles user data, validation, persistence, and email
public class User {
private String email;
private String password;
// Responsibility 1: Data validation
public boolean isValid() {
return email != null && email.contains("@")
&& password != null && password.length() >= 8;
}
// Responsibility 2: Database operations
public void save() {
Connection conn = DriverManager.getConnection("jdbc:...");
PreparedStatement ps = conn.prepareStatement("INSERT INTO users...");
ps.setString(1, email);
ps.setString(2, password);
ps.executeUpdate();
}
// Responsibility 3: Email operations
public void sendWelcomeEmail() {
EmailService.send(email, "Welcome!", "Welcome to our app");
}
// Responsibility 4: Password encryption
public void encryptPassword() {
this.password = BCrypt.hashpw(password, BCrypt.gensalt());
}
}Issues:
- User class has 4 responsibilities: data, validation, persistence, email
- Changes to validation logic affect the User class
- Changes to database schema affect the User class
- Changes to email templates affect the User class
- Difficult to test individual responsibilities
✅ Good - Single Responsibility:
java
// Responsibility: Hold user data
public class User {
private final String email;
private final String passwordHash;
public User(String email, String passwordHash) {
this.email = email;
this.passwordHash = passwordHash;
}
public String getEmail() { return email; }
public String getPasswordHash() { return passwordHash; }
}
// Responsibility: Validate user data
public class UserValidator {
public ValidationResult validate(String email, String password) {
List<String> errors = new ArrayList<>();
if (email == null || !email.contains("@")) {
errors.add("Invalid email format");
}
if (password == null || password.length() < 8) {
errors.add("Password must be at least 8 characters");
}
return new ValidationResult(errors.isEmpty(), errors);
}
}
// Responsibility: Persist user data
public class UserRepository {
private final DataSource dataSource;
public UserRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
public void save(User user) {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO users (email, password_hash) VALUES (?, ?)")) {
ps.setString(1, user.getEmail());
ps.setString(2, user.getPasswordHash());
ps.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException("Failed to save user", e);
}
}
}
// Responsibility: Send emails
public class EmailService {
public void sendWelcomeEmail(User user) {
send(user.getEmail(), "Welcome!", "Welcome to our app");
}
private void send(String to, String subject, String body) {
// Email sending logic
}
}
// Responsibility: Hash passwords
public class PasswordEncoder {
public String encode(String rawPassword) {
return BCrypt.hashpw(rawPassword, BCrypt.gensalt());
}
public boolean matches(String rawPassword, String encodedPassword) {
return BCrypt.checkpw(rawPassword, encodedPassword);
}
}Benefits:
- Each class has one clear responsibility
- Easy to test each responsibility in isolation
- Changes to one concern don't affect others
- Classes are small and focused
❌ 不良示例 - 多职责:
java
// 此类违反SRP:它处理用户数据、验证、持久化和邮件
public class User {
private String email;
private String password;
// 职责1:数据验证
public boolean isValid() {
return email != null && email.contains("@")
&& password != null && password.length() >= 8;
}
// 职责2:数据库操作
public void save() {
Connection conn = DriverManager.getConnection("jdbc:...");
PreparedStatement ps = conn.prepareStatement("INSERT INTO users...");
ps.setString(1, email);
ps.setString(2, password);
ps.executeUpdate();
}
// 职责3:邮件操作
public void sendWelcomeEmail() {
EmailService.send(email, "Welcome!", "Welcome to our app");
}
// 职责4:密码加密
public void encryptPassword() {
this.password = BCrypt.hashpw(password, BCrypt.gensalt());
}
}问题:
- User类有4个职责:数据、验证、持久化、邮件
- 验证逻辑的修改会影响User类
- 数据库 schema 的修改会影响User类
- 邮件模板的修改会影响User类
- 难以单独测试各个职责
✅ 良好示例 - 单一职责:
java
// 职责:保存用户数据
public class User {
private final String email;
private final String passwordHash;
public User(String email, String passwordHash) {
this.email = email;
this.passwordHash = passwordHash;
}
public String getEmail() { return email; }
public String getPasswordHash() { return passwordHash; }
}
// 职责:验证用户数据
public class UserValidator {
public ValidationResult validate(String email, String password) {
List<String> errors = new ArrayList<>();
if (email == null || !email.contains("@")) {
errors.add("Invalid email format");
}
if (password == null || password.length() < 8) {
errors.add("Password must be at least 8 characters");
}
return new ValidationResult(errors.isEmpty(), errors);
}
}
// 职责:持久化用户数据
public class UserRepository {
private final DataSource dataSource;
public UserRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
public void save(User user) {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO users (email, password_hash) VALUES (?, ?)")) {
ps.setString(1, user.getEmail());
ps.setString(2, user.getPasswordHash());
ps.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException("Failed to save user", e);
}
}
}
// 职责:发送邮件
public class EmailService {
public void sendWelcomeEmail(User user) {
send(user.getEmail(), "Welcome!", "Welcome to our app");
}
private void send(String to, String subject, String body) {
// 邮件发送逻辑
}
}
// 职责:哈希密码
public class PasswordEncoder {
public String encode(String rawPassword) {
return BCrypt.hashpw(rawPassword, BCrypt.gensalt());
}
public boolean matches(String rawPassword, String encodedPassword) {
return BCrypt.checkpw(rawPassword, encodedPassword);
}
}优势:
- 每个类有一个明确的职责
- 易于单独测试各个职责
- 对一个关注点的修改不会影响其他部分
- 类小巧且聚焦
Open/Closed Principle (OCP)
开闭原则(OCP)
Rule: Software entities (classes, modules, functions) should be open for extension but closed for modification.
Why it matters: You should be able to add new functionality without changing existing code, reducing the risk of breaking existing features.
规则: 软件实体(类、模块、函数)应该对扩展开放,对修改关闭。
重要性: 你应该能够在不修改现有代码的情况下添加新功能,从而降低破坏现有功能的风险。
OCP in Practice
OCP实践
❌ Bad - Violates OCP:
java
public class PaymentProcessor {
public void processPayment(String paymentType, double amount) {
if (paymentType.equals("CREDIT_CARD")) {
// Process credit card payment
System.out.println("Processing credit card payment: $" + amount);
} else if (paymentType.equals("PAYPAL")) {
// Process PayPal payment
System.out.println("Processing PayPal payment: $" + amount);
} else if (paymentType.equals("BITCOIN")) {
// Process Bitcoin payment
System.out.println("Processing Bitcoin payment: $" + amount);
}
// Adding new payment method requires modifying this class!
}
}Issues:
- Must modify PaymentProcessor to add new payment types
- Violates OCP (not closed for modification)
- Growing if-else chain
- Hard to test individual payment types
✅ Good - Follows OCP:
java
// Abstract payment interface
public interface PaymentMethod {
void process(double amount);
}
// Concrete implementations
public class CreditCardPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing credit card payment: $" + amount);
// Credit card specific logic
}
}
public class PayPalPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
// PayPal specific logic
}
}
public class BitcoinPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing Bitcoin payment: $" + amount);
// Bitcoin specific logic
}
}
// Processor delegates to payment method
public class PaymentProcessor {
public void processPayment(PaymentMethod paymentMethod, double amount) {
paymentMethod.process(amount);
}
}
// Usage
PaymentProcessor processor = new PaymentProcessor();
processor.processPayment(new CreditCardPayment(), 100.0);
processor.processPayment(new PayPalPayment(), 50.0);
// Adding new payment method: just create new class, no modification needed!
public class ApplePayPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing Apple Pay payment: $" + amount);
}
}Benefits:
- New payment methods added without modifying existing code
- Each payment type is independently testable
- Follows OCP: open for extension, closed for modification
- Clear separation of concerns
❌ 不良示例 - 违反OCP:
java
public class PaymentProcessor {
public void processPayment(String paymentType, double amount) {
if (paymentType.equals("CREDIT_CARD")) {
// 处理信用卡支付
System.out.println("Processing credit card payment: $" + amount);
} else if (paymentType.equals("PAYPAL")) {
// 处理PayPal支付
System.out.println("Processing PayPal payment: $" + amount);
} else if (paymentType.equals("BITCOIN")) {
// 处理比特币支付
System.out.println("Processing Bitcoin payment: $" + amount);
}
// 添加新支付方式需要修改此类!
}
}问题:
- 必须修改PaymentProcessor才能添加新支付类型
- 违反OCP(对修改不关闭)
- if-else链不断增长
- 难以单独测试各个支付类型
✅ 良好示例 - 遵循OCP:
java
// 抽象支付接口
public interface PaymentMethod {
void process(double amount);
}
// 具体实现
public class CreditCardPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing credit card payment: $" + amount);
// 信用卡特定逻辑
}
}
public class PayPalPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
// PayPal特定逻辑
}
}
public class BitcoinPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing Bitcoin payment: $" + amount);
// 比特币特定逻辑
}
}
// 处理器委托给支付方式
public class PaymentProcessor {
public void processPayment(PaymentMethod paymentMethod, double amount) {
paymentMethod.process(amount);
}
}
// 使用示例
PaymentProcessor processor = new PaymentProcessor();
processor.processPayment(new CreditCardPayment(), 100.0);
processor.processPayment(new PayPalPayment(), 50.0);
// 添加新支付方式:只需创建新类,无需修改现有代码!
public class ApplePayPayment implements PaymentMethod {
@Override
public void process(double amount) {
System.out.println("Processing Apple Pay payment: $" + amount);
}
}优势:
- 无需修改现有代码即可添加新支付方式
- 每个支付类型可独立测试
- 遵循OCP:对扩展开放,对修改关闭
- 关注点分离清晰
OCP with Strategy Pattern
结合策略模式的OCP
✅ Advanced Example - Discount Strategies:
java
// Strategy interface
public interface DiscountStrategy {
double applyDiscount(double price);
}
// Concrete strategies
public class NoDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price;
}
}
public class PercentageDiscount implements DiscountStrategy {
private final double percentage;
public PercentageDiscount(double percentage) {
this.percentage = percentage;
}
@Override
public double applyDiscount(double price) {
return price * (1 - percentage / 100);
}
}
public class FixedAmountDiscount implements DiscountStrategy {
private final double amount;
public FixedAmountDiscount(double amount) {
this.amount = amount;
}
@Override
public double applyDiscount(double price) {
return Math.max(0, price - amount);
}
}
// Context uses strategy
public class PriceCalculator {
private final DiscountStrategy discountStrategy;
public PriceCalculator(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateFinalPrice(double originalPrice) {
return discountStrategy.applyDiscount(originalPrice);
}
}
// Usage
PriceCalculator calc1 = new PriceCalculator(new PercentageDiscount(10));
double price1 = calc1.calculateFinalPrice(100); // 90.0
PriceCalculator calc2 = new PriceCalculator(new FixedAmountDiscount(15));
double price2 = calc2.calculateFinalPrice(100); // 85.0✅ 高级示例 - 折扣策略:
java
// 策略接口
public interface DiscountStrategy {
double applyDiscount(double price);
}
// 具体策略
public class NoDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price;
}
}
public class PercentageDiscount implements DiscountStrategy {
private final double percentage;
public PercentageDiscount(double percentage) {
this.percentage = percentage;
}
@Override
public double applyDiscount(double price) {
return price * (1 - percentage / 100);
}
}
public class FixedAmountDiscount implements DiscountStrategy {
private final double amount;
public FixedAmountDiscount(double amount) {
this.amount = amount;
}
@Override
public double applyDiscount(double price) {
return Math.max(0, price - amount);
}
}
// 上下文使用策略
public class PriceCalculator {
private final DiscountStrategy discountStrategy;
public PriceCalculator(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateFinalPrice(double originalPrice) {
return discountStrategy.applyDiscount(originalPrice);
}
}
// 使用示例
PriceCalculator calc1 = new PriceCalculator(new PercentageDiscount(10));
double price1 = calc1.calculateFinalPrice(100); // 90.0
PriceCalculator calc2 = new PriceCalculator(new FixedAmountDiscount(15));
double price2 = calc2.calculateFinalPrice(100); // 85.0Liskov Substitution Principle (LSP)
里氏替换原则(LSP)
Rule: Objects of a superclass should be replaceable with objects of a subclass without breaking the application. Subtypes must be substitutable for their base types.
Why it matters: Violating LSP leads to unexpected behavior and breaks polymorphism.
规则: 父类的对象应该可以被子类的对象替换,而不会破坏应用程序。子类必须能够替代其基类。
重要性: 违反LSP会导致意外行为并破坏多态性。
LSP in Practice
LSP实践
❌ Bad - Violates LSP:
java
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// Square violates LSP because it changes behavior of setters
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Side effect!
}
@Override
public void setHeight(int height) {
this.width = height; // Side effect!
this.height = height;
}
}
// This test works for Rectangle but fails for Square
public void testRectangle(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(4);
assertEquals(20, rect.getArea()); // Fails for Square! (25 instead of 20)
}Issues:
- Square changes the behavior of Rectangle methods
- Cannot substitute Square for Rectangle
- Violates LSP and breaks polymorphism
✅ Good - Follows LSP:
java
// Common interface for shapes
public interface Shape {
int getArea();
}
// Rectangle implementation
public class Rectangle implements Shape {
private final int width;
private final int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
public int getWidth() { return width; }
public int getHeight() { return height; }
}
// Square implementation (no inheritance from Rectangle)
public class Square implements Shape {
private final int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
public int getSide() { return side; }
}
// Works for any Shape
public int calculateTotalArea(List<Shape> shapes) {
return shapes.stream()
.mapToInt(Shape::getArea)
.sum();
}Benefits:
- Square and Rectangle are independent
- Both implement Shape contract correctly
- Can substitute any Shape implementation
- No unexpected behavior
❌ 不良示例 - 违反LSP:
java
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// Square违反LSP,因为它修改了setter的行为
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 副作用!
}
@Override
public void setHeight(int height) {
this.width = height; // 副作用!
this.height = height;
}
}
// 此测试对Rectangle有效,但对Square失败
public void testRectangle(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(4);
assertEquals(20, rect.getArea()); // 对Square失败!(得到25而非20)
}问题:
- Square修改了Rectangle方法的行为
- 无法用Square替换Rectangle
- 违反LSP并破坏多态性
✅ 良好示例 - 遵循LSP:
java
// 形状通用接口
public interface Shape {
int getArea();
}
// Rectangle实现
public class Rectangle implements Shape {
private final int width;
private final int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
public int getWidth() { return width; }
public int getHeight() { return height; }
}
// Square实现(不继承自Rectangle)
public class Square implements Shape {
private final int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
public int getSide() { return side; }
}
// 适用于任何Shape
public int calculateTotalArea(List<Shape> shapes) {
return shapes.stream()
.mapToInt(Shape::getArea)
.sum();
}优势:
- Square和Rectangle相互独立
- 两者都正确实现了Shape契约
- 可以替换任何Shape实现
- 无意外行为
LSP - Pre and Post Conditions
LSP - 前置和后置条件
✅ Good - Maintains Contracts:
java
public interface BankAccount {
// Precondition: amount > 0
// Postcondition: balance increased by amount
void deposit(double amount);
// Precondition: amount > 0 and amount <= balance
// Postcondition: balance decreased by amount
void withdraw(double amount) throws InsufficientFundsException;
double getBalance();
}
public class SavingsAccount implements BankAccount {
private double balance;
@Override
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
balance += amount;
}
@Override
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (amount > balance) {
throw new InsufficientFundsException();
}
balance -= amount;
}
@Override
public double getBalance() {
return balance;
}
}
// Subclass maintains contracts (LSP)
public class CheckingAccount implements BankAccount {
private double balance;
private final double overdraftLimit;
public CheckingAccount(double overdraftLimit) {
this.overdraftLimit = overdraftLimit;
}
@Override
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
balance += amount; // Same postcondition
}
@Override
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
// Can weaken precondition (allow overdraft) but not strengthen
if (amount > balance + overdraftLimit) {
throw new InsufficientFundsException();
}
balance -= amount; // Same postcondition
}
@Override
public double getBalance() {
return balance;
}
}✅ 良好示例 - 维护契约:
java
public interface BankAccount {
// 前置条件:amount > 0
// 后置条件:余额增加amount
void deposit(double amount);
// 前置条件:amount > 0且amount <= balance
// 后置条件:余额减少amount
void withdraw(double amount) throws InsufficientFundsException;
double getBalance();
}
public class SavingsAccount implements BankAccount {
private double balance;
@Override
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
balance += amount;
}
@Override
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (amount > balance) {
throw new InsufficientFundsException();
}
balance -= amount;
}
@Override
public double getBalance() {
return balance;
}
}
// 子类维护契约(LSP)
public class CheckingAccount implements BankAccount {
private double balance;
private final double overdraftLimit;
public CheckingAccount(double overdraftLimit) {
this.overdraftLimit = overdraftLimit;
}
@Override
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
balance += amount; // 相同的后置条件
}
@Override
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
// 可以弱化前置条件(允许透支),但不能强化
if (amount > balance + overdraftLimit) {
throw new InsufficientFundsException();
}
balance -= amount; // 相同的后置条件
}
@Override
public double getBalance() {
return balance;
}
}Interface Segregation Principle (ISP)
接口隔离原则(ISP)
Rule: Clients should not be forced to depend on interfaces they don't use. Many specific interfaces are better than one general-purpose interface.
Why it matters: Large interfaces force implementations to provide methods they don't need, leading to empty implementations and tight coupling.
规则: 客户端不应该被迫依赖它们不需要的接口。多个特定接口优于一个通用接口。
重要性: 大接口会迫使实现提供它们不需要的方法,导致空实现和紧耦合。
ISP in Practice
ISP实践
❌ Bad - Fat Interface:
java
// Fat interface forces all implementations to provide all methods
public interface Worker {
void work();
void eat();
void sleep();
void getSalary();
void attendMeeting();
}
// Robot doesn't eat or sleep but is forced to implement these methods
public class RobotWorker implements Worker {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void eat() {
// Doesn't make sense for robots!
throw new UnsupportedOperationException("Robots don't eat");
}
@Override
public void sleep() {
// Doesn't make sense for robots!
throw new UnsupportedOperationException("Robots don't sleep");
}
@Override
public void getSalary() {
throw new UnsupportedOperationException("Robots don't get paid");
}
@Override
public void attendMeeting() {
System.out.println("Robot attending meeting");
}
}Issues:
- Robot forced to implement biological methods
- Throwing UnsupportedOperationException is a code smell
- Violates ISP
- Tight coupling to irrelevant methods
✅ Good - Segregated Interfaces:
java
// Segregated interfaces - clients depend only on what they need
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public interface Payable {
void getSalary();
}
public interface MeetingAttendee {
void attendMeeting();
}
// Human implements relevant interfaces
public class HumanWorker implements Workable, Eatable, Sleepable, Payable, MeetingAttendee {
@Override
public void work() {
System.out.println("Human working");
}
@Override
public void eat() {
System.out.println("Human eating");
}
@Override
public void sleep() {
System.out.println("Human sleeping");
}
@Override
public void getSalary() {
System.out.println("Human receiving salary");
}
@Override
public void attendMeeting() {
System.out.println("Human attending meeting");
}
}
// Robot only implements relevant interfaces
public class RobotWorker implements Workable, MeetingAttendee {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void attendMeeting() {
System.out.println("Robot attending meeting");
}
}Benefits:
- Implementations only provide methods that make sense
- No UnsupportedOperationException needed
- Clear separation of concerns
- Flexible composition
❌ 不良示例 - 臃肿接口:
java
// 臃肿接口迫使所有实现提供所有方法
public interface Worker {
void work();
void eat();
void sleep();
void getSalary();
void attendMeeting();
}
// Robot不需要eat或sleep,但被迫实现这些方法
public class RobotWorker implements Worker {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void eat() {
// 对机器人毫无意义!
throw new UnsupportedOperationException("Robots don't eat");
}
@Override
public void sleep() {
// 对机器人毫无意义!
throw new UnsupportedOperationException("Robots don't sleep");
}
@Override
public void getSalary() {
throw new UnsupportedOperationException("Robots don't get paid");
}
@Override
public void attendMeeting() {
System.out.println("Robot attending meeting");
}
}问题:
- Robot被迫实现生物相关方法
- 抛出UnsupportedOperationException是代码异味
- 违反ISP
- 与无关方法紧耦合
✅ 良好示例 - 隔离接口:
java
// 隔离接口 - 客户端仅依赖所需的接口
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public interface Payable {
void getSalary();
}
public interface MeetingAttendee {
void attendMeeting();
}
// Human实现相关接口
public class HumanWorker implements Workable, Eatable, Sleepable, Payable, MeetingAttendee {
@Override
public void work() {
System.out.println("Human working");
}
@Override
public void eat() {
System.out.println("Human eating");
}
@Override
public void sleep() {
System.out.println("Human sleeping");
}
@Override
public void getSalary() {
System.out.println("Human receiving salary");
}
@Override
public void attendMeeting() {
System.out.println("Human attending meeting");
}
}
// Robot仅实现相关接口
public class RobotWorker implements Workable, MeetingAttendee {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void attendMeeting() {
System.out.println("Robot attending meeting");
}
}优势:
- 实现仅提供有意义的方法
- 无需UnsupportedOperationException
- 关注点分离清晰
- 组合灵活
Dependency Inversion Principle (DIP)
依赖倒置原则(DIP)
Rule: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Why it matters: DIP decouples code, making it more flexible, testable, and maintainable.
规则: 高层模块不应该依赖低层模块。两者都应该依赖抽象。抽象不应该依赖细节。细节应该依赖抽象。
重要性: DIP解耦代码,使其更灵活、可测试且可维护。
DIP in Practice
DIP实践
❌ Bad - High-level depends on low-level:
java
// Low-level module
public class MySQLDatabase {
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
// High-level module depends on concrete low-level module
public class UserService {
private MySQLDatabase database; // Concrete dependency!
public UserService() {
this.database = new MySQLDatabase(); // Tight coupling!
}
public void createUser(String userData) {
// Business logic
database.save(userData);
}
}Issues:
- UserService tightly coupled to MySQLDatabase
- Cannot switch to PostgreSQL without modifying UserService
- Hard to test (can't mock database)
- Violates DIP
✅ Good - Both depend on abstraction:
java
// Abstraction
public interface Database {
void save(String data);
}
// Low-level modules depend on abstraction
public class MySQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
public class PostgreSQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to PostgreSQL: " + data);
}
}
public class MongoDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to MongoDB: " + data);
}
}
// High-level module depends on abstraction
public class UserService {
private final Database database; // Abstraction!
// Dependency injected through constructor
public UserService(Database database) {
this.database = database;
}
public void createUser(String userData) {
// Business logic
database.save(userData);
}
}
// Usage - client chooses implementation
Database db = new MySQLDatabase();
UserService service = new UserService(db);
service.createUser("John Doe");
// Easy to switch implementations
Database postgresDb = new PostgreSQLDatabase();
UserService postgresService = new UserService(postgresDb);
// Easy to test with mock
Database mockDb = mock(Database.class);
UserService testService = new UserService(mockDb);Benefits:
- UserService decoupled from database implementation
- Easy to switch database implementations
- Easy to test with mocks
- Follows DIP
❌ 不良示例 - 高层依赖低层:
java
// 低层模块
public class MySQLDatabase {
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
// 高层模块依赖具体低层模块
public class UserService {
private MySQLDatabase database; // 具体依赖!
public UserService() {
this.database = new MySQLDatabase(); // 紧耦合!
}
public void createUser(String userData) {
// 业务逻辑
database.save(userData);
}
}问题:
- UserService与MySQLDatabase紧耦合
- 不修改UserService就无法切换到PostgreSQL
- 难以测试(无法模拟数据库)
- 违反DIP
✅ 良好示例 - 两者都依赖抽象:
java
// 抽象
public interface Database {
void save(String data);
}
// 低层模块依赖抽象
public class MySQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
public class PostgreSQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to PostgreSQL: " + data);
}
}
public class MongoDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to MongoDB: " + data);
}
}
// 高层模块依赖抽象
public class UserService {
private final Database database; // 抽象!
// 通过构造函数注入依赖
public UserService(Database database) {
this.database = database;
}
public void createUser(String userData) {
// 业务逻辑
database.save(userData);
}
}
// 使用示例 - 客户端选择实现
Database db = new MySQLDatabase();
UserService service = new UserService(db);
service.createUser("John Doe");
// 轻松切换实现
Database postgresDb = new PostgreSQLDatabase();
UserService postgresService = new UserService(postgresDb);
// 轻松使用模拟进行测试
Database mockDb = mock(Database.class);
UserService testService = new UserService(mockDb);优势:
- UserService与数据库实现解耦
- 轻松切换数据库实现
- 轻松使用模拟进行测试
- 遵循DIP
SOLID Principles in Spring Framework
Spring Framework中的SOLID原则
Spring Framework is built on SOLID principles, particularly Dependency Inversion.
Spring Framework基于SOLID原则构建,尤其是依赖倒置。
Dependency Injection in Spring
Spring中的依赖注入
✅ Spring DI Example:
java
// Abstraction
public interface UserRepository {
User findById(Long id);
void save(User user);
}
// Implementation
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
return entityManager.find(User.class, id);
}
@Override
public void save(User user) {
entityManager.persist(user);
}
}
// Service depends on abstraction
@Service
public class UserService {
private final UserRepository userRepository;
// Constructor injection (recommended)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
// Controller depends on service abstraction
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUser(id);
return ResponseEntity.ok(user);
}
}Spring DI Best Practices:
- Use constructor injection (required dependencies, immutability)
- Prefer field injection only for optional dependencies
- Depend on interfaces, not concrete classes
- Use when multiple implementations exist
@Qualifier
✅ Spring DI示例:
java
// 抽象
public interface UserRepository {
User findById(Long id);
void save(User user);
}
// 实现
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
return entityManager.find(User.class, id);
}
@Override
public void save(User user) {
entityManager.persist(user);
}
}
// 服务依赖抽象
@Service
public class UserService {
private final UserRepository userRepository;
// 构造函数注入(推荐)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
// 控制器依赖服务抽象
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUser(id);
return ResponseEntity.ok(user);
}
}Spring DI最佳实践:
- 使用构造函数注入(必填依赖,不可变性)
- 仅对可选依赖使用字段注入
- 依赖接口而非具体类
- 当存在多个实现时使用
@Qualifier
DRY (Don't Repeat Yourself)
DRY(不要重复自己)
Rule: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Why it matters: Duplication leads to inconsistencies, harder maintenance, and more bugs.
规则: 系统中的每一份知识都必须有单一、明确、权威的表示。
重要性: 重复会导致不一致、维护难度增加和更多bug。
Identifying Code Duplication
识别代码重复
❌ Bad - Obvious Duplication:
java
public class OrderService {
public void processOnlineOrder(Order order) {
// Validate
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
// Process
System.out.println("Processing online order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
public void processPhoneOrder(Order order) {
// Same validation - DUPLICATION!
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
// Process
System.out.println("Processing phone order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
}✅ Good - Extract Common Logic:
java
public class OrderService {
public void processOnlineOrder(Order order) {
validateOrder(order);
processOrder(order, "online");
}
public void processPhoneOrder(Order order) {
validateOrder(order);
processOrder(order, "phone");
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
}
private void processOrder(Order order, String type) {
System.out.println("Processing " + type + " order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
}❌ 不良示例 - 明显重复:
java
public class OrderService {
public void processOnlineOrder(Order order) {
// 验证
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
// 处理
System.out.println("Processing online order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
public void processPhoneOrder(Order order) {
// 相同的验证 - 重复!
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
// 处理
System.out.println("Processing phone order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
}✅ 良好示例 - 提取公共逻辑:
java
public class OrderService {
public void processOnlineOrder(Order order) {
validateOrder(order);
processOrder(order, "online");
}
public void processPhoneOrder(Order order) {
validateOrder(order);
processOrder(order, "phone");
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be positive");
}
}
private void processOrder(Order order, String type) {
System.out.println("Processing " + type + " order: " + order.getId());
order.setStatus(OrderStatus.PROCESSING);
saveOrder(order);
}
}Utility Classes and Helper Methods
工具类和辅助方法
✅ Create Utility Classes for Reusable Logic:
java
public final class StringUtils {
private StringUtils() {
// Prevent instantiation
}
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
public static String capitalize(String str) {
if (isBlank(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
public static String truncate(String str, int maxLength) {
if (str == null || str.length() <= maxLength) {
return str;
}
return str.substring(0, maxLength) + "...";
}
}
// Usage
if (StringUtils.isBlank(username)) {
throw new ValidationException("Username is required");
}
String displayName = StringUtils.capitalize(name);✅ 为可重用逻辑创建工具类:
java
public final class StringUtils {
private StringUtils() {
// 防止实例化
}
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
public static String capitalize(String str) {
if (isBlank(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
public static String truncate(String str, int maxLength) {
if (str == null || str.length() <= maxLength) {
return str;
}
return str.substring(0, maxLength) + "...";
}
}
// 使用示例
if (StringUtils.isBlank(username)) {
throw new ValidationException("Username is required");
}
String displayName = StringUtils.capitalize(name);Generics for Reusability
使用泛型实现重用
✅ Use Generics to Avoid Duplication:
java
// Instead of creating separate classes for different types
public class GenericRepository<T, ID> {
private final Class<T> entityClass;
@PersistenceContext
private EntityManager entityManager;
public GenericRepository(Class<T> entityClass) {
this.entityClass = entityClass;
}
public Optional<T> findById(ID id) {
T entity = entityManager.find(entityClass, id);
return Optional.ofNullable(entity);
}
public List<T> findAll() {
CriteriaQuery<T> query = entityManager.getCriteriaBuilder()
.createQuery(entityClass);
query.select(query.from(entityClass));
return entityManager.createQuery(query).getResultList();
}
public void save(T entity) {
entityManager.persist(entity);
}
public void delete(T entity) {
entityManager.remove(entity);
}
}
// Concrete repositories extend generic repository
@Repository
public class UserRepository extends GenericRepository<User, Long> {
public UserRepository() {
super(User.class);
}
// Add User-specific queries
public Optional<User> findByEmail(String email) {
// Custom query
}
}✅ 使用泛型避免重复:
java
// 不为不同类型创建单独的类
public class GenericRepository<T, ID> {
private final Class<T> entityClass;
@PersistenceContext
private EntityManager entityManager;
public GenericRepository(Class<T> entityClass) {
this.entityClass = entityClass;
}
public Optional<T> findById(ID id) {
T entity = entityManager.find(entityClass, id);
return Optional.ofNullable(entity);
}
public List<T> findAll() {
CriteriaQuery<T> query = entityManager.getCriteriaBuilder()
.createQuery(entityClass);
query.select(query.from(entityClass));
return entityManager.createQuery(query).getResultList();
}
public void save(T entity) {
entityManager.persist(entity);
}
public void delete(T entity) {
entityManager.remove(entity);
}
}
// 具体仓库扩展泛型仓库
@Repository
public class UserRepository extends GenericRepository<User, Long> {
public UserRepository() {
super(User.class);
}
// 添加User特定查询
public Optional<User> findByEmail(String email) {
// 自定义查询
}
}Clean Code Principles
简洁代码原则
Meaningful Names
有意义的命名
Rule: Names should reveal intent, be pronounceable, and be searchable.
❌ Bad Names:
java
int d; // elapsed time in days
String yyyymmdd;
List<int[]> list1;
public void getData() {
// What data?
}✅ Good Names:
java
int elapsedTimeInDays;
String formattedDate;
List<Customer> activeCustomers;
public Customer getCustomerById(Long customerId) {
// Clear what this method does
}规则: 名称应揭示意图,易于发音且便于搜索。
❌ 不良命名:
java
int d; // 经过的天数
String yyyymmdd;
List<int[]> list1;
public void getData() {
// 获取什么数据?
}✅ 良好命名:
java
int elapsedTimeInDays;
String formattedDate;
List<Customer> activeCustomers;
public Customer getCustomerById(Long customerId) {
// 清晰表明方法用途
}Naming Conventions
命名约定
java
// Classes: PascalCase, nouns
public class CustomerService { }
public class OrderRepository { }
// Interfaces: PascalCase, often adjectives or nouns
public interface Serializable { }
public interface UserRepository { }
// Methods: camelCase, verbs
public void calculateTotal() { }
public Customer findCustomerById(Long id) { }
// Variables: camelCase, nouns
String customerName;
int orderCount;
boolean isActive;
// Constants: UPPER_SNAKE_CASE
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
// Packages: lowercase, periods
package com.example.service;
package com.example.repository;
// Boolean methods/variables: is, has, can
boolean isValid();
boolean hasPermission();
boolean canExecute();java
// 类名:大驼峰,名词
public class CustomerService { }
public class OrderRepository { }
// 接口名:大驼峰,通常是形容词或名词
public interface Serializable { }
public interface UserRepository { }
// 方法名:小驼峰,动词
public void calculateTotal() { }
public Customer findCustomerById(Long id) { }
// 变量名:小驼峰,名词
String customerName;
int orderCount;
boolean isActive;
// 常量:大写蛇形
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
// 包名:小写,点分隔
package com.example.service;
package com.example.repository;
// 布尔方法/变量:is/has/can
boolean isValid();
boolean hasPermission();
boolean canExecute();Function Size and Complexity
函数大小和复杂度
Rule: Functions should be small and do one thing. Aim for 5-20 lines per method.
❌ Bad - Large, Complex Method:
java
public void processOrder(Order order) {
// Validation
if (order == null) throw new IllegalArgumentException();
if (order.getItems().isEmpty()) throw new IllegalArgumentException();
// Calculate total
double total = 0;
for (OrderItem item : order.getItems()) {
double itemPrice = item.getPrice();
int quantity = item.getQuantity();
double discount = item.getDiscount();
total += (itemPrice * quantity) * (1 - discount);
}
order.setTotal(total);
// Apply coupon
if (order.getCoupon() != null) {
String couponCode = order.getCoupon().getCode();
if (couponCode.startsWith("SAVE")) {
total *= 0.9;
} else if (couponCode.startsWith("BIG")) {
total *= 0.8;
}
order.setTotal(total);
}
// Check inventory
for (OrderItem item : order.getItems()) {
int available = inventoryService.getAvailableQuantity(item.getProductId());
if (available < item.getQuantity()) {
throw new InsufficientInventoryException();
}
}
// Save order
orderRepository.save(order);
// Send email
emailService.send(order.getCustomer().getEmail(), "Order Confirmation",
"Your order " + order.getId() + " has been confirmed");
// Update inventory
for (OrderItem item : order.getItems()) {
inventoryService.decrementQuantity(item.getProductId(), item.getQuantity());
}
}✅ Good - Small, Focused Methods:
java
public void processOrder(Order order) {
validateOrder(order);
calculateOrderTotal(order);
applyCouponDiscount(order);
checkInventoryAvailability(order);
saveOrder(order);
sendConfirmationEmail(order);
updateInventory(order);
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain items");
}
}
private void calculateOrderTotal(Order order) {
double total = order.getItems().stream()
.mapToDouble(this::calculateItemTotal)
.sum();
order.setTotal(total);
}
private double calculateItemTotal(OrderItem item) {
return item.getPrice() * item.getQuantity() * (1 - item.getDiscount());
}
private void applyCouponDiscount(Order order) {
if (order.getCoupon() == null) {
return;
}
double discountMultiplier = getDiscountMultiplier(order.getCoupon());
order.setTotal(order.getTotal() * discountMultiplier);
}
private double getDiscountMultiplier(Coupon coupon) {
String code = coupon.getCode();
if (code.startsWith("SAVE")) return 0.9;
if (code.startsWith("BIG")) return 0.8;
return 1.0;
}
private void checkInventoryAvailability(Order order) {
for (OrderItem item : order.getItems()) {
int available = inventoryService.getAvailableQuantity(item.getProductId());
if (available < item.getQuantity()) {
throw new InsufficientInventoryException(
"Product " + item.getProductId() + " has insufficient inventory");
}
}
}
private void saveOrder(Order order) {
orderRepository.save(order);
}
private void sendConfirmationEmail(Order order) {
String email = order.getCustomer().getEmail();
String subject = "Order Confirmation";
String body = String.format("Your order %s has been confirmed", order.getId());
emailService.send(email, subject, body);
}
private void updateInventory(Order order) {
order.getItems().forEach(item ->
inventoryService.decrementQuantity(item.getProductId(), item.getQuantity())
);
}Benefits:
- Each method has a clear, single purpose
- Easy to understand and test
- Main method reads like a table of contents
- Reusable helper methods
规则: 函数应小巧且只做一件事。目标是每个方法5-20行。
❌ 不良示例 - 大而复杂的方法:
java
public void processOrder(Order order) {
// 验证
if (order == null) throw new IllegalArgumentException();
if (order.getItems().isEmpty()) throw new IllegalArgumentException();
// 计算总价
double total = 0;
for (OrderItem item : order.getItems()) {
double itemPrice = item.getPrice();
int quantity = item.getQuantity();
double discount = item.getDiscount();
total += (itemPrice * quantity) * (1 - discount);
}
order.setTotal(total);
// 应用优惠券
if (order.getCoupon() != null) {
String couponCode = order.getCoupon().getCode();
if (couponCode.startsWith("SAVE")) {
total *= 0.9;
} else if (couponCode.startsWith("BIG")) {
total *= 0.8;
}
order.setTotal(total);
}
// 检查库存
for (OrderItem item : order.getItems()) {
int available = inventoryService.getAvailableQuantity(item.getProductId());
if (available < item.getQuantity()) {
throw new InsufficientInventoryException();
}
}
// 保存订单
orderRepository.save(order);
// 发送邮件
emailService.send(order.getCustomer().getEmail(), "Order Confirmation",
"Your order " + order.getId() + " has been confirmed");
// 更新库存
for (OrderItem item : order.getItems()) {
inventoryService.decrementQuantity(item.getProductId(), item.getQuantity());
}
}✅ 良好示例 - 小巧且聚焦的方法:
java
public void processOrder(Order order) {
validateOrder(order);
calculateOrderTotal(order);
applyCouponDiscount(order);
checkInventoryAvailability(order);
saveOrder(order);
sendConfirmationEmail(order);
updateInventory(order);
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain items");
}
}
private void calculateOrderTotal(Order order) {
double total = order.getItems().stream()
.mapToDouble(this::calculateItemTotal)
.sum();
order.setTotal(total);
}
private double calculateItemTotal(OrderItem item) {
return item.getPrice() * item.getQuantity() * (1 - item.getDiscount());
}
private void applyCouponDiscount(Order order) {
if (order.getCoupon() == null) {
return;
}
double discountMultiplier = getDiscountMultiplier(order.getCoupon());
order.setTotal(order.getTotal() * discountMultiplier);
}
private double getDiscountMultiplier(Coupon coupon) {
String code = coupon.getCode();
if (code.startsWith("SAVE")) return 0.9;
if (code.startsWith("BIG")) return 0.8;
return 1.0;
}
private void checkInventoryAvailability(Order order) {
for (OrderItem item : order.getItems()) {
int available = inventoryService.getAvailableQuantity(item.getProductId());
if (available < item.getQuantity()) {
throw new InsufficientInventoryException(
"Product " + item.getProductId() + " has insufficient inventory");
}
}
}
private void saveOrder(Order order) {
orderRepository.save(order);
}
private void sendConfirmationEmail(Order order) {
String email = order.getCustomer().getEmail();
String subject = "Order Confirmation";
String body = String.format("Your order %s has been confirmed", order.getId());
emailService.send(email, subject, body);
}
private void updateInventory(Order order) {
order.getItems().forEach(item ->
inventoryService.decrementQuantity(item.getProductId(), item.getQuantity())
);
}优势:
- 每个方法有明确的单一用途
- 易于理解和测试
- 主方法读起来像目录
- 可重用的辅助方法
Comment Best Practices
注释最佳实践
Rule: Code should be self-explanatory. Comments should explain WHY, not WHAT.
❌ Bad Comments:
java
// Set the flag to true
isActive = true;
// Loop through users
for (User user : users) {
// Check if user is active
if (user.isActive()) {
// Add to list
activeUsers.add(user);
}
}
// This is the UserService class
public class UserService {
}✅ Good Comments:
java
// No comment needed - code is self-explanatory
isActive = true;
List<User> activeUsers = users.stream()
.filter(User::isActive)
.collect(Collectors.toList());
// Good: Explains WHY, not WHAT
// We use exponential backoff to avoid overwhelming the external API
// after multiple failures (circuit breaker pattern)
private int calculateRetryDelay(int attemptNumber) {
return (int) Math.pow(2, attemptNumber) * 1000;
}
// Good: JavaDoc for public API
/**
* Transfers funds between accounts atomically.
*
* @param fromAccount source account (must have sufficient balance)
* @param toAccount destination account
* @param amount amount to transfer (must be positive)
* @throws InsufficientFundsException if source account lacks funds
* @throws IllegalArgumentException if amount is negative or zero
*/
public void transferFunds(Account fromAccount, Account toAccount, double amount)
throws InsufficientFundsException {
// Implementation
}
// Good: Explains non-obvious business rule
// Tax calculation excludes shipping but includes discount adjustments
// per IRS regulation 2024-15
double taxableAmount = subtotal - discount;When to Comment:
- Public APIs (JavaDoc)
- Complex algorithms (explain approach)
- Business rules (regulatory requirements)
- Workarounds (why the workaround is needed)
- TODO/FIXME (with ticket numbers)
When NOT to Comment:
- Obvious code
- Commented-out code (delete it, use version control)
- Change logs (use git)
规则: 代码应自解释。注释应解释原因,而非内容。
❌ 不良注释:
java
// 将标志设置为true
isActive = true;
// 遍历用户
for (User user : users) {
// 检查用户是否活跃
if (user.isActive()) {
// 添加到列表
activeUsers.add(user);
}
}
// 这是UserService类
public class UserService {
}✅ 良好注释:
java
// 无需注释 - 代码自解释
isActive = true;
List<User> activeUsers = users.stream()
.filter(User::isActive)
.collect(Collectors.toList());
// 良好:解释原因,而非内容
// 我们使用指数退避来避免在多次失败后压垮外部API
// (断路器模式)
private int calculateRetryDelay(int attemptNumber) {
return (int) Math.pow(2, attemptNumber) * 1000;
}
// 良好:公共API的JavaDoc
/**
* 原子性地在账户间转账。
*
* @param fromAccount 源账户(必须有足够余额)
* @param toAccount 目标账户
* @param amount 转账金额(必须为正)
* @throws InsufficientFundsException 如果源账户资金不足
* @throws IllegalArgumentException 如果金额为负或零
*/
public void transferFunds(Account fromAccount, Account toAccount, double amount)
throws InsufficientFundsException {
// 实现
}
// 良好:解释不明显的业务规则
// 根据IRS规定2024-15,税费计算不包含运费但包含折扣调整
double taxableAmount = subtotal - discount;何时添加注释:
- 公共API(JavaDoc)
- 复杂算法(解释方法)
- 业务规则(法规要求)
- 变通方案(解释为何需要变通)
- TODO/FIXME(带工单编号)
何时不要添加注释:
- 明显的代码
- 被注释掉的代码(删除它,使用版本控制)
- 变更日志(使用git)
Error Handling
错误处理
Rule: Use exceptions for exceptional cases. Don't use exceptions for control flow.
❌ Bad Error Handling:
java
// Using exceptions for control flow
public User findUser(Long id) {
try {
return userRepository.findById(id);
} catch (NotFoundException e) {
return null; // Swallowing exception
}
}
// Catching generic Exception
public void processData(String data) {
try {
// Complex logic
} catch (Exception e) {
// Too broad!
}
}
// Empty catch block
try {
riskyOperation();
} catch (IOException e) {
// Ignored - NEVER DO THIS
}✅ Good Error Handling:
java
// Use Optional for "not found" scenarios
public Optional<User> findUser(Long id) {
return userRepository.findById(id);
}
// Catch specific exceptions
public void processData(String data) {
try {
parseAndValidate(data);
saveToDatabase(data);
} catch (JsonParseException e) {
log.error("Failed to parse JSON data: {}", data, e);
throw new DataProcessingException("Invalid JSON format", e);
} catch (DataAccessException e) {
log.error("Database error while saving data", e);
throw new DataProcessingException("Failed to save data", e);
}
}
// Always handle or rethrow exceptions
try {
riskyOperation();
} catch (IOException e) {
log.error("Operation failed", e);
throw new ApplicationException("Failed to perform operation", e);
}
// Use try-with-resources for auto-closeable resources
public String readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.lines().collect(Collectors.joining("\n"));
}
// Reader automatically closed, even if exception occurs
}规则: 异常用于异常情况。不要将异常用于控制流。
❌ 不良错误处理:
java
// 使用异常进行控制流
public User findUser(Long id) {
try {
return userRepository.findById(id);
} catch (NotFoundException e) {
return null; // 吞掉异常
}
}
// 捕获通用Exception
public void processData(String data) {
try {
// 复杂逻辑
} catch (Exception e) {
// 范围太广!
}
}
// 空catch块
try {
riskyOperation();
} catch (IOException e) {
// 忽略 - 绝对不要这样做
}✅ 良好错误处理:
java
// 对“未找到”场景使用Optional
public Optional<User> findUser(Long id) {
return userRepository.findById(id);
}
// 捕获特定异常
public void processData(String data) {
try {
parseAndValidate(data);
saveToDatabase(data);
} catch (JsonParseException e) {
log.error("Failed to parse JSON data: {}", data, e);
throw new DataProcessingException("Invalid JSON format", e);
} catch (DataAccessException e) {
log.error("Database error while saving data", e);
throw new DataProcessingException("Failed to save data", e);
}
}
// 始终处理或重新抛出异常
try {
riskyOperation();
} catch (IOException e) {
log.error("Operation failed", e);
throw new ApplicationException("Failed to perform operation", e);
}
// 对AutoCloseable资源使用try-with-resources
public String readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.lines().collect(Collectors.joining("\n"));
}
// 即使发生异常,Reader也会自动关闭
}Code Organization
代码组织
Rule: Organize code logically within classes. Related methods should be close together.
✅ Good Class Organization:
java
public class UserService {
// 1. Constants
private static final int MAX_LOGIN_ATTEMPTS = 3;
private static final long LOCKOUT_DURATION_MINUTES = 30;
// 2. Static fields
private static final Logger log = LoggerFactory.getLogger(UserService.class);
// 3. Instance fields
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
// 4. Constructors
public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
EmailService emailService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.emailService = emailService;
}
// 5. Public methods (grouped by functionality)
// User creation methods
public User registerUser(UserRegistrationDto dto) {
validateRegistration(dto);
User user = createUser(dto);
sendWelcomeEmail(user);
return user;
}
// User authentication methods
public AuthToken login(String email, String password) {
User user = findUserByEmail(email);
validatePassword(user, password);
return generateAuthToken(user);
}
// 6. Private helper methods (near methods that use them)
private void validateRegistration(UserRegistrationDto dto) {
// Validation logic
}
private User createUser(UserRegistrationDto dto) {
// Creation logic
}
private void sendWelcomeEmail(User user) {
emailService.send(user.getEmail(), "Welcome!", getWelcomeEmailBody());
}
private User findUserByEmail(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UserNotFoundException(email));
}
private void validatePassword(User user, String password) {
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
throw new AuthenticationException("Invalid password");
}
}
private AuthToken generateAuthToken(User user) {
// Token generation logic
}
private String getWelcomeEmailBody() {
return "Welcome to our application!";
}
}规则: 在类中逻辑地组织代码。相关方法应放在一起。
✅ 良好的类组织:
java
public class UserService {
// 1. 常量
private static final int MAX_LOGIN_ATTEMPTS = 3;
private static final long LOCKOUT_DURATION_MINUTES = 30;
// 2. 静态字段
private static final Logger log = LoggerFactory.getLogger(UserService.class);
// 3. 实例字段
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
// 4. 构造函数
public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
EmailService emailService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.emailService = emailService;
}
// 5. 公共方法(按功能分组)
// 用户创建方法
public User registerUser(UserRegistrationDto dto) {
validateRegistration(dto);
User user = createUser(dto);
sendWelcomeEmail(user);
return user;
}
// 用户认证方法
public AuthToken login(String email, String password) {
User user = findUserByEmail(email);
validatePassword(user, password);
return generateAuthToken(user);
}
// 6. 私有辅助方法(靠近使用它们的方法)
private void validateRegistration(UserRegistrationDto dto) {
// 验证逻辑
}
private User createUser(UserRegistrationDto dto) {
// 创建逻辑
}
private void sendWelcomeEmail(User user) {
emailService.send(user.getEmail(), "Welcome!", getWelcomeEmailBody());
}
private User findUserByEmail(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UserNotFoundException(email));
}
private void validatePassword(User user, String password) {
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
throw new AuthenticationException("Invalid password");
}
}
private AuthToken generateAuthToken(User user) {
// 令牌生成逻辑
}
private String getWelcomeEmailBody() {
return "Welcome to our application!";
}
}Java-Specific Best Practices
Java特定最佳实践
Using Optional Instead of Null
使用Optional而非Null
Rule: Use to represent values that may be absent. Never return null for collections.
Optional<T>❌ Bad - Returning Null:
java
public User findUser(Long id) {
User user = database.find(id);
return user; // May return null!
}
// Caller must remember to check null
User user = findUser(123L);
if (user != null) {
// Use user
}✅ Good - Using Optional:
java
public Optional<User> findUser(Long id) {
User user = database.find(id);
return Optional.ofNullable(user);
}
// Caller forced to handle absence
Optional<User> userOpt = findUser(123L);
// Method 1: ifPresent
userOpt.ifPresent(user -> System.out.println(user.getName()));
// Method 2: orElse
User user = userOpt.orElse(createDefaultUser());
// Method 3: orElseThrow
User user = userOpt.orElseThrow(() ->
new UserNotFoundException("User 123 not found"));
// Method 4: map/flatMap
String email = userOpt
.map(User::getEmail)
.orElse("unknown@example.com");Optional Best Practices:
- Return from methods that may not find a value
Optional<T> - Never use for fields
Optional - Never pass as method parameters
Optional - Never return null from -returning methods
Optional - Use instead of
Optional.empty()Optional.ofNullable(null)
❌ Bad Optional Usage:
java
// Don't use Optional as field
public class User {
private Optional<String> middleName; // BAD!
}
// Don't use Optional as parameter
public void setEmail(Optional<String> email) { // BAD!
}
// Don't call get() without checking
Optional<User> userOpt = findUser(id);
User user = userOpt.get(); // May throw NoSuchElementException!✅ Good Optional Usage:
java
// Use null for optional fields (or use proper null handling)
public class User {
private String middleName; // Can be null
public Optional<String> getMiddleName() {
return Optional.ofNullable(middleName);
}
}
// Use regular parameter with @Nullable annotation
public void setEmail(@Nullable String email) {
this.email = email;
}
// Always check before get(), or use other methods
Optional<User> userOpt = findUser(id);
if (userOpt.isPresent()) {
User user = userOpt.get();
// Use user
}
// Or use orElse/orElseThrow/ifPresent
User user = userOpt.orElseThrow(() -> new NotFoundException());规则: 使用表示可能不存在的值。永远不要为集合返回null。
Optional<T>❌ 不良示例 - 返回Null:
java
public User findUser(Long id) {
User user = database.find(id);
return user; // 可能返回null!
}
// 调用者必须记得检查null
User user = findUser(123L);
if (user != null) {
// 使用user
}✅ 良好示例 - 使用Optional:
java
public Optional<User> findUser(Long id) {
User user = database.find(id);
return Optional.ofNullable(user);
}
// 调用者必须处理不存在的情况
Optional<User> userOpt = findUser(123L);
// 方法1:ifPresent
userOpt.ifPresent(user -> System.out.println(user.getName()));
// 方法2:orElse
User user = userOpt.orElse(createDefaultUser());
// 方法3:orElseThrow
User user = userOpt.orElseThrow(() ->
new UserNotFoundException("User 123 not found"));
// 方法4:map/flatMap
String email = userOpt
.map(User::getEmail)
.orElse("unknown@example.com");Optional最佳实践:
- 从可能找不到值的方法返回
Optional<T> - 永远不要对字段使用
Optional - 永远不要将作为方法参数传递
Optional - 永远不要从返回的方法返回null
Optional - 使用而非
Optional.empty()Optional.ofNullable(null)
❌ 不良Optional用法:
java
// 不要将Optional用作字段
public class User {
private Optional<String> middleName; // 不良!
}
// 不要将Optional用作参数
public void setEmail(Optional<String> email) { // 不良!
}
// 不要在未检查的情况下调用get()
Optional<User> userOpt = findUser(id);
User user = userOpt.get(); // 可能抛出NoSuchElementException!✅ 良好Optional用法:
java
// 对可选字段使用null(或使用适当的null处理)
public class User {
private String middleName; // 可以为null
public Optional<String> getMiddleName() {
return Optional.ofNullable(middleName);
}
}
// 使用带@Nullable注解的常规参数
public void setEmail(@Nullable String email) {
this.email = email;
}
// 始终在调用get()前检查,或使用其他方法
Optional<User> userOpt = findUser(id);
if (userOpt.isPresent()) {
User user = userOpt.get();
// 使用user
}
// 或使用orElse/orElseThrow/ifPresent
User user = userOpt.orElseThrow(() -> new NotFoundException());Prefer Composition Over Inheritance
优先使用组合而非继承
Rule: Favor composition (has-a) over inheritance (is-a) unless there's a true is-a relationship.
❌ Bad - Inheritance Abuse:
java
// Inheritance used just to reuse code (wrong!)
public class Stack extends ArrayList<Object> {
public void push(Object item) {
add(item);
}
public Object pop() {
return remove(size() - 1);
}
}
// Problems:
// 1. Stack exposes all ArrayList methods (add, remove, clear, etc.)
// 2. Stack IS-NOT-A ArrayList semantically
// 3. Breaks encapsulation✅ Good - Composition:
java
public class Stack<T> {
private final List<T> elements = new ArrayList<>();
public void push(T item) {
elements.add(item);
}
public T pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public T peek() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.get(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
public int size() {
return elements.size();
}
}When to Use Inheritance:
- True is-a relationship exists
- Subclass is a specialized version of superclass
- Liskov Substitution Principle holds
When to Use Composition:
- Reusing functionality
- Has-a relationship
- Need flexibility to change implementation
- Multiple behaviors needed (can compose many, inherit one)
规则: 优先使用组合(有一个)而非继承(是一个),除非存在真正的是一个关系。
❌ 不良示例 - 滥用继承:
java
// 仅为重用代码而使用继承(错误!)
public class Stack extends ArrayList<Object> {
public void push(Object item) {
add(item);
}
public Object pop() {
return remove(size() - 1);
}
}
// 问题:
// 1. Stack暴露所有ArrayList方法(add, remove, clear等)
// 2. Stack在语义上不是ArrayList
// 3. 破坏封装✅ 良好示例 - 组合:
java
public class Stack<T> {
private final List<T> elements = new ArrayList<>();
public void push(T item) {
elements.add(item);
}
public T pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public T peek() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.get(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
public int size() {
return elements.size();
}
}何时使用继承:
- 存在真正的是一个关系
- 子类是父类的特化版本
- 遵循里氏替换原则
何时使用组合:
- 重用功能
- 有一个关系
- 需要灵活更改实现
- 需要多种行为(可以组合多个,只能继承一个)
Immutability with Final
使用Final实现不可变性
Rule: Make classes and variables immutable when possible. Use extensively.
final✅ Immutable Class:
java
public final class Money {
private final double amount;
private final String currency;
public Money(double amount, String currency) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public double getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// Return new instance instead of modifying
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount + other.amount, this.currency);
}
public Money multiply(double multiplier) {
return new Money(this.amount * multiplier, this.currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return Double.compare(money.amount, amount) == 0
&& currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}Benefits of Immutability:
- Thread-safe by default
- Can be safely shared
- Simpler to reason about
- No defensive copying needed
- Safe to use as HashMap keys
✅ Using Final for Variables:
java
public void processOrder(Order order) {
final double total = order.getTotal();
final List<OrderItem> items = order.getItems();
// Compiler prevents reassignment
// total = 100; // Compilation error
// items = new ArrayList<>(); // Compilation error
// Note: final prevents reassignment, not mutation
items.add(new OrderItem()); // This is allowed!
// For true immutability, use Collections.unmodifiableList
final List<OrderItem> immutableItems =
Collections.unmodifiableList(new ArrayList<>(items));
}规则: 尽可能使类和变量不可变。广泛使用。
final✅ 不可变类:
java
public final class Money {
private final double amount;
private final String currency;
public Money(double amount, String currency) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public double getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// 返回新实例而非修改
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount + other.amount, this.currency);
}
public Money multiply(double multiplier) {
return new Money(this.amount * multiplier, this.currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return Double.compare(money.amount, amount) == 0
&& currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}不可变性的优势:
- 默认线程安全
- 可以安全共享
- 更易于推理
- 无需防御性复制
- 安全用作HashMap键
✅ 对变量使用Final:
java
public void processOrder(Order order) {
final double total = order.getTotal();
final List<OrderItem> items = order.getItems();
// 编译器阻止重新赋值
// total = 100; // 编译错误
// items = new ArrayList<>(); // 编译错误
// 注意:final阻止重新赋值,而非突变
items.add(new OrderItem()); // 这是允许的!
// 要实现真正的不可变性,使用Collections.unmodifiableList
final List<OrderItem> immutableItems =
Collections.unmodifiableList(new ArrayList<>(items));
}Stream API Usage
Stream API使用
Rule: Use Stream API for collection operations. It's more readable, functional, and can be parallelized.
❌ Bad - Imperative Style:
java
List<User> activeUsers = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
activeUsers.add(user);
}
}
List<String> emails = new ArrayList<>();
for (User user : activeUsers) {
emails.add(user.getEmail());
}
Collections.sort(emails);✅ Good - Declarative with Streams:
java
List<String> emails = users.stream()
.filter(User::isActive)
.map(User::getEmail)
.sorted()
.collect(Collectors.toList());✅ Common Stream Patterns:
java
// Filtering
List<Order> largeOrders = orders.stream()
.filter(order -> order.getTotal() > 1000)
.collect(Collectors.toList());
// Mapping
List<String> customerNames = orders.stream()
.map(order -> order.getCustomer().getName())
.collect(Collectors.toList());
// FlatMap (flatten nested collections)
List<OrderItem> allItems = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.toList());
// Reduce (sum, average, etc.)
double totalRevenue = orders.stream()
.mapToDouble(Order::getTotal)
.sum();
Optional<Order> maxOrder = orders.stream()
.max(Comparator.comparing(Order::getTotal));
// Grouping
Map<String, List<Order>> ordersByStatus = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus));
// Partitioning (special case of grouping - boolean)
Map<Boolean, List<Order>> ordersByShipped = orders.stream()
.collect(Collectors.partitioningBy(Order::isShipped));
// Find first/any
Optional<User> firstAdmin = users.stream()
.filter(User::isAdmin)
.findFirst();
// Distinct
List<String> uniqueCities = users.stream()
.map(User::getCity)
.distinct()
.collect(Collectors.toList());
// Limit and skip
List<User> firstTenUsers = users.stream()
.limit(10)
.collect(Collectors.toList());
// Combining operations
double averageOrderValueForActiveCustomers = orders.stream()
.filter(order -> order.getCustomer().isActive())
.mapToDouble(Order::getTotal)
.average()
.orElse(0.0);Stream Best Practices:
- Don't reuse streams (create new stream for each pipeline)
- Avoid side effects in stream operations
- Use method references when possible
- Consider parallel streams for large datasets (but measure!)
- Streams are lazy - terminal operation triggers execution
❌ Bad Stream Usage:
java
// Don't modify external state in streams
List<String> results = new ArrayList<>();
users.stream()
.forEach(user -> results.add(user.getName())); // Side effect!
// Use collect instead
List<String> results = users.stream()
.map(User::getName)
.collect(Collectors.toList());
// Don't reuse streams
Stream<User> userStream = users.stream();
long count = userStream.count(); // OK
List<User> list = userStream.collect(Collectors.toList()); // IllegalStateException!规则: 对集合操作使用Stream API。它更具可读性、函数式且可以并行化。
❌ 不良示例 - 命令式风格:
java
List<User> activeUsers = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
activeUsers.add(user);
}
}
List<String> emails = new ArrayList<>();
for (User user : activeUsers) {
emails.add(user.getEmail());
}
Collections.sort(emails);✅ 良好示例 - 声明式风格(使用Streams):
java
List<String> emails = users.stream()
.filter(User::isActive)
.map(User::getEmail)
.sorted()
.collect(Collectors.toList());✅ 常见Stream模式:
java
// 过滤
List<Order> largeOrders = orders.stream()
.filter(order -> order.getTotal() > 1000)
.collect(Collectors.toList());
// 映射
List<String> customerNames = orders.stream()
.map(order -> order.getCustomer().getName())
.collect(Collectors.toList());
// FlatMap(展平嵌套集合)
List<OrderItem> allItems = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.toList());
// 归约(求和、平均等)
double totalRevenue = orders.stream()
.mapToDouble(Order::getTotal)
.sum();
Optional<Order> maxOrder = orders.stream()
.max(Comparator.comparing(Order::getTotal));
// 分组
Map<String, List<Order>> ordersByStatus = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus));
// 分区(分组的特殊情况 - 布尔值)
Map<Boolean, List<Order>> ordersByShipped = orders.stream()
.collect(Collectors.partitioningBy(Order::isShipped));
// 查找第一个/任意一个
Optional<User> firstAdmin = users.stream()
.filter(User::isAdmin)
.findFirst();
// 去重
List<String> uniqueCities = users.stream()
.map(User::getCity)
.distinct()
.collect(Collectors.toList());
// 限制和跳过
List<User> firstTenUsers = users.stream()
.limit(10)
.collect(Collectors.toList());
// 组合操作
double averageOrderValueForActiveCustomers = orders.stream()
.filter(order -> order.getCustomer().isActive())
.mapToDouble(Order::getTotal)
.average()
.orElse(0.0);Stream最佳实践:
- 不要重用流(为每个管道创建新流)
- 避免在流操作中产生副作用
- 尽可能使用方法引用
- 对大型数据集考虑并行流(但要衡量!)
- 流是惰性的 - 终端操作触发执行
❌ 不良Stream用法:
java
// 不要在流中修改外部状态
List<String> results = new ArrayList<>();
users.stream()
.forEach(user -> results.add(user.getName())); // 副作用!
// 使用collect代替
List<String> results = users.stream()
.map(User::getName)
.collect(Collectors.toList());
// 不要重用流
Stream<User> userStream = users.stream();
long count = userStream.count(); // 可以
List<User> list = userStream.collect(Collectors.toList()); // IllegalStateException!Lambda Expressions
Lambda表达式
Rule: Use lambda expressions for functional interfaces. Prefer method references when applicable.
✅ Lambda Best Practices:
java
// Lambda expression
List<User> sorted = users.stream()
.sorted((u1, u2) -> u1.getName().compareTo(u2.getName()))
.collect(Collectors.toList());
// Better: Method reference
List<User> sorted = users.stream()
.sorted(Comparator.comparing(User::getName))
.collect(Collectors.toList());
// Lambda with multiple statements
users.forEach(user -> {
user.setLastLoginTime(LocalDateTime.now());
user.incrementLoginCount();
userRepository.save(user);
});
// Custom functional interface
@FunctionalInterface
public interface OrderProcessor {
void process(Order order);
}
OrderProcessor processor = order -> {
validateOrder(order);
calculateTotal(order);
saveOrder(order);
};规则: 对函数式接口使用Lambda表达式。适用时优先使用方法引用。
✅ Lambda最佳实践:
java
// Lambda表达式
List<User> sorted = users.stream()
.sorted((u1, u2) -> u1.getName().compareTo(u2.getName()))
.collect(Collectors.toList());
// 更好:方法引用
List<User> sorted = users.stream()
.sorted(Comparator.comparing(User::getName))
.collect(Collectors.toList());
// 包含多个语句的Lambda
users.forEach(user -> {
user.setLastLoginTime(LocalDateTime.now());
user.incrementLoginCount();
userRepository.save(user);
});
// 自定义函数式接口
@FunctionalInterface
public interface OrderProcessor {
void process(Order order);
}
OrderProcessor processor = order -> {
validateOrder(order);
calculateTotal(order);
saveOrder(order);
};Method References
方法引用
Rule: Use method references instead of lambda expressions when possible. They're more concise.
java
// Lambda vs Method Reference
// Lambda: user -> user.getName()
// Method reference: User::getName
users.stream().map(User::getName);
// Lambda: user -> System.out.println(user)
// Method reference: System.out::println
users.forEach(System.out::println);
// Lambda: () -> new ArrayList<>()
// Method reference: ArrayList::new
Supplier<List<String>> supplier = ArrayList::new;
// Lambda: str -> str.length()
// Method reference: String::length
strings.stream().map(String::length);Types of Method References:
- Static method:
ClassName::staticMethod - Instance method of particular object:
object::instanceMethod - Instance method of arbitrary object:
ClassName::instanceMethod - Constructor:
ClassName::new
规则: 适用时使用方法引用而非Lambda表达式。它们更简洁。
java
// Lambda vs 方法引用
// Lambda: user -> user.getName()
// 方法引用: User::getName
users.stream().map(User::getName);
// Lambda: user -> System.out.println(user)
// 方法引用: System.out::println
users.forEach(System.out::println);
// Lambda: () -> new ArrayList<>()
// 方法引用: ArrayList::new
Supplier<List<String>> supplier = ArrayList::new;
// Lambda: str -> str.length()
// 方法引用: String::length
strings.stream().map(String::length);方法引用类型:
- 静态方法:
ClassName::staticMethod - 特定对象的实例方法:
object::instanceMethod - 任意对象的实例方法:
ClassName::instanceMethod - 构造函数:
ClassName::new
Try-With-Resources
Try-With-Resources
Rule: Always use try-with-resources for AutoCloseable resources.
❌ Bad - Manual Resource Management:
java
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
// Process line
} catch (IOException e) {
log.error("Error reading file", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("Error closing reader", e);
}
}
}✅ Good - Try-With-Resources:
java
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
// Process line
} catch (IOException e) {
log.error("Error reading file", e);
}
// Reader automatically closed, even if exception occurs
// Multiple resources
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
// Use streams
} catch (IOException e) {
log.error("Error processing files", e);
}
// Both streams automatically closed in reverse order规则: 对AutoCloseable资源始终使用try-with-resources。
❌ 不良示例 - 手动资源管理:
java
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
// 处理行
} catch (IOException e) {
log.error("Error reading file", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("Error closing reader", e);
}
}
}✅ 良好示例 - Try-With-Resources:
java
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
// 处理行
} catch (IOException e) {
log.error("Error reading file", e);
}
// 即使发生异常,Reader也会自动关闭
// 多个资源
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
// 使用流
} catch (IOException e) {
log.error("Error processing files", e);
}
// 两个流都会按相反顺序自动关闭StringBuilder vs String Concatenation
StringBuilder vs 字符串拼接
Rule: Use for multiple string concatenations in loops. Use for simple concatenations.
StringBuilder+❌ Bad - String Concatenation in Loop:
java
String result = "";
for (int i = 0; i < 1000; i++) {
result += i + ","; // Creates 1000 new String objects!
}✅ Good - StringBuilder:
java
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append(i).append(",");
}
String result = builder.toString();
// Or use String.join for collections
List<String> items = Arrays.asList("apple", "banana", "cherry");
String result = String.join(", ", items);
// Or use Streams
String result = IntStream.range(0, 1000)
.mapToObj(String::valueOf)
.collect(Collectors.joining(","));✅ Simple Concatenation - Use + is Fine:
java
// OK for simple cases (compiler optimizes)
String fullName = firstName + " " + lastName;
String message = "Hello, " + name + "!";
// Not OK in loops
for (String item : items) {
result = result + item; // BAD!
}规则: 对循环中的多次字符串拼接使用。对简单拼接使用。
StringBuilder+❌ 不良示例 - 循环中的字符串拼接:
java
String result = "";
for (int i = 0; i < 1000; i++) {
result += i + ","; // 创建1000个新String对象!
}✅ 良好示例 - StringBuilder:
java
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append(i).append(",");
}
String result = builder.toString();
// 或对集合使用String.join
List<String> items = Arrays.asList("apple", "banana", "cherry");
String result = String.join(", ", items);
// 或使用Streams
String result = IntStream.range(0, 1000)
.mapToObj(String::valueOf)
.collect(Collectors.joining(","));✅ 简单拼接 - 使用+即可:
java
// 简单情况没问题(编译器优化)
String fullName = firstName + " " + lastName;
String message = "Hello, " + name + "!";
// 循环中不行
for (String item : items) {
result = result + item; // 不良!
}Enum Usage
Enum使用
Rule: Use enums for fixed sets of constants. Enums can have fields, methods, and constructors.
✅ Basic Enum:
java
public enum OrderStatus {
PENDING,
PROCESSING,
SHIPPED,
DELIVERED,
CANCELLED
}
// Usage
Order order = new Order();
order.setStatus(OrderStatus.PENDING);
if (order.getStatus() == OrderStatus.DELIVERED) {
// Process delivery
}✅ Enum with Fields and Methods:
java
public enum PaymentMethod {
CREDIT_CARD("Credit Card", 2.9),
DEBIT_CARD("Debit Card", 1.5),
PAYPAL("PayPal", 3.5),
BITCOIN("Bitcoin", 1.0);
private final String displayName;
private final double transactionFeePercent;
PaymentMethod(String displayName, double transactionFeePercent) {
this.displayName = displayName;
this.transactionFeePercent = transactionFeePercent;
}
public String getDisplayName() {
return displayName;
}
public double calculateFee(double amount) {
return amount * (transactionFeePercent / 100);
}
public double calculateTotal(double amount) {
return amount + calculateFee(amount);
}
}
// Usage
PaymentMethod method = PaymentMethod.CREDIT_CARD;
double total = method.calculateTotal(100.0); // 102.90
System.out.println("Paying with: " + method.getDisplayName());✅ Enum with Abstract Methods (Strategy Pattern):
java
public enum Operation {
PLUS {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS {
@Override
public double apply(double x, double y) {
return x - y;
}
},
MULTIPLY {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
public double apply(double x, double y) {
if (y == 0) throw new ArithmeticException("Division by zero");
return x / y;
}
};
public abstract double apply(double x, double y);
}
// Usage
double result = Operation.PLUS.apply(5, 3); // 8.0规则: 对固定的常量集使用枚举。枚举可以有字段、方法和构造函数。
✅ 基础枚举:
java
public enum OrderStatus {
PENDING,
PROCESSING,
SHIPPED,
DELIVERED,
CANCELLED
}
// 使用示例
Order order = new Order();
order.setStatus(OrderStatus.PENDING);
if (order.getStatus() == OrderStatus.DELIVERED) {
// 处理交付
}✅ 带字段和方法的枚举:
java
public enum PaymentMethod {
CREDIT_CARD("Credit Card", 2.9),
DEBIT_CARD("Debit Card", 1.5),
PAYPAL("PayPal", 3.5),
BITCOIN("Bitcoin", 1.0);
private final String displayName;
private final double transactionFeePercent;
PaymentMethod(String displayName, double transactionFeePercent) {
this.displayName = displayName;
this.transactionFeePercent = transactionFeePercent;
}
public String getDisplayName() {
return displayName;
}
public double calculateFee(double amount) {
return amount * (transactionFeePercent / 100);
}
public double calculateTotal(double amount) {
return amount + calculateFee(amount);
}
}
// 使用示例
PaymentMethod method = PaymentMethod.CREDIT_CARD;
double total = method.calculateTotal(100.0); // 102.90
System.out.println("Paying with: " + method.getDisplayName());✅ 带抽象方法的枚举(策略模式):
java
public enum Operation {
PLUS {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS {
@Override
public double apply(double x, double y) {
return x - y;
}
},
MULTIPLY {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
public double apply(double x, double y) {
if (y == 0) throw new ArithmeticException("Division by zero");
return x / y;
}
};
public abstract double apply(double x, double y);
}
// 使用示例
double result = Operation.PLUS.apply(5, 3); // 8.0Exception Handling
异常处理
Checked vs Unchecked Exceptions
受检异常 vs 非受检异常
Rule: Use checked exceptions for recoverable conditions, unchecked for programming errors.
Checked Exceptions:
- Extend (not
Exception)RuntimeException - Must be declared in method signature or caught
- Use for conditions caller can reasonably handle
Unchecked Exceptions:
- Extend
RuntimeException - Don't need to be declared or caught
- Use for programming errors
✅ When to Use Each:
java
// Checked exception - caller can handle
public User findUserById(Long id) throws UserNotFoundException {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
// Unchecked exception - programming error
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
// Checked exception - I/O operation
public String readFile(String path) throws IOException {
return Files.readString(Paths.get(path));
}
// Unchecked exception - null argument (programming error)
public void processOrder(Order order) {
Objects.requireNonNull(order, "Order cannot be null");
// Process order
}规则: 对可恢复的条件使用受检异常,对编程错误使用非受检异常。
受检异常:
- 扩展(不是
Exception)RuntimeException - 必须在方法签名中声明或捕获
- 用于调用者可以合理处理的条件
非受检异常:
- 扩展
RuntimeException - 无需声明或捕获
- 用于编程错误
✅ 何时使用哪种:
java
// 受检异常 - 调用者可以处理
public User findUserById(Long id) throws UserNotFoundException {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
// 非受检异常 - 编程错误
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
// 受检异常 - I/O操作
public String readFile(String path) throws IOException {
return Files.readString(Paths.get(path));
}
// 非受检异常 - null参数(编程错误)
public void processOrder(Order order) {
Objects.requireNonNull(order, "Order cannot be null");
// 处理订单
}When to Catch vs Throw
何时捕获 vs 抛出
Rule: Catch exceptions only if you can handle them meaningfully. Otherwise, let them propagate.
❌ Bad - Catching and Rethrowing:
java
public void processData(String data) throws DataProcessingException {
try {
// Process data
} catch (JsonParseException e) {
throw new DataProcessingException(e); // Unnecessary try-catch
}
}
// Better: Let it propagate
public void processData(String data) throws JsonParseException {
// Process data
}✅ Good - Catch When You Can Handle:
java
public void processDataWithRetry(String data) {
int maxAttempts = 3;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
processData(data);
return; // Success
} catch (TransientException e) {
if (attempt == maxAttempts) {
log.error("Failed after {} attempts", maxAttempts, e);
throw new DataProcessingException("Processing failed", e);
}
log.warn("Attempt {} failed, retrying...", attempt);
sleep(1000 * attempt); // Exponential backoff
}
}
}规则: 仅当可以有意义地处理异常时才捕获。否则,让它传播。
❌ 不良示例 - 捕获并重新抛出:
java
public void processData(String data) throws DataProcessingException {
try {
// 处理数据
} catch (JsonParseException e) {
throw new DataProcessingException(e); // 不必要的try-catch
}
}
// 更好:让它传播
public void processData(String data) throws JsonParseException {
// 处理数据
}✅ 良好示例 - 可以处理时捕获:
java
public void processDataWithRetry(String data) {
int maxAttempts = 3;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
processData(data);
return; // 成功
} catch (TransientException e) {
if (attempt == maxAttempts) {
log.error("Failed after {} attempts", maxAttempts, e);
throw new DataProcessingException("Processing failed", e);
}
log.warn("Attempt {} failed, retrying...", attempt);
sleep(1000 * attempt); // 指数退避
}
}
}Custom Exception Design
自定义异常设计
✅ Well-Designed Custom Exception:
java
public class InsufficientFundsException extends Exception {
private final double requestedAmount;
private final double availableBalance;
public InsufficientFundsException(double requestedAmount, double availableBalance) {
super(String.format("Insufficient funds: requested %.2f, available %.2f",
requestedAmount, availableBalance));
this.requestedAmount = requestedAmount;
this.availableBalance = availableBalance;
}
public double getRequestedAmount() {
return requestedAmount;
}
public double getAvailableBalance() {
return availableBalance;
}
public double getShortfall() {
return requestedAmount - availableBalance;
}
}
// Usage
try {
account.withdraw(500);
} catch (InsufficientFundsException e) {
log.error("Withdrawal failed: {}", e.getMessage());
notifyUser(String.format("You need $%.2f more", e.getShortfall()));
}✅ 设计良好的自定义异常:
java
public class InsufficientFundsException extends Exception {
private final double requestedAmount;
private final double availableBalance;
public InsufficientFundsException(double requestedAmount, double availableBalance) {
super(String.format("Insufficient funds: requested %.2f, available %.2f",
requestedAmount, availableBalance));
this.requestedAmount = requestedAmount;
this.availableBalance = availableBalance;
}
public double getRequestedAmount() {
return requestedAmount;
}
public double getAvailableBalance() {
return availableBalance;
}
public double getShortfall() {
return requestedAmount - availableBalance;
}
}
// 使用示例
try {
account.withdraw(500);
} catch (InsufficientFundsException e) {
log.error("Withdrawal failed: {}", e.getMessage());
notifyUser(String.format("You need $%.2f more", e.getShortfall()));
}Logging Exceptions
记录异常
✅ Exception Logging Best Practices:
java
public void processOrder(Order order) {
try {
validateOrder(order);
saveOrder(order);
sendConfirmation(order);
} catch (ValidationException e) {
// Log with context
log.error("Order validation failed for order {}: {}",
order.getId(), e.getMessage(), e);
throw e;
} catch (DataAccessException e) {
// Log with different level based on recoverability
log.error("Failed to save order {}", order.getId(), e);
throw new OrderProcessingException("Failed to save order", e);
} catch (EmailException e) {
// Non-critical error - log warning
log.warn("Failed to send confirmation email for order {}",
order.getId(), e);
// Don't rethrow - order was saved successfully
}
}✅ 异常记录最佳实践:
java
public void processOrder(Order order) {
try {
validateOrder(order);
saveOrder(order);
sendConfirmation(order);
} catch (ValidationException e) {
// 带上下文记录
log.error("Order validation failed for order {}: {}",
order.getId(), e.getMessage(), e);
throw e;
} catch (DataAccessException e) {
// 根据可恢复性使用不同级别记录
log.error("Failed to save order {}", order.getId(), e);
throw new OrderProcessingException("Failed to save order", e);
} catch (EmailException e) {
// 非关键错误 - 记录警告
log.warn("Failed to send confirmation email for order {}",
order.getId(), e);
// 不重新抛出 - 订单已成功保存
}
}Never Swallow Exceptions
永远不要吞掉异常
❌ Bad - Swallowing Exceptions:
java
try {
riskyOperation();
} catch (Exception e) {
// Silent failure - NEVER DO THIS!
}
try {
closeResource();
} catch (Exception e) {
e.printStackTrace(); // Insufficient - use logging!
}✅ Good - Proper Exception Handling:
java
try {
riskyOperation();
} catch (Exception e) {
log.error("Operation failed", e);
throw new ApplicationException("Failed to perform operation", e);
}
// Or if truly acceptable to ignore
try {
closeResource();
} catch (IOException e) {
log.warn("Failed to close resource (non-critical)", e);
// OK to continue without rethrowing in cleanup scenarios
}❌ 不良示例 - 吞掉异常:
java
try {
riskyOperation();
} catch (Exception e) {
// 静默失败 - 绝对不要这样做!
}
try {
closeResource();
} catch (Exception e) {
e.printStackTrace(); // 不足 - 使用日志!
}✅ 良好示例 - 正确异常处理:
java
try {
riskyOperation();
} catch (Exception e) {
log.error("Operation failed", e);
throw new ApplicationException("Failed to perform operation", e);
}
// 或者如果确实可以忽略
try {
closeResource();
} catch (IOException e) {
log.warn("Failed to close resource (non-critical)", e);
// 在清理场景中可以不重新抛出
}Collections and Generics
集合和泛型
Choosing the Right Collection
选择正确的集合
Guide to Collection Selection:
java
// List - Ordered collection, allows duplicates
// Use ArrayList for random access, LinkedList for frequent insertions/deletions
List<String> names = new ArrayList<>();
List<Task> taskQueue = new LinkedList<>();
// Set - No duplicates, no guaranteed order
// Use HashSet for general use, TreeSet for sorted, LinkedHashSet for insertion order
Set<String> uniqueEmails = new HashSet<>();
Set<Integer> sortedNumbers = new TreeSet<>();
Set<String> insertionOrderSet = new LinkedHashSet<>();
// Map - Key-value pairs, no duplicate keys
// Use HashMap for general use, TreeMap for sorted keys, LinkedHashMap for insertion order
Map<Long, User> userCache = new HashMap<>();
Map<String, Integer> sortedMap = new TreeMap<>();
Map<String, String> orderedMap = new LinkedHashMap<>();
// Queue - FIFO operations
// Use LinkedList or ArrayDeque for general queue
Queue<Task> taskQueue = new LinkedList<>();
Deque<String> deque = new ArrayDeque<>();
// Stack operations - Use Deque instead of Stack class
Deque<String> stack = new ArrayDeque<>();
stack.push("item");
String item = stack.pop();Performance Characteristics:
java
// ArrayList
// - Get by index: O(1)
// - Add at end: O(1) amortized
// - Insert/remove at position: O(n)
// - Search: O(n)
// LinkedList
// - Get by index: O(n)
// - Add/remove at beginning/end: O(1)
// - Insert/remove at position: O(n)
// - Search: O(n)
// HashSet/HashMap
// - Add/remove/contains: O(1) average
// - Iteration: O(capacity + size)
// TreeSet/TreeMap
// - Add/remove/contains: O(log n)
// - Iteration in sorted order: O(n)集合选择指南:
java
// List - 有序集合,允许重复
// 随机访问用ArrayList,频繁在两端插入/删除用LinkedList
List<String> names = new ArrayList<>();
List<Task> taskQueue = new LinkedList<>();
// Set - 无重复,无保证顺序
// 一般用途用HashSet,排序用TreeSet,插入顺序用LinkedHashSet
Set<String> uniqueEmails = new HashSet<>();
Set<Integer> sortedNumbers = new TreeSet<>();
Set<String> insertionOrderSet = new LinkedHashSet<>();
// Map - 键值对,无重复键
// 一般用途用HashMap,按键排序用TreeMap,插入顺序用LinkedHashMap
Map<Long, User> userCache = new HashMap<>();
Map<String, Integer> sortedMap = new TreeMap<>();
Map<String, String> orderedMap = new LinkedHashMap<>();
// Queue - FIFO操作
// 一般队列用LinkedList或ArrayDeque
Queue<Task> taskQueue = new LinkedList<>();
Deque<String> deque = new ArrayDeque<>();
// 栈操作 - 使用Deque而非Stack类
Deque<String> stack = new ArrayDeque<>();
stack.push("item");
String item = stack.pop();性能特征:
java
// ArrayList
// - 按索引获取:O(1)
// - 在末尾添加:O(1) 均摊
// - 在指定位置插入/删除:O(n)
// - 搜索:O(n)
// LinkedList
// - 按索引获取:O(n)
// - 在开头/末尾添加/删除:O(1)
// - 在指定位置插入/删除:O(n)
// - 搜索:O(n)
// HashSet/HashMap
// - 添加/删除/包含:O(1) 平均
// - 迭代:O(capacity + size)
// TreeSet/TreeMap
// - 添加/删除/包含:O(log n)
// - 按顺序迭代:O(n)Immutable Collections
不可变集合
✅ Creating Immutable Collections:
java
// Java 9+ factory methods (preferred)
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("x", "y", "z");
Map<String, Integer> immutableMap = Map.of(
"one", 1,
"two", 2,
"three", 3
);
// For more than 10 entries in Map
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("key1", 1),
Map.entry("key2", 2),
Map.entry("key3", 3)
);
// Pre-Java 9 - Collections.unmodifiableXxx
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
List<String> immutableList = Collections.unmodifiableList(list);
// Guava (if available)
ImmutableList<String> immutableList = ImmutableList.of("a", "b", "c");
ImmutableSet<String> immutableSet = ImmutableSet.of("x", "y", "z");
ImmutableMap<String, Integer> immutableMap = ImmutableMap.of("one", 1, "two", 2);✅ 创建不可变集合:
java
// Java 9+ 工厂方法(推荐)
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("x", "y", "z");
Map<String, Integer> immutableMap = Map.of(
"one", 1,
"two", 2,
"three", 3
);
// Map中超过10个条目时
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("key1", 1),
Map.entry("key2", 2),
Map.entry("key3", 3)
);
// Java 9之前 - Collections.unmodifiableXxx
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
List<String> immutableList = Collections.unmodifiableList(list);
// Guava(如果可用)
ImmutableList<String> immutableList = ImmutableList.of("a", "b", "c");
ImmutableSet<String> immutableSet = ImmutableSet.of("x", "y", "z");
ImmutableMap<String, Integer> immutableMap = ImmutableMap.of("one", 1, "two", 2);Generic Type Safety
泛型类型安全
✅ Proper Generic Usage:
java
// Generic class
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// Multiple type parameters
public class Pair<K, V> {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// Bounded type parameters
public class NumberBox<T extends Number> {
private T number;
public void set(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue(); // Can call Number methods
}
}
// Generic method
public <T> List<T> createList(T... elements) {
return Arrays.asList(elements);
}
// Wildcard usage
public void processList(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num.doubleValue());
}
}
public void addToList(List<? super Integer> list) {
list.add(42); // Can add Integer
}✅ 正确使用泛型:
java
// 泛型类
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// 多个类型参数
public class Pair<K, V> {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 有界类型参数
public class NumberBox<T extends Number> {
private T number;
public void set(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue(); // 可以调用Number方法
}
}
// 泛型方法
public <T> List<T> createList(T... elements) {
return Arrays.asList(elements);
}
// 通配符使用
public void processList(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num.doubleValue());
}
}
public void addToList(List<? super Integer> list) {
list.add(42); // 可以添加Integer
}Diamond Operator Usage
菱形运算符使用
✅ Use Diamond Operator (Java 7+):
java
// Before Java 7
Map<String, List<String>> map = new HashMap<String, List<String>>();
// Java 7+ with diamond operator
Map<String, List<String>> map = new HashMap<>();
// Works with anonymous classes (Java 9+)
List<String> list = new ArrayList<>() {
{
add("item");
}
};✅ 使用菱形运算符(Java 7+):
java
// Java 7之前
Map<String, List<String>> map = new HashMap<String, List<String>>();
// Java 7+ 带菱形运算符
Map<String, List<String>> map = new HashMap<>();
// 与匿名类一起使用(Java 9+)
List<String> list = new ArrayList<>() {
{
add("item");
}
};Concurrency
并发
Thread Safety
线程安全
Rule: Design for thread safety from the start. Assume multi-threaded access unless documented otherwise.
✅ Thread-Safe Patterns:
java
// 1. Immutable objects (inherently thread-safe)
public final class ImmutableUser {
private final String name;
private final String email;
public ImmutableUser(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
}
// 2. Synchronized methods
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
// 3. Concurrent collections
private final Map<String, User> userCache = new ConcurrentHashMap<>();
private final List<String> logMessages = new CopyOnWriteArrayList<>();
// 4. Atomic variables
private final AtomicInteger counter = new AtomicInteger(0);
public void incrementCounter() {
counter.incrementAndGet();
}
public int getCounter() {
return counter.get();
}
// 5. ThreadLocal for thread-specific data
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date);
}规则: 从一开始就为线程安全设计。除非有文档说明,否则假设多线程访问。
✅ 线程安全模式:
java
// 1. 不可变对象(天生线程安全)
public final class ImmutableUser {
private final String name;
private final String email;
public ImmutableUser(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
}
// 2. 同步方法
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
// 3. 并发集合
private final Map<String, User> userCache = new ConcurrentHashMap<>();
private final List<String> logMessages = new CopyOnWriteArrayList<>();
// 4. 原子变量
private final AtomicInteger counter = new AtomicInteger(0);
public void incrementCounter() {
counter.incrementAndGet();
}
public int getCounter() {
return counter.get();
}
// 5. ThreadLocal用于线程特定数据
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date);
}ExecutorService Usage
ExecutorService使用
✅ Using ExecutorService:
java
// Fixed thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);
// Submit tasks
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
processTask(taskId);
});
}
// Shutdown gracefully
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
// Try-with-resources (Java 19+)
try (ExecutorService executor = Executors.newFixedThreadPool(10)) {
// Submit tasks
} // Auto-shutdown
// Custom thread pool for better control
ExecutorService customExecutor = new ThreadPoolExecutor(
5, // core pool size
10, // maximum pool size
60L, TimeUnit.SECONDS, // keep alive time
new LinkedBlockingQueue<>(100), // work queue
new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy
);✅ 使用ExecutorService:
java
// 固定线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任务
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
processTask(taskId);
});
}
// 优雅关闭
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
// Try-with-resources(Java 19+)
try (ExecutorService executor = Executors.newFixedThreadPool(10)) {
// 提交任务
} // 自动关闭
// 自定义线程池以获得更好的控制
ExecutorService customExecutor = new ThreadPoolExecutor(
5, // 核心池大小
10, // 最大池大小
60L, TimeUnit.SECONDS, // 保持活动时间
new LinkedBlockingQueue<>(100), // 工作队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);CompletableFuture Patterns
CompletableFuture模式
✅ Asynchronous Programming with CompletableFuture:
java
// Simple async task
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Long-running task
return fetchDataFromApi();
});
// Chain operations
CompletableFuture<UserDto> userFuture = CompletableFuture
.supplyAsync(() -> fetchUserFromDatabase(userId))
.thenApply(user -> mapToDto(user))
.thenApply(dto -> enrichWithAdditionalData(dto));
// Combine multiple futures
CompletableFuture<User> userFuture = fetchUserAsync(userId);
CompletableFuture<List<Order>> ordersFuture = fetchOrdersAsync(userId);
CompletableFuture<UserWithOrders> combined = userFuture
.thenCombine(ordersFuture, (user, orders) ->
new UserWithOrders(user, orders));
// Handle errors
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> riskyOperation())
.exceptionally(ex -> {
log.error("Operation failed", ex);
return "default value";
});
// Multiple async operations
List<CompletableFuture<String>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(() -> fetchData(id)))
.collect(Collectors.toList());
// Wait for all to complete
CompletableFuture<Void> allOf = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
allOf.thenRun(() -> {
List<String> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
processResults(results);
});✅ 使用CompletableFuture进行异步编程:
java
// 简单异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 长时间运行的任务
return fetchDataFromApi();
});
// 链式操作
CompletableFuture<UserDto> userFuture = CompletableFuture
.supplyAsync(() -> fetchUserFromDatabase(userId))
.thenApply(user -> mapToDto(user))
.thenApply(dto -> enrichWithAdditionalData(dto));
// 组合多个future
CompletableFuture<User> userFuture = fetchUserAsync(userId);
CompletableFuture<List<Order>> ordersFuture = fetchOrdersAsync(userId);
CompletableFuture<UserWithOrders> combined = userFuture
.thenCombine(ordersFuture, (user, orders) ->
new UserWithOrders(user, orders));
// 处理错误
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> riskyOperation())
.exceptionally(ex -> {
log.error("Operation failed", ex);
return "default value";
});
// 多个异步操作
List<CompletableFuture<String>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(() -> fetchData(id)))
.collect(Collectors.toList());
// 等待所有完成
CompletableFuture<Void> allOf = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
allOf.thenRun(() -> {
List<String> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
processResults(results);
});Avoiding Deadlocks
避免死锁
✅ Deadlock Prevention:
java
// 1. Always acquire locks in the same order
public class BankAccount {
private final Object lock = new Object();
private double balance;
public static void transfer(BankAccount from, BankAccount to, double amount) {
// Always lock accounts in consistent order (by hashCode)
BankAccount first = from.hashCode() < to.hashCode() ? from : to;
BankAccount second = from.hashCode() < to.hashCode() ? to : from;
synchronized (first.lock) {
synchronized (second.lock) {
if (from == first) {
from.withdraw(amount);
to.deposit(amount);
} else {
from.deposit(amount);
to.withdraw(amount);
}
}
}
}
}
// 2. Use tryLock with timeout
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// Critical section
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. Use higher-level concurrency utilities
ConcurrentHashMap<String, Account> accounts = new ConcurrentHashMap<>();
accounts.compute("account1", (key, account) -> {
account.withdraw(100);
return account;
});✅ 死锁预防:
java
// 1. 始终按相同顺序获取锁
public class BankAccount {
private final Object lock = new Object();
private double balance;
public static void transfer(BankAccount from, BankAccount to, double amount) {
// 始终按一致顺序锁定账户(通过hashCode)
BankAccount first = from.hashCode() < to.hashCode() ? from : to;
BankAccount second = from.hashCode() < to.hashCode() ? to : from;
synchronized (first.lock) {
synchronized (second.lock) {
if (from == first) {
from.withdraw(amount);
to.deposit(amount);
} else {
from.deposit(amount);
to.withdraw(amount);
}
}
}
}
}
// 2. 使用带超时的tryLock
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. 使用高级并发工具
ConcurrentHashMap<String, Account> accounts = new ConcurrentHashMap<>();
accounts.compute("account1", (key, account) -> {
account.withdraw(100);
return account;
});Atomic Classes
原子类
✅ Using Atomic Classes:
java
public class Statistics {
private final AtomicLong totalRequests = new AtomicLong(0);
private final AtomicLong successfulRequests = new AtomicLong(0);
private final AtomicLong failedRequests = new AtomicLong(0);
public void recordSuccess() {
totalRequests.incrementAndGet();
successfulRequests.incrementAndGet();
}
public void recordFailure() {
totalRequests.incrementAndGet();
failedRequests.incrementAndGet();
}
public double getSuccessRate() {
long total = totalRequests.get();
if (total == 0) return 0.0;
return (double) successfulRequests.get() / total;
}
// Atomic update with compareAndSet
private final AtomicReference<Config> config =
new AtomicReference<>(new Config());
public void updateConfig(Config newConfig) {
Config current;
do {
current = config.get();
} while (!config.compareAndSet(current, newConfig));
}
}✅ 使用原子类:
java
public class Statistics {
private final AtomicLong totalRequests = new AtomicLong(0);
private final AtomicLong successfulRequests = new AtomicLong(0);
private final AtomicLong failedRequests = new AtomicLong(0);
public void recordSuccess() {
totalRequests.incrementAndGet();
successfulRequests.incrementAndGet();
}
public void recordFailure() {
totalRequests.incrementAndGet();
failedRequests.incrementAndGet();
}
public double getSuccessRate() {
long total = totalRequests.get();
if (total == 0) return 0.0;
return (double) successfulRequests.get() / total;
}
// 使用compareAndSet进行原子更新
private final AtomicReference<Config> config =
new AtomicReference<>(new Config());
public void updateConfig(Config newConfig) {
Config current;
do {
current = config.get();
} while (!config.compareAndSet(current, newConfig));
}
}Testing (TDD)
测试(TDD)
Unit Testing with JUnit 5
使用JUnit 5进行单元测试
✅ JUnit 5 Best Practices:
java
@DisplayName("User Service Tests")
class UserServiceTest {
private UserService userService;
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
passwordEncoder = mock(PasswordEncoder.class);
userService = new UserService(userRepository, passwordEncoder);
}
@Test
@DisplayName("Should create user with valid data")
void shouldCreateUserWithValidData() {
// Arrange
String email = "test@example.com";
String password = "SecurePass123!";
String encodedPassword = "encoded_password";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(userRepository.existsByEmail(email)).thenReturn(false);
// Act
User user = userService.createUser(email, password);
// Assert
assertNotNull(user);
assertEquals(email, user.getEmail());
assertEquals(encodedPassword, user.getPasswordHash());
verify(userRepository).save(any(User.class));
}
@Test
@DisplayName("Should throw exception when email already exists")
void shouldThrowExceptionWhenEmailExists() {
// Arrange
String email = "existing@example.com";
when(userRepository.existsByEmail(email)).thenReturn(true);
// Act & Assert
assertThrows(DuplicateEmailException.class, () ->
userService.createUser(email, "password"));
verify(userRepository, never()).save(any());
}
@ParameterizedTest
@ValueSource(strings = {"", " ", "invalid-email", "@example.com"})
@DisplayName("Should throw exception for invalid email formats")
void shouldThrowExceptionForInvalidEmail(String invalidEmail) {
assertThrows(ValidationException.class, () ->
userService.createUser(invalidEmail, "password"));
}
@ParameterizedTest
@CsvSource({
"test@example.com, short, 'Password too short'",
"test@example.com, nodigits, 'Password must contain digits'",
"invalid, ValidPass123!, 'Invalid email format'"
})
@DisplayName("Should validate user input correctly")
void shouldValidateUserInput(String email, String password, String expectedError) {
ValidationException exception = assertThrows(ValidationException.class, () ->
userService.createUser(email, password));
assertTrue(exception.getMessage().contains(expectedError));
}
@Test
@DisplayName("Should handle repository exception gracefully")
void shouldHandleRepositoryException() {
// Arrange
when(userRepository.save(any())).thenThrow(new DataAccessException("DB error"));
// Act & Assert
assertThrows(UserCreationException.class, () ->
userService.createUser("test@example.com", "password"));
}
@Nested
@DisplayName("User Authentication Tests")
class UserAuthenticationTests {
@Test
@DisplayName("Should authenticate user with correct password")
void shouldAuthenticateWithCorrectPassword() {
// Arrange
User user = new User("test@example.com", "encoded_pass");
when(userRepository.findByEmail("test@example.com"))
.thenReturn(Optional.of(user));
when(passwordEncoder.matches("password", "encoded_pass"))
.thenReturn(true);
// Act
boolean authenticated = userService.authenticate("test@example.com", "password");
// Assert
assertTrue(authenticated);
}
@Test
@DisplayName("Should reject authentication with wrong password")
void shouldRejectWithWrongPassword() {
// Arrange
User user = new User("test@example.com", "encoded_pass");
when(userRepository.findByEmail("test@example.com"))
.thenReturn(Optional.of(user));
when(passwordEncoder.matches("wrong", "encoded_pass"))
.thenReturn(false);
// Act
boolean authenticated = userService.authenticate("test@example.com", "wrong");
// Assert
assertFalse(authenticated);
}
}
}✅ JUnit 5最佳实践:
java
@DisplayName("User Service Tests")
class UserServiceTest {
private UserService userService;
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
passwordEncoder = mock(PasswordEncoder.class);
userService = new UserService(userRepository, passwordEncoder);
}
@Test
@DisplayName("应使用有效数据创建用户")
void shouldCreateUserWithValidData() {
// 准备
String email = "test@example.com";
String password = "SecurePass123!";
String encodedPassword = "encoded_password";
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
when(userRepository.existsByEmail(email)).thenReturn(false);
// 执行
User user = userService.createUser(email, password);
// 断言
assertNotNull(user);
assertEquals(email, user.getEmail());
assertEquals(encodedPassword, user.getPasswordHash());
verify(userRepository).save(any(User.class));
}
@Test
@DisplayName("当邮箱已存在时应抛出异常")
void shouldThrowExceptionWhenEmailExists() {
// 准备
String email = "existing@example.com";
when(userRepository.existsByEmail(email)).thenReturn(true);
// 执行 & 断言
assertThrows(DuplicateEmailException.class, () ->
userService.createUser(email, "password"));
verify(userRepository, never()).save(any());
}
@ParameterizedTest
@ValueSource(strings = {"", " ", "invalid-email", "@example.com"})
@DisplayName("对无效邮箱格式应抛出异常")
void shouldThrowExceptionForInvalidEmail(String invalidEmail) {
assertThrows(ValidationException.class, () ->
userService.createUser(invalidEmail, "password"));
}
@ParameterizedTest
@CsvSource({
"test@example.com, short, 'Password too short'",
"test@example.com, nodigits, 'Password must contain digits'",
"invalid, ValidPass123!, 'Invalid email format'"
})
@DisplayName("应正确验证用户输入")
void shouldValidateUserInput(String email, String password, String expectedError) {
ValidationException exception = assertThrows(ValidationException.class, () ->
userService.createUser(email, password));
assertTrue(exception.getMessage().contains(expectedError));
}
@Test
@DisplayName("应优雅处理仓库异常")
void shouldHandleRepositoryException() {
// 准备
when(userRepository.save(any())).thenThrow(new DataAccessException("DB error"));
// 执行 & 断言
assertThrows(UserCreationException.class, () ->
userService.createUser("test@example.com", "password"));
}
@Nested
@DisplayName("用户认证测试")
class UserAuthenticationTests {
@Test
@DisplayName("应使用正确密码认证用户")
void shouldAuthenticateWithCorrectPassword() {
// 准备
User user = new User("test@example.com", "encoded_pass");
when(userRepository.findByEmail("test@example.com"))
.thenReturn(Optional.of(user));
when(passwordEncoder.matches("password", "encoded_pass"))
.thenReturn(true);
// 执行
boolean authenticated = userService.authenticate("test@example.com", "password");
// 断言
assertTrue(authenticated);
}
@Test
@DisplayName("应拒绝使用错误密码的认证")
void shouldRejectWithWrongPassword() {
// 准备
User user = new User("test@example.com", "encoded_pass");
when(userRepository.findByEmail("test@example.com"))
.thenReturn(Optional.of(user));
when(passwordEncoder.matches("wrong", "encoded_pass"))
.thenReturn(false);
// 执行
boolean authenticated = userService.authenticate("test@example.com", "wrong");
// 断言
assertFalse(authenticated);
}
}
}Mocking with Mockito
使用Mockito进行模拟
✅ Mockito Best Practices:
java
public class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentService paymentService;
@Mock
private EmailService emailService;
@InjectMocks
private OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void shouldProcessOrderSuccessfully() {
// Arrange
Order order = new Order();
order.setId(1L);
order.setTotal(100.0);
when(paymentService.charge(any(), anyDouble())).thenReturn(true);
when(orderRepository.save(any(Order.class))).thenReturn(order);
// Act
orderService.processOrder(order);
// Assert
verify(paymentService).charge(order, 100.0);
verify(orderRepository).save(order);
verify(emailService).sendConfirmation(order);
assertEquals(OrderStatus.COMPLETED, order.getStatus());
}
@Test
void shouldHandlePaymentFailure() {
// Arrange
Order order = new Order();
when(paymentService.charge(any(), anyDouble())).thenReturn(false);
// Act & Assert
assertThrows(PaymentFailedException.class, () ->
orderService.processOrder(order));
verify(emailService, never()).sendConfirmation(any());
assertEquals(OrderStatus.PAYMENT_FAILED, order.getStatus());
}
@Test
void shouldRetryOnTransientFailure() {
// Arrange
Order order = new Order();
// First two calls fail, third succeeds
when(paymentService.charge(any(), anyDouble()))
.thenThrow(new TransientException())
.thenThrow(new TransientException())
.thenReturn(true);
// Act
orderService.processOrderWithRetry(order);
// Assert
verify(paymentService, times(3)).charge(any(), anyDouble());
}
@Test
void shouldCaptureArgumentValues() {
// Arrange
ArgumentCaptor<Order> orderCaptor = ArgumentCaptor.forClass(Order.class);
Order order = new Order();
order.setTotal(100.0);
// Act
orderService.processOrder(order);
// Assert
verify(orderRepository).save(orderCaptor.capture());
Order savedOrder = orderCaptor.getValue();
assertEquals(OrderStatus.COMPLETED, savedOrder.getStatus());
assertNotNull(savedOrder.getProcessedAt());
}
}✅ Mockito最佳实践:
java
public class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentService paymentService;
@Mock
private EmailService emailService;
@InjectMocks
private OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void shouldProcessOrderSuccessfully() {
// 准备
Order order = new Order();
order.setId(1L);
order.setTotal(100.0);
when(paymentService.charge(any(), anyDouble())).thenReturn(true);
when(orderRepository.save(any(Order.class))).thenReturn(order);
// 执行
orderService.processOrder(order);
// 断言
verify(paymentService).charge(order, 100.0);
verify(orderRepository).save(order);
verify(emailService).sendConfirmation(order);
assertEquals(OrderStatus.COMPLETED, order.getStatus());
}
@Test
void shouldHandlePaymentFailure() {
// 准备
Order order = new Order();
when(paymentService.charge(any(), anyDouble())).thenReturn(false);
// 执行 & 断言
assertThrows(PaymentFailedException.class, () ->
orderService.processOrder(order));
verify(emailService, never()).sendConfirmation(any());
assertEquals(OrderStatus.PAYMENT_FAILED, order.getStatus());
}
@Test
void shouldRetryOnTransientFailure() {
// 准备
Order order = new Order();
// 前两次调用失败,第三次成功
when(paymentService.charge(any(), anyDouble()))
.thenThrow(new TransientException())
.thenThrow(new TransientException())
.thenReturn(true);
// 执行
orderService.processOrderWithRetry(order);
// 断言
verify(paymentService, times(3)).charge(any(), anyDouble());
}
@Test
void shouldCaptureArgumentValues() {
// 准备
ArgumentCaptor<Order> orderCaptor = ArgumentCaptor.forClass(Order.class);
Order order = new Order();
order.setTotal(100.0);
// 执行
orderService.processOrder(order);
// 断言
verify(orderRepository).save(orderCaptor.capture());
Order savedOrder = orderCaptor.getValue();
assertEquals(OrderStatus.COMPLETED, savedOrder.getStatus());
assertNotNull(savedOrder.getProcessedAt());
}
}Test Organization
测试组织
Package Structure:
src/
├── main/
│ └── java/
│ └── com/example/
│ ├── model/
│ │ └── User.java
│ └── service/
│ └── UserService.java
└── test/
└── java/
└── com/example/
├── model/
│ └── UserTest.java
└── service/
└── UserServiceTest.java包结构:
src/
├── main/
│ └── java/
│ └── com/example/
│ ├── model/
│ │ └── User.java
│ └── service/
│ └── UserService.java
└── test/
└── java/
└── com/example/
├── model/
│ └── UserTest.java
└── service/
└── UserServiceTest.javaAAA Pattern (Arrange-Act-Assert)
AAA模式(准备-执行-断言)
✅ Clear Test Structure:
java
@Test
void shouldCalculateDiscountCorrectly() {
// Arrange - Set up test data and expectations
Product product = new Product("Laptop", 1000.0);
Discount discount = new Discount(10); // 10%
PriceCalculator calculator = new PriceCalculator();
// Act - Execute the behavior being tested
double finalPrice = calculator.calculateFinalPrice(product, discount);
// Assert - Verify the expected outcome
assertEquals(900.0, finalPrice, 0.01);
}✅ 清晰的测试结构:
java
@Test
void shouldCalculateDiscountCorrectly() {
// 准备 - 设置测试数据和预期
Product product = new Product("Laptop", 1000.0);
Discount discount = new Discount(10); // 10%
PriceCalculator calculator = new PriceCalculator();
// 执行 - 执行要测试的行为
double finalPrice = calculator.calculateFinalPrice(product, discount);
// 断言 - 验证预期结果
assertEquals(900.0, finalPrice, 0.01);
}Testing Private Methods
测试私有方法
Rule: Don't test private methods directly. Test them through public methods.
❌ Bad:
java
@Test
void testPrivateMethod() {
// Using reflection to test private method - BAD!
Method method = UserService.class.getDeclaredMethod("validateEmail", String.class);
method.setAccessible(true);
boolean result = (boolean) method.invoke(userService, "test@example.com");
assertTrue(result);
}✅ Good:
java
@Test
void shouldRejectInvalidEmailDuringUserCreation() {
// Test private validateEmail() through public createUser()
assertThrows(ValidationException.class, () ->
userService.createUser("invalid-email", "password"));
}规则: 不要直接测试私有方法。通过公共方法测试它们。
❌ 不良示例:
java
@Test
void testPrivateMethod() {
// 使用反射测试私有方法 - 不良!
Method method = UserService.class.getDeclaredMethod("validateEmail", String.class);
method.setAccessible(true);
boolean result = (boolean) method.invoke(userService, "test@example.com");
assertTrue(result);
}✅ 良好示例:
java
@Test
void shouldRejectInvalidEmailDuringUserCreation() {
// 通过公共createUser()测试私有validateEmail()
assertThrows(ValidationException.class, () ->
userService.createUser("invalid-email", "password"));
}Test Coverage Goals
测试覆盖率目标
Recommended Coverage Targets:
- Critical business logic: 100%
- Service layer: 90-100%
- Controller layer: 80-90%
- Utility classes: 90-100%
- Overall project: 80% minimum
Tools:
- JaCoCo for coverage reporting
- SonarQube for code quality analysis
xml
<!-- Maven JaCoCo Plugin -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>coverage-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>推荐覆盖率目标:
- 关键业务逻辑:100%
- 服务层:90-100%
- 控制器层:80-90%
- 工具类:90-100%
- 整体项目:最低80%
工具:
- JaCoCo用于覆盖率报告
- SonarQube用于代码质量分析
xml
<!-- Maven JaCoCo插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>coverage-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>Code Organization
代码组织
Package Structure
包结构
✅ Layered Architecture:
com.example.myapp/
├── controller/ # REST controllers
│ ├── UserController.java
│ └── OrderController.java
├── service/ # Business logic
│ ├── UserService.java
│ └── OrderService.java
├── repository/ # Data access
│ ├── UserRepository.java
│ └── OrderRepository.java
├── model/ # Domain models
│ ├── User.java
│ └── Order.java
├── dto/ # Data transfer objects
│ ├── UserDto.java
│ └── OrderDto.java
├── exception/ # Custom exceptions
│ ├── UserNotFoundException.java
│ └── ValidationException.java
├── config/ # Configuration classes
│ ├── SecurityConfig.java
│ └── DatabaseConfig.java
└── util/ # Utility classes
├── DateUtils.java
└── StringUtils.java✅ Feature-Based Structure (for larger apps):
com.example.myapp/
├── user/
│ ├── User.java
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ └── UserDto.java
├── order/
│ ├── Order.java
│ ├── OrderController.java
│ ├── OrderService.java
│ ├── OrderRepository.java
│ └── OrderDto.java
└── common/
├── exception/
├── config/
└── util/✅ 分层架构:
com.example.myapp/
├── controller/ # REST控制器
│ ├── UserController.java
│ └── OrderController.java
├── service/ # 业务逻辑
│ ├── UserService.java
│ └── OrderService.java
├── repository/ # 数据访问
│ ├── UserRepository.java
│ └── OrderRepository.java
├── model/ # 领域模型
│ ├── User.java
│ └── Order.java
├── dto/ # 数据传输对象
│ ├── UserDto.java
│ └── OrderDto.java
├── exception/ # 自定义异常
│ ├── UserNotFoundException.java
│ └── ValidationException.java
├── config/ # 配置类
│ ├── SecurityConfig.java
│ └── DatabaseConfig.java
└── util/ # 工具类
├── DateUtils.java
└── StringUtils.java✅ 基于功能的结构(适用于大型应用):
com.example.myapp/
├── user/
│ ├── User.java
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ └── UserDto.java
├── order/
│ ├── Order.java
│ ├── OrderController.java
│ ├── OrderService.java
│ ├── OrderRepository.java
│ └── OrderDto.java
└── common/
├── exception/
├── config/
└── util/Class Naming Conventions
类命名约定
java
// Controllers: Noun + Controller
public class UserController { }
public class OrderController { }
// Services: Noun + Service
public class UserService { }
public class PaymentService { }
// Repositories: Noun + Repository
public class UserRepository { }
public class OrderRepository { }
// DTOs: Noun + Dto
public class UserDto { }
public class CreateOrderDto { }
// Exceptions: Description + Exception
public class UserNotFoundException extends RuntimeException { }
public class InvalidEmailException extends ValidationException { }
// Utilities: Plural noun + Utils
public class StringUtils { }
public class DateUtils { }
// Interfaces: Adjective or Noun
public interface Serializable { }
public interface PaymentProcessor { }java
// 控制器:名词 + Controller
public class UserController { }
public class OrderController { }
// 服务:名词 + Service
public class UserService { }
public class PaymentService { }
// 仓库:名词 + Repository
public class UserRepository { }
public class OrderRepository { }
// DTO:名词 + Dto
public class UserDto { }
public class CreateOrderDto { }
// 异常:描述 + Exception
public class UserNotFoundException extends RuntimeException { }
public class InvalidEmailException extends ValidationException { }
// 工具类:复数名词 + Utils
public class StringUtils { }
public class DateUtils { }
// 接口:形容词或名词
public interface Serializable { }
public interface PaymentProcessor { }Method Naming Conventions
方法命名约定
java
// Getters/Setters
public String getName() { }
public void setName(String name) { }
// Boolean getters: is/has/can
public boolean isActive() { }
public boolean hasPermission() { }
public boolean canExecute() { }
// Actions: verb + noun
public void createUser() { }
public void processPayment() { }
public void calculateTotal() { }
// Queries: get/find/search + noun
public User getUser(Long id) { }
public List<User> findActiveUsers() { }
public List<Order> searchByDate(LocalDate date) { }
// Validators: validate + noun
public void validateEmail(String email) { }
public boolean isValidPassword(String password) { }java
// Getter/Setter
public String getName() { }
public void setName(String name) { }
// 布尔Getter:is/has/can
public boolean isActive() { }
public boolean hasPermission() { }
public boolean canExecute() { }
// 动作:动词 + 名词
public void createUser() { }
public void processPayment() { }
public void calculateTotal() { }
// 查询:get/find/search + 名词
public User getUser(Long id) { }
public List<User> findActiveUsers() { }
public List<Order> searchByDate(LocalDate date) { }
// 验证器:validate + 名词
public void validateEmail(String email) { }
public boolean isValidPassword(String password) { }Constants and Configuration
常量和配置
✅ Constants:
java
public class ApplicationConstants {
// Public constants: public static final
public static final int MAX_RETRY_ATTEMPTS = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
public static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(30);
// Private constructor to prevent instantiation
private ApplicationConstants() {
throw new AssertionError("Cannot instantiate constants class");
}
}
// Or use enums for related constants
public enum HttpStatus {
OK(200, "OK"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}✅ 常量:
java
public class ApplicationConstants {
// 公共常量:public static final
public static final int MAX_RETRY_ATTEMPTS = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
public static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(30);
// 私有构造函数防止实例化
private ApplicationConstants() {
throw new AssertionError("Cannot instantiate constants class");
}
}
// 或对相关常量使用枚举
public enum HttpStatus {
OK(200, "OK"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}Builder Pattern
构建器模式
✅ Builder for Complex Objects:
java
public class User {
private final String email;
private final String firstName;
private final String lastName;
private final LocalDate dateOfBirth;
private final String phoneNumber;
private final Address address;
private User(Builder builder) {
this.email = builder.email;
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.dateOfBirth = builder.dateOfBirth;
this.phoneNumber = builder.phoneNumber;
this.address = builder.address;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String email;
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private String phoneNumber;
private Address address;
public Builder email(String email) {
this.email = email;
return this;
}
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public Builder dateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
return this;
}
public Builder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public Builder address(Address address) {
this.address = address;
return this;
}
public User build() {
validateRequired();
return new User(this);
}
private void validateRequired() {
if (email == null) throw new IllegalStateException("Email is required");
if (firstName == null) throw new IllegalStateException("First name is required");
}
}
// Getters
public String getEmail() { return email; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public LocalDate getDateOfBirth() { return dateOfBirth; }
public String getPhoneNumber() { return phoneNumber; }
public Address getAddress() { return address; }
}
// Usage
User user = User.builder()
.email("john@example.com")
.firstName("John")
.lastName("Doe")
.dateOfBirth(LocalDate.of(1990, 1, 1))
.phoneNumber("555-1234")
.build();✅ 用于复杂对象的构建器:
java
public class User {
private final String email;
private final String firstName;
private final String lastName;
private final LocalDate dateOfBirth;
private final String phoneNumber;
private final Address address;
private User(Builder builder) {
this.email = builder.email;
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.dateOfBirth = builder.dateOfBirth;
this.phoneNumber = builder.phoneNumber;
this.address = builder.address;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String email;
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private String phoneNumber;
private Address address;
public Builder email(String email) {
this.email = email;
return this;
}
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public Builder dateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
return this;
}
public Builder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public Builder address(Address address) {
this.address = address;
return this;
}
public User build() {
validateRequired();
return new User(this);
}
private void validateRequired() {
if (email == null) throw new IllegalStateException("Email is required");
if (firstName == null) throw new IllegalStateException("First name is required");
}
}
// Getter
public String getEmail() { return email; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public LocalDate getDateOfBirth() { return dateOfBirth; }
public String getPhoneNumber() { return phoneNumber; }
public Address getAddress() { return address; }
}
// 使用示例
User user = User.builder()
.email("john@example.com")
.firstName("John")
.lastName("Doe")
.dateOfBirth(LocalDate.of(1990, 1, 1))
.phoneNumber("555-1234")
.build();Factory Pattern
工厂模式
✅ Factory for Object Creation:
java
public interface PaymentProcessor {
void processPayment(double amount);
}
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment: $" + amount);
}
}
public class PayPalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
}
}
public class PaymentProcessorFactory {
public static PaymentProcessor create(PaymentMethod method) {
switch (method) {
case CREDIT_CARD:
return new CreditCardProcessor();
case PAYPAL:
return new PayPalProcessor();
case BITCOIN:
return new BitcoinProcessor();
default:
throw new IllegalArgumentException("Unknown payment method: " + method);
}
}
}
// Usage
PaymentProcessor processor = PaymentProcessorFactory.create(PaymentMethod.CREDIT_CARD);
processor.processPayment(100.0);✅ 用于对象创建的工厂:
java
public interface PaymentProcessor {
void processPayment(double amount);
}
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment: $" + amount);
}
}
public class PayPalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
}
}
public class PaymentProcessorFactory {
public static PaymentProcessor create(PaymentMethod method) {
switch (method) {
case CREDIT_CARD:
return new CreditCardProcessor();
case PAYPAL:
return new PayPalProcessor();
case BITCOIN:
return new BitcoinProcessor();
default:
throw new IllegalArgumentException("Unknown payment method: " + method);
}
}
}
// 使用示例
PaymentProcessor processor = PaymentProcessorFactory.create(PaymentMethod.CREDIT_CARD);
processor.processPayment(100.0);Performance
性能
String Optimization
字符串优化
✅ String Performance Tips:
java
// Use String.format() sparingly (it's slow)
// OK for occasional use
String message = String.format("User %s logged in at %s", username, timestamp);
// For frequent formatting, use StringBuilder
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append("Item ").append(i).append("\n");
}
String result = builder.toString();
// Use String.join() for collections
List<String> items = Arrays.asList("apple", "banana", "cherry");
String joined = String.join(", ", items);
// Avoid string concatenation in loops
String result = "";
for (String item : items) {
result += item; // BAD - creates many String objects
}
// Use StringBuilder instead
StringBuilder result = new StringBuilder();
for (String item : items) {
result.append(item);
}
// String interning for frequently used strings
String s1 = new String("hello").intern();
String s2 = "hello";
assert s1 == s2; // Same reference✅ 字符串性能技巧:
java
// 谨慎使用String.format()(速度慢)
// 偶尔使用没问题
String message = String.format("User %s logged in at %s", username, timestamp);
// 频繁格式化使用StringBuilder
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append("Item ").append(i).append("\n");
}
String result = builder.toString();
// 对集合使用String.join()
List<String> items = Arrays.asList("apple", "banana", "cherry");
String joined = String.join(", ", items);
// 避免在循环中进行字符串拼接
String result = "";
for (String item : items) {
result += item; // 不良 - 创建许多String对象
}
// 改用StringBuilder
StringBuilder result = new StringBuilder();
for (String item : items) {
result.append(item);
}
// 对频繁使用的字符串使用字符串驻留
String s1 = new String("hello").intern();
String s2 = "hello";
assert s1 == s2; // 相同引用Collection Performance
集合性能
java
// ArrayList vs LinkedList
List<String> arrayList = new ArrayList<>(); // Fast random access O(1)
List<String> linkedList = new LinkedList<>(); // Fast insertion/deletion at ends O(1)
// HashSet vs TreeSet
Set<String> hashSet = new HashSet<>(); // O(1) add/contains, no order
Set<String> treeSet = new TreeSet<>(); // O(log n) add/contains, sorted
// HashMap vs TreeMap vs LinkedHashMap
Map<String, Integer> hashMap = new HashMap<>(); // O(1) get/put, no order
Map<String, Integer> treeMap = new TreeMap<>(); // O(log n) get/put, sorted by key
Map<String, Integer> linkedHashMap = new LinkedHashMap<>(); // O(1) get/put, insertion order
// Pre-size collections when size is known
List<String> list = new ArrayList<>(1000); // Avoids resizing
Map<String, String> map = new HashMap<>(1000);
// Use EnumSet for enum values
Set<DayOfWeek> weekdays = EnumSet.of(
DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY, DayOfWeek.FRIDAY
);java
// ArrayList vs LinkedList
List<String> arrayList = new ArrayList<>(); // 快速随机访问 O(1)
List<String> linkedList = new LinkedList<>(); // 快速在两端插入/删除 O(1)
// HashSet vs TreeSet
Set<String> hashSet = new HashSet<>(); // O(1) 添加/包含,无顺序
Set<String> treeSet = new TreeSet<>(); // O(log n) 添加/包含,有序
// HashMap vs TreeMap vs LinkedHashMap
Map<String, Integer> hashMap = new HashMap<>(); // O(1) 获取/放入,无顺序
Map<String, Integer> treeMap = new TreeMap<>(); // O(log n) 获取/放入,按键排序
Map<String, Integer> linkedHashMap = new LinkedHashMap<>(); // O(1) 获取/放入,按插入顺序
// 已知大小时预分配集合
List<String> list = new ArrayList<>(1000); // 避免调整大小
Map<String, String> map = new HashMap<>(1000);
// 对枚举值使用EnumSet
Set<DayOfWeek> weekdays = EnumSet.of(
DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY, DayOfWeek.FRIDAY
);Stream vs Loop Performance
Stream vs 循环性能
Rule: Streams are readable but may have overhead for small collections. Measure performance for critical paths.
java
// For small collections (<100 items), loops may be faster
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Loop (slightly faster for small collections)
int sum = 0;
for (int num : numbers) {
sum += num;
}
// Stream (more readable, negligible overhead)
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
// For large collections or parallel processing, streams shine
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// Parallel stream for CPU-intensive operations
int sum = largeList.parallelStream()
.mapToInt(Integer::intValue)
.sum();规则: Stream更具可读性,但对小型集合可能有开销。对关键路径测量性能。
java
// 小型集合(<100个元素),循环可能更快
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 循环(对小型集合略快)
int sum = 0;
for (int num : numbers) {
sum += num;
}
// Stream(更具可读性,开销可忽略)
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
// 大型集合或并行处理,Stream表现出色
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// 并行流用于CPU密集型操作
int sum = largeList.parallelStream()
.mapToInt(Integer::intValue)
.sum();Memory Management
内存管理
✅ Memory Best Practices:
java
// Close resources to free memory
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// Use streams
} // Automatically closed
// Avoid memory leaks with listeners
public class EventSource {
private final List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void removeListener(EventListener listener) {
listeners.remove(listener); // Important: allow garbage collection
}
}
// Use weak references for caches
Map<String, WeakReference<User>> cache = new WeakHashMap<>();
// Clear collections when done
List<LargeObject> objects = new ArrayList<>();
// ... use objects
objects.clear(); // Allow garbage collection
// Avoid string concatenation creating many objects
String result = "Hello" + " " + "World"; // OK - compiler optimizes
String result = str1 + str2 + str3; // OK - compiler uses StringBuilder
// Not OK in loops - use StringBuilder explicitly
for (String item : items) {
result = result + item; // Creates new String each iteration
}✅ 内存最佳实践:
java
// 关闭资源以释放内存
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 使用流
} // 自动关闭
// 避免监听器导致的内存泄漏
public class EventSource {
private final List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void removeListener(EventListener listener) {
listeners.remove(listener); // 重要:允许垃圾回收
}
}
// 对缓存使用弱引用
Map<String, WeakReference<User>> cache = new WeakHashMap<>();
// 使用完后清空集合
List<LargeObject> objects = new ArrayList<>();
// ... 使用objects
objects.clear(); // 允许垃圾回收
// 避免字符串拼接创建许多对象
String result = "Hello" + " " + "World"; // 没问题 - 编译器优化
String result = str1 + str2 + str3; // 没问题 - 编译器使用StringBuilder
// 循环中不行 - 显式使用StringBuilder
for (String item : items) {
result = result + item; // 每次迭代创建新String
}Common Anti-Patterns
常见反模式
God Objects
上帝对象
❌ Anti-Pattern - God Object:
java
// One class doing everything - AVOID!
public class OrderManager {
// User management
public void createUser() { }
public void deleteUser() { }
// Order processing
public void createOrder() { }
public void processOrder() { }
// Payment processing
public void processPayment() { }
// Email sending
public void sendConfirmation() { }
// Reporting
public void generateReport() { }
// Database operations
public void saveToDatabase() { }
// Logging
public void logActivity() { }
// 50+ more methods...
}✅ Solution - Single Responsibility:
java
public class UserService {
public void createUser() { }
public void deleteUser() { }
}
public class OrderService {
public void createOrder() { }
public void processOrder() { }
}
public class PaymentService {
public void processPayment() { }
}
public class EmailService {
public void sendConfirmation() { }
}❌ 反模式 - 上帝对象:
java
// 一个类做所有事情 - 避免!
public class OrderManager {
// 用户管理
public void createUser() { }
public void deleteUser() { }
// 订单处理
public void createOrder() { }
public void processOrder() { }
// 支付处理
public void processPayment() { }
// 邮件发送
public void sendConfirmation() { }
// 报告
public void generateReport() { }
// 数据库操作
public void saveToDatabase() { }
// 日志
public void logActivity() { }
// 50多个其他方法...
}✅ 解决方案 - 单一职责:
java
public class UserService {
public void createUser() { }
public void deleteUser() { }
}
public class OrderService {
public void createOrder() { }
public void processOrder() { }
}
public class PaymentService {
public void processPayment() { }
}
public class EmailService {
public void sendConfirmation() { }
}Primitive Obsession
原始类型痴迷
❌ Anti-Pattern - Primitive Obsession:
java
public class Order {
private double price; // What currency?
private double weight; // What unit?
private String phoneNumber; // No validation!
private String email; // No validation!
}✅ Solution - Value Objects:
java
public class Money {
private final double amount;
private final Currency currency;
public Money(double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
// Methods for money operations
}
public class Weight {
private final double value;
private final WeightUnit unit;
public Weight(double value, WeightUnit unit) {
this.value = value;
this.unit = unit;
}
}
public class Email {
private final String value;
public Email(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("Invalid email");
}
this.value = value;
}
private boolean isValid(String email) {
return email.contains("@"); // Simplified
}
}
public class Order {
private Money price;
private Weight weight;
private Email customerEmail;
}❌ 反模式 - 原始类型痴迷:
java
public class Order {
private double price; // 什么货币?
private double weight; // 什么单位?
private String phoneNumber; // 无验证!
private String email; // 无验证!
}✅ 解决方案 - 值对象:
java
public class Money {
private final double amount;
private final String currency;
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// 货币操作方法
}
public class Weight {
private final double value;
private final WeightUnit unit;
public Weight(double value, WeightUnit unit) {
this.value = value;
this.unit = unit;
}
}
public class Email {
private final String value;
public Email(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("Invalid email");
}
this.value = value;
}
private boolean isValid(String email) {
return email.contains("@"); // 简化
}
}
public class Order {
private Money price;
private Weight weight;
private Email customerEmail;
}Long Parameter Lists
长参数列表
❌ Anti-Pattern - Long Parameter Lists:
java
public void createUser(
String firstName,
String lastName,
String email,
String phoneNumber,
String addressLine1,
String addressLine2,
String city,
String state,
String zipCode,
String country
) {
// Too many parameters!
}✅ Solution - Parameter Object:
java
public class UserRegistration {
private final String firstName;
private final String lastName;
private final String email;
private final String phoneNumber;
private final Address address;
// Constructor, getters, builder
}
public class Address {
private final String line1;
private final String line2;
private final String city;
private final String state;
private final String zipCode;
private final String country;
// Constructor, getters, builder
}
public void createUser(UserRegistration registration) {
// Clean method signature
}❌ 反模式 - 长参数列表:
java
public void createUser(
String firstName,
String lastName,
String email,
String phoneNumber,
String addressLine1,
String addressLine2,
String city,
String state,
String zipCode,
String country
) {
// 参数太多!
}✅ 解决方案 - 参数对象:
java
public class UserRegistration {
private final String firstName;
private final String lastName;
private final String email;
private final String phoneNumber;
private final Address address;
// 构造函数、getter、构建器
}
public class Address {
private final String line1;
private final String line2;
private final String city;
private final String state;
private final String zipCode;
private final String country;
// 构造函数、getter、构建器
}
public void createUser(UserRegistration registration) {
// 简洁的方法签名
}Magic Numbers
魔法数字
❌ Anti-Pattern - Magic Numbers:
java
public void processOrder(Order order) {
if (order.getTotal() > 100) { // What is 100?
applyDiscount(order, 0.1); // What is 0.1?
}
if (order.getItems().size() > 5) { // What is 5?
// Free shipping
}
}✅ Solution - Named Constants:
java
private static final double FREE_SHIPPING_THRESHOLD = 100.0;
private static final double BULK_DISCOUNT_RATE = 0.1;
private static final int BULK_ORDER_ITEM_COUNT = 5;
public void processOrder(Order order) {
if (order.getTotal() > FREE_SHIPPING_THRESHOLD) {
applyDiscount(order, BULK_DISCOUNT_RATE);
}
if (order.getItems().size() > BULK_ORDER_ITEM_COUNT) {
// Free shipping
}
}❌ 反模式 - 魔法数字:
java
public void processOrder(Order order) {
if (order.getTotal() > 100) { // 100是什么?
applyDiscount(order, 0.1); // 0.1是什么?
}
if (order.getItems().size() > 5) { // 5是什么?
// 免运费
}
}✅ 解决方案 - 命名常量:
java
private static final double FREE_SHIPPING_THRESHOLD = 100.0;
private static final double BULK_DISCOUNT_RATE = 0.1;
private static final int BULK_ORDER_ITEM_COUNT = 5;
public void processOrder(Order order) {
if (order.getTotal() > FREE_SHIPPING_THRESHOLD) {
applyDiscount(order, BULK_DISCOUNT_RATE);
}
if (order.getItems().size() > BULK_ORDER_ITEM_COUNT) {
// 免运费
}
}Nested Conditionals
嵌套条件
❌ Anti-Pattern - Deeply Nested Conditionals:
java
public void processPayment(Order order) {
if (order != null) {
if (order.getCustomer() != null) {
if (order.getCustomer().hasPaymentMethod()) {
if (order.getTotal() > 0) {
if (inventory.isAvailable(order)) {
// Process payment
} else {
throw new InsufficientInventoryException();
}
} else {
throw new InvalidAmountException();
}
} else {
throw new NoPaymentMethodException();
}
} else {
throw new NoCustomerException();
}
} else {
throw new NullOrderException();
}
}✅ Solution - Guard Clauses:
java
public void processPayment(Order order) {
// Guard clauses - fail fast
if (order == null) {
throw new NullOrderException();
}
if (order.getCustomer() == null) {
throw new NoCustomerException();
}
if (!order.getCustomer().hasPaymentMethod()) {
throw new NoPaymentMethodException();
}
if (order.getTotal() <= 0) {
throw new InvalidAmountException();
}
if (!inventory.isAvailable(order)) {
throw new InsufficientInventoryException();
}
// Happy path at the end
processPaymentInternal(order);
}❌ 反模式 - 深度嵌套条件:
java
public void processPayment(Order order) {
if (order != null) {
if (order.getCustomer() != null) {
if (order.getCustomer().hasPaymentMethod()) {
if (order.getTotal() > 0) {
if (inventory.isAvailable(order)) {
// 处理支付
} else {
throw new InsufficientInventoryException();
}
} else {
throw new InvalidAmountException();
}
} else {
throw new NoPaymentMethodException();
}
} else {
throw new NoCustomerException();
}
} else {
throw new NullOrderException();
}
}✅ 解决方案 - 守卫子句:
java
public void processPayment(Order order) {
// 守卫子句 - 快速失败
if (order == null) {
throw new NullOrderException();
}
if (order.getCustomer() == null) {
throw new NoCustomerException();
}
if (!order.getCustomer().hasPaymentMethod()) {
throw new NoPaymentMethodException();
}
if (order.getTotal() <= 0) {
throw new InvalidAmountException();
}
if (!inventory.isAvailable(order)) {
throw new InsufficientInventoryException();
}
// 最后是成功路径
processPaymentInternal(order);
}Returning Null
返回Null
❌ Anti-Pattern - Returning Null:
java
public User findUser(Long id) {
// May return null
return database.find(id);
}
// Caller must remember null check
User user = findUser(123L);
if (user != null) {
String email = user.getEmail(); // NullPointerException risk
}✅ Solution - Return Optional:
java
public Optional<User> findUser(Long id) {
return Optional.ofNullable(database.find(id));
}
// Caller forced to handle absence
Optional<User> userOpt = findUser(123L);
String email = userOpt
.map(User::getEmail)
.orElse("unknown@example.com");❌ 反模式 - 返回Null:
java
public User findUser(Long id) {
// 可能返回null
return database.find(id);
}
// 调用者必须记得检查null
User user = findUser(123L);
if (user != null) {
String email = user.getEmail(); // 有NullPointerException风险
}✅ 解决方案 - 返回Optional:
java
public Optional<User> findUser(Long id) {
return Optional.ofNullable(database.find(id));
}
// 调用者必须处理不存在的情况
Optional<User> userOpt = findUser(123L);
String email = userOpt
.map(User::getEmail)
.orElse("unknown@example.com");Not Closing Resources
不关闭资源
❌ Anti-Pattern - Resource Leaks:
java
public String readFile(String path) {
FileReader reader = new FileReader(path);
BufferedReader br = new BufferedReader(reader);
return br.readLine(); // Resources never closed!
}✅ Solution - Try-With-Resources:
java
public String readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
} // Automatically closed
}❌ 反模式 - 资源泄漏:
java
public String readFile(String path) {
FileReader reader = new FileReader(path);
BufferedReader br = new BufferedReader(reader);
return br.readLine(); // 资源永远不会关闭!
}✅ 解决方案 - Try-With-Resources:
java
public String readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
} // 自动关闭
}Checklist
检查清单
Use this checklist during code reviews to verify Java code quality:
在代码审查期间使用此检查清单验证Java代码质量:
SOLID Principles
SOLID原则
- Each class has a single, well-defined responsibility
- Classes are open for extension, closed for modification
- Subclasses can be substituted for base classes
- Interfaces are small and focused
- Dependencies are inverted (depend on abstractions)
- 每个类有单一、明确的职责
- 类对扩展开放,对修改关闭
- 子类可以替换基类
- 接口小巧且聚焦
- 依赖倒置(依赖抽象)
DRY Principle
DRY原则
- No obvious code duplication
- Common logic extracted to utility methods/classes
- Generics used where appropriate
- 无明显代码重复
- 公共逻辑提取到工具方法/类
- 适当地使用泛型
Clean Code
简洁代码
- Class, method, and variable names are meaningful
- Methods are small (5-20 lines ideally)
- Comments explain WHY, not WHAT
- Proper exception handling (no swallowed exceptions)
- Code is properly organized
- 类、方法和变量名称有意义
- 方法小巧(理想情况下5-20行)
- 注释解释原因,而非内容
- 正确的异常处理(无吞掉的异常)
- 代码组织合理
Java-Specific
Java特定
- Optional used instead of null for absent values
- Composition preferred over inheritance
- Immutability used where appropriate (final fields/classes)
- Stream API used for collection operations
- Lambda expressions and method references used
- Try-with-resources used for AutoCloseable resources
- StringBuilder used for string concatenation in loops
- Enums used for fixed sets of constants
- 使用Optional而非null表示不存在的值
- 优先使用组合而非继承
- 适当地使用不可变性(final字段/类)
- 对集合操作使用Stream API
- 使用Lambda表达式和方法引用
- 对AutoCloseable资源使用try-with-resources
- 循环中字符串拼接使用StringBuilder
- 对固定常量集使用枚举
Exception Handling
异常处理
- Appropriate exception types (checked vs unchecked)
- Exceptions are caught only when they can be handled
- Custom exceptions are well-designed
- Exceptions are logged with context
- No empty catch blocks
- 使用适当的异常类型(受检vs非受检)
- 仅在可以处理时捕获异常
- 自定义异常设计良好
- 异常带上下文记录
- 无空catch块
Collections
集合
- Appropriate collection type chosen
- Immutable collections used where appropriate
- Generic type parameters specified
- Diamond operator used (Java 7+)
- 选择了适当的集合类型
- 适当地使用不可变集合
- 指定了泛型类型参数
- 使用了菱形运算符(Java 7+)
Concurrency
并发
- Thread safety considered
- Appropriate synchronization mechanisms used
- ExecutorService used for thread pools
- CompletableFuture used for async operations
- Deadlock prevention considered
- Atomic classes used for simple atomic operations
- 考虑了线程安全
- 使用了适当的同步机制
- 对线程池使用ExecutorService
- 对异步操作使用CompletableFuture
- 考虑了死锁预防
- 对简单原子操作使用原子类
Testing
测试
- Unit tests exist for business logic
- Tests follow AAA pattern
- Mocking used appropriately
- Tests are isolated and independent
- Test coverage meets standards (80%+)
- 业务逻辑有单元测试
- 测试遵循AAA模式
- 适当地使用模拟
- 测试独立且隔离
- 测试覆盖率符合标准(80%+)
Code Organization
代码组织
- Proper package structure
- Naming conventions followed
- Constants properly defined
- Builder/Factory patterns used appropriately
- 适当的包结构
- 遵循命名约定
- 常量定义正确
- 适当地使用构建器/工厂模式
Performance
性能
- String operations optimized
- Appropriate collection types for use case
- Resources properly managed
- No obvious performance issues
- 字符串操作已优化
- 为用例选择了适当的集合类型
- 资源管理正确
- 无明显性能问题
Anti-Patterns
反模式
- No God objects
- No primitive obsession
- No long parameter lists
- No magic numbers
- No deeply nested conditionals
- No null returns where Optional is appropriate
- No resource leaks
- 无上帝对象
- 无原始类型痴迷
- 无长参数列表
- 无魔法数字
- 无深度嵌套条件
- 无在应使用Optional时返回null的情况
- 无资源泄漏
Related Skills
相关技能
- uncle-duke-java: Java code review agent that uses this skill as reference
- uncle-duke-java:将本技能作为参考的Java代码审查代理
References
参考资料
Java Documentation
Java文档
Spring Framework
Spring Framework
Best Practices
最佳实践
Testing
测试
Tools
工具
Version: 1.0
Last Updated: 2025-12-24
Maintainer: Development Team