优化Java代码 9 条简单的规则
使用 java.util.Optional 代替 null
通过使用java.util.Optional
,将强制客户端检查该值是否存在。
考虑getBeer(...)
下面的方法,该方法的调用者期望接收一个Beer
对象,并且从方法 API 中不清楚Beer
可以是null
顺便说一句,调用者可能会忘记添加null
检查,并且可能会收到NullPointerException
程序错误。
以前:
public Beer getBeer(Customer customer) {
return customer.age < 18
? null
: this.beerCatalogue.get(0);
}
以后:
public Optional<Beer> getBeer(Customer customer) {
return customer.age < 18
? empty()
: Optional.of(this.beerCatalogue.get(0));
}
返回空集合/数组,而不是 null
动机与上述相同——我们不应该依赖null
以前:
public List<Beer> getBeerCatalogue(Customer customer) {
return customer.age < 18
? null
: this.beerCatalogue;
}
以后:
public List<Beer> getBeerCatalogue(Customer customer) {
return customer.age < 18
? emptyList()
: this.beerCatalogue;
}
使用var
类型推理减少了样板代码的数量并降低了认知复杂性,从而提高了可读性。
以前:
static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor, SchemaDefinition schemaDefinition) {
ValueGetter valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
return (dynamicMessageBuilder, row) -> {
Iterable<DynamicMessage> iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
for (Object entry : iterable) {
dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
}
};
}
以后:
static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor, SchemaDefinition schemaDefinition) {
var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
return (dynamicMessageBuilder, row) -> {
var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
for (var entry : iterable) {
dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
}
};
}
定义局部变量 final
定义局部变量final
向程序提示该变量不能被重新分配,这通常会导致更好的代码质量并有助于避免错误。
以前:
static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor, SchemaDefinition schemaDefinition) {
var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
return (dynamicMessageBuilder, row) -> {
var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
for (var entry : iterable) {
dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
}
};
}
以后:
static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor, SchemaDefinition schemaDefinition) {
final var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
return (dynamicMessageBuilder, row) -> {
final var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
for (final var entry : iterable) {
dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
}
};
}
使用静态导入
静态导入(Static Imports)是Java编程语言的一个特性,它允许在类中直接使用静态成员(字段和方法),而不需要使用类名来限定。
静态导入使代码不那么冗长,因此更具可读性。
注意,这条规则有一个极端情况 - Java 静态导入中有一堆静态方法(List.of()
、Set.of()
、Map.of()
),这会损害代码质量,使其模棱两可。因此,使用此规则时,需要确认这种静态导入是否使代码更具可读性?
以前:
public static List<FieldGetter> fieldGetters(SchemaDefinition schemaDefinition,
FieldName fieldName,
FieldDescriptor fieldDescriptor) {
final var schemaField = SchemaUtils.findFieldByName(schemaDefinition, fieldName).orElseThrow(SchemaFieldNotFoundException::new);
if (fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
return schemaField.getFields().stream()
.flatMap(it -> fieldGetters(schemaDefinition, it.getName(), it.getDescriptor()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
以后:
public static List<FieldGetter> fieldGetters(SchemaDefinition schemaDefinition,
FieldName fieldName,
FieldDescriptor fieldDescriptor) {
final var schemaField = findFieldByName(schemaDefinition, fieldName).orElseThrow(SchemaFieldNotFoundException::new);
if (fieldDescriptor.getJavaType() == MESSAGE) {
return schemaField.getFields().stream()
.flatMap(it -> fieldGetters(schemaDefinition, it.getName(), it.getDescriptor()))
.collect(toList());
}
return emptyList();
}
Prefer fully qualified imports(更倾向于使用完全限定的导入)
"Prefer fully qualified imports"是一种编码风格建议,特别是在Java等编程语言中。它建议开发人员在代码中使用完全限定的导入语句,而不是使用通配符(*
)或静态导入。
以前:
相比之下,通配符导入语句可能会导致一些问题。例如,如果使用了通配符导入:
import java.util.*;
这会导入java.util
包中的所有类和成员。虽然这样可以减少代码量,但可能会造成以下问题:
- 命名冲突: 如果不小心引入了具有相同名称的类或成员,可能会导致命名冲突,使得代码难以理解和维护。
- 不清晰: 读者可能无法轻易地确定特定类或成员的来源,需要查看导入语句才能确定。
- 性能问题: Java编译器可能需要额外的时间来解析通配符导入语句,特别是在大型项目中。
因此,推荐使用完全限定的导入语句,这样可以:
- 明确代码中使用的类和成员的来源。
- 避免命名冲突和意外的行为。
- 提高代码的可读性和可维护性。
以后:
完全限定的导入语句是指导入特定类或成员时使用完整的类名或成员名称。
import java.util.ArrayList;
import java.util.List;
这里的导入语句完全限定了要导入的类(ArrayList和List),并且清晰地表明了代码中使用的类的来源。
然而,对于静态成员的导入,有时静态导入可以改善代码的可读性。在这种情况下,尽管通配符导入要小心使用,但静态导入可以使得代码更加清晰。
在长方法/构造函数声明中将每个参数放在新行上
拥有特定的代码风格并在整个代码库中使用它可以降低认知复杂性,这意味着代码更易于阅读和理解。
以前:
public void processUserData(String name, int age, String address, double salary, boolean isEmployed, String occupation) {
//...
}
// or
public void processUserData(
String name, int age, String address, double salary, boolean isEmployed, String occupation
) {
//...
}
// or
public void processUserData(String name, int age,
String address, double salary, boolean isEmployed, String occupation
) {
//...
}
// or
public void processUserData(String name, int age, String address, double salary,
boolean isEmployed, String occupation
) {
//...
}
以后:
public void processUserData(String name,
int age,
String address,
double salary,
boolean isEmployed,
String occupation) {
//...
}
// or
public void processUserData(
String name,
int age,
String address,
double salary,
boolean isEmployed,
String occupation) {
//...
}
创建不可变的 POJO 或使用 record
不可变类比可变类更容易设计、实现和使用。它们不易出错,也更安全。使用不可变对象,不必担心同步或对象状态(对象是否初始化?
要使类不可变:
- 创建类
final
中的所有字段 - 创建一个
constructor
/builder
来初始化所有字段 - 如果该值是可选的(可以是
null
),请使用java.util.Optional
- 使用不可变集合或在 getter 中返回不可变视图(
Collections.unmodifiableList(...)
等) - 不要公开修改对象状态的方法
以前:
public class User {
private String name;
private int age;
private String address;
private List<Claim> claims;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Claim> getClaims() {
return claims;
}
public void setClaims(List<Claim> claims) {
this.claims = claims;
}
// ...
}
以后:
public class User {
private final String name;
private final int age;
private final String address;
private final List<Claim> claims;
public User(String name, int age, String address, List<Claim> claims) {
this.name = requireNonNull(name);
this.age = requirePositive(age);
this.address = requireNonNull(address);
this.claims = List.copyOf(requireNonNull(claims));
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public List<Claim> getClaims() {
return claims;
}
// ...
}
或者, record
如果使用 java 14+
public record User(String name, int age, String address, List<Claim> claims) {
public User {
requireNonNull(name);
requirePositive(age);
requireNonNull(address);
claims = List.copyOf(requireNonNull(claims));
}
}
将 Builder 模式用于具有许多参数/可选参数的类
Builder 模式模拟 Python/Scala/Kotlin 中可用的命名参数。它使客户端代码易于读取和编写,并能够更流畅地使用具有默认值的可选/参数。
以前:
public class User {
private final UUID id;
private final Instant createdAt;
private final Instant updatedAt;
private final String firstName;
private final String lastName;
private final Email email;
private final Optional<Integer> age;
private final Optional<String> middleName;
private final Optional<Address> address;
public User(UUID id,
Instant createdAt,
Instant updatedAt,
String firstName,
String lastName,
Email email,
Optional<Integer> age,
Optional<String> middleName,
Optional<Address> address) {
this.id = requireNonNull(id);
this.createdAt = requireNonNull(createdAt);
this.updatedAt = requireNonNull(updatedAt);
this.firstName = requireNonNull(firstName);
this.lastName = requireNonNull(lastName);
this.email = requireNonNull(email);
this.age = requireNonNull(age);
this.middleName = requireNonNull(middleName);
this.address = requireNonNull(address);
if (firstName.isBlank()) {
// throw exception
}
// ... validation
}
// ...
}
// And then you would write:
public User createUser() {
final var user = new User(
randomUUID(),
now(),
now().minus(1L, DAYS),
// firstName, lastName and email are String, what if you
// mix up parameters order in constructor?
"first_name",
"last_name",
"user@test.com",
empty(),
Optional.of("middle_name"),
empty()
);
// ...
return user;
}
以后:
public class User {
private final UUID id;
private final Instant createdAt;
private final Instant updatedAt;
private final String firstName;
private final String lastName;
private final String email;
private final Optional<Integer> age;
private final Optional<String> middleName;
private final Optional<Address> address;
// private constructor
private User(Builder builder) {
this.id = requireNonNull(builder.id);
this.createdAt = requireNonNull(builder.createdAt);
this.updatedAt = requireNonNull(builder.updatedAt);
this.firstName = requireNonNull(builder.firstName);
this.lastName = requireNonNull(builder.lastName);
this.email = requireNonNull(builder.email);
this.age = requireNonNull(builder.age);
this.middleName = requireNonNull(builder.middleName);
this.address = requireNonNull(builder.address);
if (firstName.isBlank()) {
// throw exception
}
// ... validation
}
// ...
public static class Builder {
private UUID id;
private Instant createdAt;
private Instant updatedAt;
private String firstName;
private String lastName;
private String email;
// Optionals are empty by default
private Optional<Integer> age = empty();
private Optional<String> middleName = empty();
private Optional<Address> address = empty();
private Builder() {
}
public static Builder newUser() {
// You can easily add (lazy) default parameters
return new Builder()
.id(randomUUID())
.createdAt(now())
.updatedAt(now());
}
public Builder id(UUID id) {
this.id = id;
return this;
}
public Builder createdAt(Instant createdAt) {
this.createdAt = createdAt;
return this;
}
// ...
public Builder address(Address address) {
this.address = Optional.ofNullable(address);
return this;
}
public User build() {
return new User(this);
}
}
}
// And then:
public User createUser() {
// You end up writing more code in User class but the
// class API becomes more concise
final var user = newUser()
.updatedAt(now().minus(1L, DAYS))
.firstName("first_name")
.lastName("last_name")
.email("user@test.com")
.middleName("middle_name")
.build();
// ...
return user;
}
- 点赞
- 收藏
- 关注作者
评论(0)