java-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJava Best Practices by VirtusLab
VirtusLab 出品的Java最佳实践
Opinionated, modern Java skill. Targets Java 21+ and explicitly cuts off outdated patterns. When writing or reviewing Java code, follow these guidelines strictly.
带有主观倾向的现代Java规范,面向Java 21+版本,明确摒弃过时的编程模式。编写或评审Java代码时,请严格遵循这些指南。
Core Philosophy
核心理念
- Composed Method Pattern is the single most impactful practice in Java. Every method should do one thing, at one level of abstraction, and be short enough to read in one glance. This pattern alone transforms unreadable code into clean code.
- Effective Java by Joshua Bloch remains the authoritative reference. Its principles are still current. When in doubt, consult it.
- Use library code. Do not reimplement what the standard library or a well-established library already provides. This is one of the most violated and most impactful principles.
- Composition over inheritance. Always. Use interfaces with default methods and delegation instead of deep class hierarchies.
- GRASP principles (General Responsibility Assignment Software Patterns) guide object-oriented design: Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations.
- Composed Method Pattern是Java中影响力最大的实践,每个方法都应该只做一件事、处于同一抽象层级,并且足够简短,一眼就能读完。仅这一条模式就能将不可读的代码转化为整洁代码。
- Joshua Bloch所著的**《Effective Java》**仍然是权威参考,其原则至今仍然适用,有疑问时请查阅该书。
- 使用库代码,不要重复实现标准库或成熟第三方库已经提供的能力,这是最常被违反、同时影响最大的原则之一。
- 组合优先于继承,始终如此。使用带默认方法的接口和委托来替代深层类继承体系。
- GRASP原则(通用职责分配软件模式)指导面向对象设计:信息专家、创建者、控制器、低耦合、高内聚、多态、纯虚构、间接性、受保护变化。
Modern Java (21+) — Use These Features
现代Java(21+)——请使用这些特性
Use modern Java features aggressively. Do not write pre-Java 17 style code.
积极使用现代Java特性,不要编写Java 17之前风格的代码。
Records for Data Objects
数据对象使用Records
Use for all immutable data carriers. Do NOT use Lombok , , , or hand-written POJOs for data objects.
record@Value@Data@Getterjava
// WRONG — legacy style
@Value
public class User {
String username;
String email;
}
// WRONG — hand-written boilerplate
public class User {
private final String username;
private final String email;
// constructor, getters, equals, hashCode, toString...
}
// CORRECT
public record User(String username, String email) {}Lombok is acceptable only for on non-record classes where the builder pattern is genuinely needed. For everything else, use records or plain Java.
@Builder所有不可变数据载体都使用实现,不要对数据对象使用Lombok的、、或者手写POJO。
record@Value@Data@Getterjava
// WRONG — 旧风格
@Value
public class User {
String username;
String email;
}
// WRONG — 手写样板代码
public class User {
private final String username;
private final String email;
// constructor, getters, equals, hashCode, toString...
}
// CORRECT
public record User(String username, String email) {}Lombok仅可在非record类确实需要构建器模式时,用于添加注解。其他所有场景都使用records或者原生Java能力即可。
@BuilderSealed Classes for Domain Modeling (ADTs)
领域建模使用密封类(ADTs)
Use classes and interfaces to model algebraic data types. This gives exhaustiveness checking in expressions.
sealedswitchjava
public sealed interface PaymentResult
permits PaymentSuccess, PaymentFailure, PaymentPending {
}
public record PaymentSuccess(String transactionId, Money amount) implements PaymentResult {}
public record PaymentFailure(String reason, ErrorCode code) implements PaymentResult {}
public record PaymentPending(String transactionId) implements PaymentResult {}使用类和接口来建模代数数据类型,这可以让表达式支持穷尽性检查。
sealedswitchjava
public sealed interface PaymentResult
permits PaymentSuccess, PaymentFailure, PaymentPending {
}
public record PaymentSuccess(String transactionId, Money amount) implements PaymentResult {}
public record PaymentFailure(String reason, ErrorCode code) implements PaymentResult {}
public record PaymentPending(String transactionId) implements PaymentResult {}Switch Expressions with Pattern Matching
带模式匹配的Switch表达式
Use switch expressions (not statements) with pattern matching. Prefer exhaustive switches.
java
// CORRECT
String message = switch (result) {
case PaymentSuccess success ->
"Paid %s".formatted(success.amount());
case PaymentFailure failure ->
"Failed: %s".formatted(failure.reason());
case PaymentPending pending ->
"Pending: %s".formatted(pending.transactionId());
};
// WRONG — old-style switch statement with break
switch (result.getType()) {
case SUCCESS:
message = "Paid";
break;
// ...
}使用带模式匹配的switch表达式(而非语句),优先使用穷尽式switch。
java
// CORRECT
String message = switch (result) {
case PaymentSuccess success ->
"Paid %s".formatted(success.amount());
case PaymentFailure failure ->
"Failed: %s".formatted(failure.reason());
case PaymentPending pending ->
"Pending: %s".formatted(pending.transactionId());
};
// WRONG — 带break的旧风格switch语句
switch (result.getType()) {
case SUCCESS:
message = "Paid";
break;
// ...
}Null Handling Inside switch
(Java 21+)
switch在switch
内部处理Null(Java 21+)
switchjava
// CORRECT — null is an explicit case
return switch (status) {
case null -> "unknown";
case ACTIVE -> "active";
case PAUSED -> "paused";
};
// WRONG — null handled outside the switch
if (status == null) return "unknown";
return switch (status) {
case ACTIVE -> "active";
case PAUSED -> "paused";
};java
// CORRECT — null作为显式case处理
return switch (status) {
case null -> "unknown";
case ACTIVE -> "active";
case PAUSED -> "paused";
};
// WRONG — 在switch外部处理null
if (status == null) return "unknown";
return switch (status) {
case ACTIVE -> "active";
case PAUSED -> "paused";
};Pattern Matching for instanceof
instanceofinstanceof
的模式匹配
instanceofjava
// CORRECT
if (obj instanceof String s && !s.isBlank()) {
process(s);
}
// WRONG
if (obj instanceof String) {
String s = (String) obj;
process(s);
}java
// CORRECT
if (obj instanceof String s && !s.isBlank()) {
process(s);
}
// WRONG
if (obj instanceof String) {
String s = (String) obj;
process(s);
}Text Blocks
文本块
Use text blocks for multi-line strings (SQL, JSON, HTML, etc.).
java
String query = """
SELECT u.id, u.name
FROM users u
WHERE u.active = true
ORDER BY u.name
""";多行字符串(SQL、JSON、HTML等)使用文本块实现。
java
String query = """
SELECT u.id, u.name
FROM users u
WHERE u.active = true
ORDER BY u.name
""";var
for Local Variables
var局部变量使用var
varUse for local variables when the type is obvious from the right-hand side. Do not use when it reduces readability.
varvarjava
// CORRECT — type is obvious
var users = userRepository.findAll();
var mapper = new ObjectMapper();
// WRONG — type is not obvious, use explicit type
var result = process(data);当变量类型从右侧赋值可以明确推断时,局部变量使用声明,如果会降低可读性则不要使用。
varvarjava
// CORRECT — 类型明确
var users = userRepository.findAll();
var mapper = new ObjectMapper();
// WRONG — 类型不明确,使用显式类型
var result = process(data);Record Patterns (Java 21)
Record模式(Java 21)
Destructure records directly in and expressions.
instanceofswitchjava
// CORRECT
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
String desc = switch (shape) {
case Circle(double r) -> "Circle r=" + r;
case Rect(double w, double h) -> "Rect " + w + "x" + h;
};
// WRONG — old style: cast + field access
if (obj instanceof Point p) {
System.out.println(p.x() + ", " + p.y());
}在和表达式中直接解构record。
instanceofswitchjava
// CORRECT
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
String desc = switch (shape) {
case Circle(double r) -> "Circle r=" + r;
case Rect(double w, double h) -> "Rect " + w + "x" + h;
};
// WRONG — 旧风格:强制类型转换 + 字段访问
if (obj instanceof Point p) {
System.out.println(p.x() + ", " + p.y());
}Unnamed Variables (Java 22)
未命名变量(Java 22)
Use for unused variables, catch parameters, and lambda parameters.
_java
// CORRECT
try { ... } catch (Exception _) { ... }
try (var _ = ScopedValue.where(KEY, value)) { ... }
list.stream().map(_ -> "constant").toList();
// WRONG
try { ... } catch (Exception ignored) { ... }
try { ... } catch (Exception e) { ... } // when e is never used未使用的变量、catch参数、lambda参数使用声明。
_java
// CORRECT
try { ... } catch (Exception _) { ... }
try (var _ = ScopedValue.where(KEY, value)) { ... }
list.stream().map(_ -> "constant").toList();
// WRONG
try { ... } catch (Exception ignored) { ... }
try { ... } catch (Exception e) { ... } // 当e从未被使用时Immutable Collections
不可变集合
Use factory methods for immutable collections. Never use or for fixed collections.
Arrays.asList()new ArrayList<>()java
// CORRECT
var roles = List.of("ADMIN", "USER");
var config = Map.of("timeout", 30, "retries", 3);
var tags = Set.of("java", "backend");
// WRONG
var roles = Arrays.asList("ADMIN", "USER");
var roles = new ArrayList<>(List.of("ADMIN", "USER"));
var roles = Collections.unmodifiableList(Arrays.asList("ADMIN", "USER"));使用工厂方法创建不可变集合,固定集合永远不要使用或者。
Arrays.asList()new ArrayList<>()java
// CORRECT
var roles = List.of("ADMIN", "USER");
var config = Map.of("timeout", 30, "retries", 3);
var tags = Set.of("java", "backend");
// WRONG
var roles = Arrays.asList("ADMIN", "USER");
var roles = new ArrayList<>(List.of("ADMIN", "USER"));
var roles = Collections.unmodifiableList(Arrays.asList("ADMIN", "USER"));Sequenced Collections (Java 21)
有序集合(Java 21)
Use , , instead of index arithmetic.
getFirst()getLast()reversed()java
// CORRECT
var first = list.getFirst();
var last = list.getLast();
var rev = list.reversed();
// WRONG
var first = list.get(0);
var last = list.get(list.size() - 1);
var rev = new ArrayList<>(list); Collections.reverse(rev);使用、、替代索引运算。
getFirst()getLast()reversed()java
// CORRECT
var first = list.getFirst();
var last = list.getLast();
var rev = list.reversed();
// WRONG
var first = list.get(0);
var last = list.get(list.size() - 1);
var rev = new ArrayList<>(list); Collections.reverse(rev);String Utilities (Java 11+)
字符串工具(Java 11+)
Use modern methods. Do not reimplement what the standard library already provides.
Stringjava
// CORRECT
str.isBlank() // replaces str.trim().isEmpty()
str.strip() // Unicode-aware trim
"ha".repeat(3) // "hahaha"
"Hello %s".formatted(name) // replaces String.format(...)
str.lines() // Stream<String> of lines
// WRONG
str.trim().isEmpty()
String.format("Hello %s", name)使用现代方法,不要重复实现标准库已经提供的能力。
Stringjava
// CORRECT
str.isBlank() // 替代str.trim().isEmpty()
str.strip() // 支持Unicode的trim
"ha".repeat(3) // 结果为"hahaha"
"Hello %s".formatted(name) // 替代String.format(...)
str.lines() // 按行分割得到Stream<String>
// WRONG
str.trim().isEmpty()
String.format("Hello %s", name)Null Safety
空安全
Nulls are not acceptable inside the system. Things are non-null by default. Do not let nulls propagate beyond system boundaries (external APIs, database results, user input).
- Do NOT annotate everything with — that pollutes the code. Instead, treat everything as non-null by default.
@NonNull - Use only when something genuinely can be null (rare, at system edges).
@Nullable - If you receive null from an external source, convert it at the boundary: to an , a default value, or throw early.
Optional - Never pass as a method argument. Never return
nullfrom a method. Usenullfor methods that may have no result.Optional
java
// CORRECT — Optional for methods that may return nothing
public Optional<User> findByEmail(String email) {
return Optional.ofNullable(repository.get(email));
}
// WRONG — returning null
public User findByEmail(String email) {
return repository.get(email); // might return null
}系统内部不允许出现Null,默认所有对象都是非空的,不要让Null传播到系统边界之外(外部API、数据库返回结果、用户输入)。
- 不要给所有对象加注解 —— 这会污染代码,默认所有对象都是非空的即可。
@NonNull - 仅当对象确实可能为Null时(少见,仅在系统边界)使用注解。
@Nullable - 如果你从外部来源收到Null,在边界处就做转换:转为、默认值,或者提前抛出异常。
Optional - 永远不要将作为方法参数传递,永远不要从方法返回
null,可能返回空结果的方法使用null。Optional
java
// CORRECT — 可能返回空结果的方法返回Optional
public Optional<User> findByEmail(String email) {
return Optional.ofNullable(repository.get(email));
}
// WRONG — 返回null
public User findByEmail(String email) {
return repository.get(email); // 可能返回null
}Optional
Optional
- Use as return type for public methods that may have no result. It communicates intent: "this method might return nothing, and you must handle it."
Optional - Never use as a field type, method parameter, or in collections.
Optional - Never call without checking. Use
Optional.get(),orElseThrow(),orElse(),map(),flatMap().ifPresent()
java
// CORRECT
userService.findByEmail(email)
.map(User::username)
.orElseThrow(() -> new UserNotFoundException(email));
// WRONG
User user = userService.findByEmail(email).get();- 可能返回空结果的公共方法使用作为返回类型,它传递了明确的意图:"该方法可能没有返回结果,你必须处理这种情况"。
Optional - 永远不要将用作字段类型、方法参数,或者放在集合中。
Optional - 永远不要不做检查就调用,请使用
Optional.get()、orElseThrow()、orElse()、map()、flatMap()。ifPresent()
java
// CORRECT
userService.findByEmail(email)
.map(User::username)
.orElseThrow(() -> new UserNotFoundException(email));
// WRONG
User user = userService.findByEmail(email).get();Error Handling
错误处理
Use Java's exception mechanism. Do not try to turn Java into Go or C with error return codes or wrapper types. Java has checked and unchecked exceptions — use them.
Result- Use unchecked exceptions (extending ) for programming errors and unexpected failures.
RuntimeException - Use checked exceptions sparingly, only when the caller genuinely must handle the failure and there is a reasonable recovery path.
- Never swallow exceptions. Always log or rethrow.
- Never catch or
Exceptiongenerically unless at the top-level error boundary (e.g., controller advice, global handler).Throwable - Use specific exception types. Create custom domain exceptions when the standard ones do not fit.
- Use multi-catch to avoid duplicated catch blocks.
java
// CORRECT
catch (IOException | SQLException e) {
log.error("Data access failed", e);
}
// WRONG — duplicated handlers
catch (IOException e) { log.error("...", e); }
catch (SQLException e) { log.error("...", e); }java
// CORRECT
public User getUser(UserId id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
// WRONG — returning null on error
public User getUser(UserId id) {
try {
return userRepository.findById(id).orElse(null);
} catch (Exception e) {
return null;
}
}使用Java的异常机制,不要尝试用错误返回码或者包装类型把Java改成Go或者C风格,Java有受检异常和非受检异常,请合理使用。
Result- 编程错误和非预期故障使用非受检异常(继承)。
RuntimeException - 谨慎使用受检异常,仅当调用方确实必须处理故障且有合理的恢复路径时才使用。
- 永远不要吞掉异常,始终要打印日志或者重新抛出。
- 永远不要泛型捕获或者
Exception,除非是在顶层错误边界(比如控制器通知、全局异常处理器)。Throwable - 使用具体的异常类型,当标准异常不适用时创建自定义领域异常。
- 使用多重捕获避免重复的catch块。
java
// CORRECT
catch (IOException | SQLException e) {
log.error("Data access failed", e);
}
// WRONG — 重复的处理器
catch (IOException e) { log.error("...", e); }
catch (SQLException e) { log.error("...", e); }java
// CORRECT
public User getUser(UserId id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
// WRONG — 出错时返回null
public User getUser(UserId id) {
try {
return userRepository.findById(id).orElse(null);
} catch (Exception e) {
return null;
}
}Naming Conventions
命名规范
- Classes: — nouns (
PascalCase,UserService).PaymentProcessor - Methods: — verbs (
camelCase,findByEmail,processPayment).calculateTotal - Variables: — nouns (
camelCase,userId).orderItems - Constants: (
UPPER_SNAKE_CASE,MAX_RETRY_COUNT).DEFAULT_TIMEOUT - Packages: lowercase, dot-separated ().
com.company.project.domain.user - Booleans: prefix with ,
is,has,should(can,isValid(),hasPermission()).shouldRetry() - Collections: plural names (,
users,orders).products - No abbreviations in public APIs. not
userRepository.userReponottransactionManager.txMgr - Methods should not have surprising side effects. The name must accurately describe what the method does.
- 类:,使用名词(
PascalCase、UserService)。PaymentProcessor - 方法:,使用动词(
camelCase、findByEmail、processPayment)。calculateTotal - 变量:,使用名词(
camelCase、userId)。orderItems - 常量:(
UPPER_SNAKE_CASE、MAX_RETRY_COUNT)。DEFAULT_TIMEOUT - 包名:全小写,用点分隔()。
com.company.project.domain.user - 布尔值:以、
is、has、should为前缀(can、isValid()、hasPermission())。shouldRetry() - 集合:使用复数名称(、
users、orders)。products - 公共API中不要使用缩写,用而非
userRepository,用userRepo而非transactionManager。txMgr - 方法不要有意外的副作用,名称必须准确描述方法的功能。
Code Style & Formatting
代码风格与格式化
- Use one consistent style across the project: either standard Java Coding Conventions or IntelliJ defaults. The specific choice does not matter — consistency does.
- Keep classes package-private by default. Only expose what is needed as .
public - Apply to variables when immutability is intended.
final - Use method references over lambdas when possible: instead of
User::userId.u -> u.userId() - Organize imports: standard library, third-party, internal packages. No wildcard imports.
- If the project includes Spotless, run the formatter after every code change (only for edited files). For Gradle: . For Maven:
./gradlew spotlessApply.mvn spotless:apply
- 整个项目使用统一的代码风格:要么用标准Java编码规范,要么用IntelliJ默认配置,具体选择不重要,一致性才重要。
- 类默认使用包私有,仅将需要对外暴露的内容声明为。
public - 有意设置为不可变的变量加上修饰符。
final - 尽可能使用方法引用替代lambda:用而非
User::userId。u -> u.userId() - 导入分组:标准库、第三方库、内部包,不要使用通配符导入。
- 如果项目集成了Spotless,每次代码变更后运行格式化工具(仅针对修改的文件)。Gradle项目执行:,Maven项目执行:
./gradlew spotlessApply。mvn spotless:apply
Streams & Collections
流与集合
Use Java Streams for collection transformations. Prefer , , , .
filter()map()flatMap()toList()java
// CORRECT
var activeEmails = users.stream()
.filter(User::isActive)
.map(User::email)
.toList();
// WRONG — manual loop for simple transformation
var activeEmails = new ArrayList<String>();
for (User user : users) {
if (user.isActive()) {
activeEmails.add(user.getEmail());
}
}Never perform side effects in intermediate operations (like , , ) — these should be pure functions. Terminal is the right place for side effects. Prefer over when no intermediate operations are needed.
mapfilterflatMapforEachIterable.forEach()stream().forEach()java
// WRONG — side effect in intermediate operation
users.stream()
.map(user -> { emailService.send(user); return user.email(); })
.toList();
// WRONG — unnecessary stream() when there is no pipeline
users.stream().forEach(user -> emailService.send(user));
// CORRECT — Iterable.forEach for simple side effects
users.forEach(user -> emailService.send(user));
// CORRECT — pure filter, side effect only in terminal forEach
users.stream()
.filter(User::isActive)
.forEach(user -> emailService.send(user));
// CORRECT — plain loop when you need break/continue or checked exceptions
for (var user : users) {
emailService.send(user);
}集合转换使用Java Streams,优先使用、、、。
filter()map()flatMap()toList()java
// CORRECT
var activeEmails = users.stream()
.filter(User::isActive)
.map(User::email)
.toList();
// WRONG — 简单转换用手写循环
var activeEmails = new ArrayList<String>();
for (User user : users) {
if (user.isActive()) {
activeEmails.add(user.getEmail());
}
}永远不要在中间操作(比如、、)中执行副作用——这些操作应该是纯函数,终端操作才是放置副作用的正确位置。如果不需要中间操作,优先使用而非。
mapfilterflatMapforEachIterable.forEach()stream().forEach()java
// WRONG — 中间操作包含副作用
users.stream()
.map(user -> { emailService.send(user); return user.email(); })
.toList();
// WRONG — 没有处理流程时没必要使用stream()
users.stream().forEach(user -> emailService.send(user));
// CORRECT — 简单副作用用Iterable.forEach
users.forEach(user -> emailService.send(user));
// CORRECT — 纯过滤,仅在终端forEach中执行副作用
users.stream()
.filter(User::isActive)
.forEach(user -> emailService.send(user));
// CORRECT — 需要break/continue或者处理受检异常时用普通循环
for (var user : users) {
emailService.send(user);
}Testing
测试
Consistent Test Naming
统一的测试命名
Pick one naming convention for the entire project and enforce it. Recommended format:
methodName_shouldExpectedBehavior_whenConditionExamples:
java
@Test
void findByEmail_shouldReturnUser_whenUserExists() { ... }
@Test
void findByEmail_shouldThrowNotFoundException_whenUserDoesNotExist() { ... }
@Test
void processPayment_shouldReturnSuccess_whenBalanceSufficient() { ... }Do NOT mix , , , and styles in the same project. Consistency matters more than the specific convention.
camelCasesnake_casegiven/when/thenshould整个项目选择一种命名规范并强制执行,推荐格式:
methodName_shouldExpectedBehavior_whenCondition示例:
java
@Test
void findByEmail_shouldReturnUser_whenUserExists() { ... }
@Test
void findByEmail_shouldThrowNotFoundException_whenUserDoesNotExist() { ... }
@Test
void processPayment_shouldReturnSuccess_whenBalanceSufficient() { ... }同一个项目中不要混合使用、、和风格,一致性比具体选择哪种规范更重要。
camelCasesnake_casegiven/when/thenshouldTest Structure
测试结构
- Use JUnit 5 as the default testing framework.
- Spock Framework (Groovy-based) is an acceptable alternative if the team prefers BDD-style specifications. It is powerful but requires Groovy proficiency.
- Follow Arrange-Act-Assert (or Given-When-Then in Spock).
- Each test should test one behavior. No multi-assert tests covering unrelated behaviors.
- Use sparingly — a well-named test method is self-documenting.
@DisplayName
- 默认测试框架使用JUnit 5。
- 如果团队偏好BDD风格的规范,Spock Framework(基于Groovy)是可接受的替代方案,它功能强大但需要掌握Groovy。
- 遵循Arrange-Act-Assert模式(Spock中用Given-When-Then)。
- 每个测试只验证一个行为,不要写覆盖不相关行为的多断言测试。
- 谨慎使用——命名良好的测试方法本身就是自说明的。
@DisplayName
TDD
TDD
Test-Driven Development produces better APIs. Writing tests first forces you to design the public interface before the implementation. Even if not doing strict TDD, write tests immediately after writing the method signature and before the implementation body.
测试驱动开发可以产出更好的API,先写测试会迫使你在实现之前先设计公共接口。即使不做严格的TDD,也应该在写完方法签名之后、写实现代码之前立刻写测试。
Test Independence
测试独立性
Tests must be independent and repeatable. No shared mutable state between tests. No ordering dependencies.
测试必须是独立且可重复的,测试之间没有共享可变状态,没有执行顺序依赖。
Concurrency
并发
Virtual Threads (Project Loom)
虚拟线程(Project Loom)
Use Virtual Threads for I/O-bound concurrent work. They eliminate the need for reactive frameworks for most use cases.
java
// CORRECT — Virtual Threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future = executor.submit(() -> fetchData(url));
return future.get();
}
// WRONG — reactive chains for simple I/O
Mono.fromCallable(() -> fetchData(url))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(data -> process(data))
.subscribe();IO密集型并发任务使用虚拟线程,大多数场景下它们可以替代响应式框架。
java
// CORRECT — 虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future = executor.submit(() -> fetchData(url));
return future.get();
}
// WRONG — 简单IO用响应式链
Mono.fromCallable(() -> fetchData(url))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(data -> process(data))
.subscribe();Immutability Enables Safe Concurrency
不可变性实现安全并发
Writing proper OOP with small, immutable objects in small methods with limited scope is the best concurrency strategy. Small objects are created and garbage-collected quickly, reducing contention.
用小的不可变对象、短方法、有限作用域编写规范的OOP代码是最好的并发策略,小对象创建和回收速度快,能减少竞争。
Structured Concurrency (Java 25)
结构化并发(Java 25)
Use to manage lifetimes of forked subtasks as a unit. Ensures that subtasks are reliably cancelled and joined when the scope closes.
StructuredTaskScopejava
// CORRECT
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(() -> fetchUser(id));
var order = scope.fork(() -> fetchOrder(id));
scope.join().throwIfFailed();
return new Response(user.get(), order.get());
}
// WRONG — manual executor management, no automatic lifecycle
var executor = Executors.newFixedThreadPool(2);
var f1 = executor.submit(() -> fetchUser(id));
var f2 = executor.submit(() -> fetchOrder(id));
executor.shutdown();
return new Response(f1.get(), f2.get());使用将分叉的子任务作为一个整体管理生命周期,确保作用域关闭时子任务能可靠地取消和合并。
StructuredTaskScopejava
// CORRECT
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(() -> fetchUser(id));
var order = scope.fork(() -> fetchOrder(id));
scope.join().throwIfFailed();
return new Response(user.get(), order.get());
}
// WRONG — 手动管理executor,没有自动生命周期
var executor = Executors.newFixedThreadPool(2);
var f1 = executor.submit(() -> fetchUser(id));
var f2 = executor.submit(() -> fetchOrder(id));
executor.shutdown();
return new Response(f1.get(), f2.get());What NOT to Use
不要使用这些能力
- Do NOT use RxJava. It is overly complex, hard to debug, and unnecessary with Virtual Threads. If you are in a project that uses RxJava, do not introduce more of it.
- Avoid chains when Virtual Threads can express the same logic sequentially.
CompletableFuture - Do not prematurely introduce reactive patterns. Use them only when you have proven back-pressure requirements.
- 不要使用RxJava,它过于复杂、难以调试,有了虚拟线程之后完全没必要使用。如果你的项目已经在用RxJava,不要再新增相关代码。
- 当虚拟线程可以用顺序逻辑实现相同功能时,避免使用链。
CompletableFuture - 不要过早引入响应式模式,仅当你确实有背压需求时再使用。
Modern I/O
现代IO
Use convenience methods and factory. Avoid / boilerplate for simple operations.
FilesPath.of()BufferedReaderWriterjava
// CORRECT
String content = Files.readString(Path.of("config.json"));
Files.writeString(Path.of("output.txt"), data);
var path = Path.of("src", "main", "java");
// WRONG
var br = new BufferedReader(new FileReader("config.json"));
// ... manual read loop ...
var path = Paths.get("src", "main", "java");Use the built-in for HTTP calls. Do not add boilerplate.
HttpClientHttpURLConnectionjava
// CORRECT
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(url)).GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// WRONG — HttpURLConnection with manual stream handling
var conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// ...使用便捷方法和工厂方法,简单操作不要用/的样板代码。
FilesPath.of()BufferedReaderWriterjava
// CORRECT
String content = Files.readString(Path.of("config.json"));
Files.writeString(Path.of("output.txt"), data);
var path = Path.of("src", "main", "java");
// WRONG
var br = new BufferedReader(new FileReader("config.json"));
// ... 手动读循环 ...
var path = Paths.get("src", "main", "java");HTTP调用使用内置的,不要写的样板代码。
HttpClientHttpURLConnectionjava
// CORRECT
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(url)).GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// WRONG — HttpURLConnection加手动流处理
var conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// ...Date and Time
日期与时间
Always use (Java 8+). Never use , , or .
java.timeDateCalendarSimpleDateFormatjava
// CORRECT
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2025, Month.JANUARY, 15);
Instant now = Instant.now();
long days = ChronoUnit.DAYS.between(start, end);
Duration duration = Duration.ofMinutes(30);
// WRONG
Date date = new Date();
Calendar cal = Calendar.getInstance();
cal.set(2025, 0, 15); // zero-indexed months始终使用(Java 8+),永远不要用、或者。
java.timeDateCalendarSimpleDateFormatjava
// CORRECT
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2025, Month.JANUARY, 15);
Instant now = Instant.now();
long days = ChronoUnit.DAYS.between(start, end);
Duration duration = Duration.ofMinutes(30);
// WRONG
Date date = new Date();
Calendar cal = Calendar.getInstance();
cal.set(2025, 0, 15); // 月份从0开始计数Logging
日志
- Use SLF4J (). Never use
org.slf4j.LoggerorSystem.out.println().System.err.println() - Use parameterized logging — never string concatenation.
java
// CORRECT
log.info("Processing order {} for user {}", orderId, userId);
// WRONG
log.info("Processing order " + orderId + " for user " + userId);- Log levels:
- — exceptions and failures requiring attention.
error - — recoverable issues, degraded behavior.
warn - — significant business events (order placed, payment processed).
info - — detailed technical information for troubleshooting.
debug - — very fine-grained, typically disabled in production.
trace
- 使用SLF4J(),永远不要用
org.slf4j.Logger或者System.out.println()。System.err.println() - 使用参数化日志,永远不要用字符串拼接。
java
// CORRECT
log.info("Processing order {} for user {}", orderId, userId);
// WRONG
log.info("Processing order " + orderId + " for user " + userId);- 日志级别:
- — 需要关注的异常和故障。
error - — 可恢复的问题,功能降级。
warn - — 重要的业务事件(订单创建、支付完成)。
info - — 用于排查问题的详细技术信息。
debug - — 非常细粒度的日志,生产环境通常关闭。
trace
Project Structure
项目结构
- Separate domain code from infrastructure code in the package structure.
- Keep configuration classes in dedicated packages.
config - Follow consistent package naming: (e.g.,
com.company.project.<domain>.<layer>).com.acme.shop.order.repository - One public class per file. No multi-class files except for tightly coupled inner classes.
- 包结构中领域代码和基础设施代码分离。
- 配置类放在专门的包中。
config - 遵循统一的包命名规则:(例如
com.company.project.<domain>.<layer>)。com.acme.shop.order.repository - 每个文件一个公共类,除了紧密耦合的内部类之外不要在一个文件中写多个类。
JVM & GC
JVM & GC
- Do not tune GC parameters unless you have measured a problem. The default GC (G1GC in modern JVMs) is good enough for the vast majority of applications.
- Writing proper OOP with small, short-lived objects is better than any GC tuning.
- Profile before optimizing. Use JFR (Java Flight Recorder) and JMC (Java Mission Control) for performance analysis.
- 除非你已经测出问题,否则不要调整GC参数,现代JVM的默认GC(G1GC)对绝大多数应用来说都足够好用。
- 用小的短生命周期对象编写规范的OOP代码,比任何GC调优效果都好。
- 优化前先做 profiling,使用JFR(Java Flight Recorder)和JMC(Java Mission Control)做性能分析。
Deprecated Patterns — Do NOT Use
已弃用模式——不要使用
These patterns are outdated and must not be used in new code:
| Deprecated Pattern | Modern Replacement |
|---|---|
Lombok | |
| |
| |
| |
| RxJava / Project Reactor for simple I/O | Virtual Threads |
Old | Switch expressions |
Manual | Pattern matching |
String concatenation with | Text blocks |
| Anonymous inner classes for functional interfaces | Lambdas / method references |
| |
Returning | |
| SLF4J logging |
Raw types ( | Always use generics |
| |
| |
| |
| |
| |
| |
| |
Duplicate | Multi-catch |
| |
| |
| |
| Manual executor lifecycle for structured tasks | |
这些模式已经过时,新代码中禁止使用:
| 已弃用模式 | 现代替代方案 |
|---|---|
数据类使用Lombok | |
| |
| |
| |
| 简单IO使用RxJava / Project Reactor | 虚拟线程 |
带 | Switch表达式 |
手动 | 模式匹配 |
多行字符串用 | 文本块 |
| 函数式接口用匿名内部类 | Lambda / 方法引用 |
简单场景用 | |
方法返回 | |
| SLF4J日志 |
原始类型(用 | 始终使用泛型 |
| |
| |
| |
| |
| |
| |
| |
重复 | 多重捕获 |
文件读取用 | |
| |
| |
| 结构化任务手动管理executor生命周期 | |