【问题标题】:Most Efficient Way to Initialize Java Fields that are Objects?初始化作为对象的 Java 字段的最有效方法?
【发布时间】:2022-08-18 21:47:12
【问题描述】:

假设我有这样的课程:

public class ManyFields {
    public Object1 object1;
    public Object2 object2;
    public Object3 object3;
    // etc.
}

在尝试使用ManyFieldsobject 执行任何操作时,我想确保这些字段不为空。所以也许我会有这样的验证方法:

public Object ensureNotNull(Object o) {
   if (o.object1 != null) o.object1 = new Object1();
   // and so on and so forth
   return o;
}

有没有更好的,还高性能的,接近这里?而不是单独检查每个字段是否为空?我确实像这样探索反射:

for (Field f : fields) {
    try {
        if (f.get(c) == null) {
            f.set(c, f.getType().newInstance());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

然而,我读过这个反射并不是初始化字段的最友好的方法。有什么建议会有所帮助吗?谢谢。

  • 如果您想从创建ManyFields 时确保状态一致,那么您应该在构造函数中实例化所有字段。这就是它的用途,您不需要空检查。
  • @QBrute,同意。但是,在这种情况下,对于更多的上下文,我正在考虑有人可以向我发送这个带有空字段的 ManyFields 对象的可能性。就像他们故意关闭并手动取消字段一样。
  • 检查每个字段是否为空性能最好的选择。
  • 除了初始化构造函数中的字段外,您还应该将它们设为private,并为它们设置设置器。 setter 可以检查一个空参数,并抛出一个适当的异常(或做一些不同的事情,比如创建一个新对象)。这样一来,没人能\"...故意关闭并手动取消字段...\"
  • 确切地。这实际的问题就在这里:public Object1 object1;。使它们private 并初始化,并控制设置器,您的问题就消失了。

标签: java


【解决方案1】:

反射是做几乎任何事情的最糟糕和最慢的方法。

你想要的是封装您的字段:将它们设为私有,以便您的班级完全控制它们的更改方式。

如果它们不是太多,您可以将它们全部设为 final 并让构造函数设置它们:

public class Person {
    private final String firstName;
    private final String lastName;

    public Person(String firstName,
                  String lastName) {

        this.firstName = Objects.requireNonNull(firstName,
            "First name cannot be null.");
        this.lastName = Objects.requireNonNull(lastName,
            "Last name cannot be null.");
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

使用这种方法,外部代码完全不可能将字段设置为空值。¹如果调用者传递空值,则构造函数永远不会完成,因此永远不会存在具有空值字段的 Person 的现有实例。这意味着你可以制作一个书面保证调用者将永远不必检查值是否为空:

/**
 * Returns this user's first name.  This value will never be null.
 */
public String getFirstName() {
    return firstName;
}

/**
 * Returns this user's last name.  This value will never be null.
 */
public String getLastName() {
    return lastName;
}

您甚至可以更进一步,验证值,而不仅仅是检查 null:

public Person(String firstName,
              String lastName) {

    this.firstName = Objects.requireNonNull(firstName,
        "First name cannot be null.");
    this.lastName = Objects.requireNonNull(lastName,
        "Last name cannot be null.");

    if (firstName.isBlank()) {
        throw new IllegalArgumentException("First name cannot be blank.");
    }
    if (lastName.isBlank()) {
        throw new IllegalArgumentException("Last name cannot be blank.");
    }
}

如果你有很多字段,你可以只使用 get-methods 和 set-methods 而不是在构造函数中设置所有内容。在这种情况下,将每个字段初始化为非空值是很有用的,因此该类能够提供与构造函数方法相同的非空保证:

public class Person {
    private String firstName = "(unknown)";
    private String lastName = "(unknown)";
    private String socialSecurityNumber = "000-00-0000";
    private LocalDate dateOfBirth = LocalDate.MAX;

    public String getFirstName() {
        return firstName;
    }

    public String setFirstName(String name) {
        this.firstName = Objects.requireNonNull(name,
            "Name cannot be null.");
    }

    public String getLastName() {
        return lastName;
    }

    public String setLastName(String name) {
        this.lastName = Objects.requireNonNull(name,
            "Name cannot be null.");
    }

    public String getSocialSecurityNumber() {
        return socialSecurityNumber;
    }

    public void setSocialSecuityNumber(String num) {
        Objects.requireNonNull(num, "Argument cannot be null.");
        if (!num.matches("\\d{3}-\\d{2}-\\d{4}")) {
            throw new IllegalArgumentException(
                "Argument must be in the format nnn-nn-nnnn.");
        }

        this.socialSecurityNumber = num;
    }

    public LocalDate getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(LocalDate date) {
        Objects.requireNonNull(date, "Date cannot be null.");

        int age = date.getYear() - LocalDate.now().getYear();
        if (age > 150) {
            throw new IllegalArgumentException(
                "Invalid date:  no one is more than 150 years old.");
        }
        if (age < 0) {
            throw new IllegalArgumentException(
                "Invalid date:  cannot be born in the future.");
        }

        this.dateOfBirth = date;
    }
}

1. 从技术上讲,外部代码可以使用反射的setAccessible 方法来破解不在模块中的类的私有成员,除非安装了 SecurityManager。然而,以这种方式侵入事物的人应该期望类以意想不到的方式破坏,因为他们本质上是“使保修失效”。意思是,这很糟糕,没有人应该这样做,事实上 Java 的更高版本不会让他们这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    相关资源
    最近更新 更多