优化Java代码 9 条简单的规则

举报
不惑 发表于 2024/11/15 17:08:01 2024/11/15
【摘要】 优化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包中的所有类和成员。虽然这样可以减少代码量,但可能会造成以下问题:

  1. 命名冲突: 如果不小心引入了具有相同名称的类或成员,可能会导致命名冲突,使得代码难以理解和维护。
  2. 不清晰: 读者可能无法轻易地确定特定类或成员的来源,需要查看导入语句才能确定。
  3. 性能问题: 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;
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。