【问题标题】:Is it a good practice to use set methods in the constructor to initialize class's fields?在构造函数中使用 set 方法来初始化类的字段是一种好习惯吗?
【发布时间】:2015-03-15 09:39:28
【问题描述】:

我想在构造函数中使用类的 set 方法来检查要初始化的值,如果它们不符合我设置的约束,则抛出异常。

代码示例

public class MyClass {

    // Fields
    private int number;
    private String string;

    // Constructor
    public MyClass(int number, String string) {
        setNumber(number);
        setString(string);
    }

    // Set Methods
    public void setNumber(int number) {
        if (number<=0) {    // Certain constrain for number
            throw new IllegalArgumentException("Number must be positive");
        }
        this.number = number;
    }

    public void setString(String string) { // Certain constrain for string
        if (string.equals("")) {
            throw new IllegalArgumentException("string cannot be empty");
        } 
        this.string = string;
    }

    public String toString() {
        return String.format("Ordered %d x %s%n", number, string);
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass(8, "Souvlaki");   // Everything allright
        System.out.println(obj);
        try {
            MyClass obj2 = new MyClass(-3, "Mousaka");  // Error in number argument
        } catch (IllegalArgumentException exception) {  // catch the exception
            System.out.printf("Exception Caught: Number must be positive%n%n");
        }
        MyClass obj2 = new MyClass(4, "");  // Error in string argument
        // Allow the exception to end program execution
    }
}

输出

订购了 8 x 苏夫拉基

异常捕获:数字必须是正数

线程“主”java.lang.IllegalArgumentException 中的异常:字符串 在 MyClass.setString(MyClass.java:23) 处不能为空 MyClass.(MyClass.java:10) 在 MyClass.main(MyClass.java:40)

输出正是我想要的。创建的第一个对象用适当的值初始化。调用 toString() 方法隐含地证明了这一点。 第二个和第三个对象由于初始化错误而抛出异常。 捕获第一个异常是为了让程序继续执行。第二个异常没有被捕获以便输出打印的错误消息是异常。

所以一切似乎都是正确的,但这是一种好的编程技术还是隐藏了一些错误?

【问题讨论】:

  • if (string == "") 不再... stackoverflow.com/questions/513832/…
  • No, It's really a bad idea。请避免这样做。
  • 是和不是。如果您使用的方法假定某种状态,则可能会出现问题,这种状态在构造类之前无法保证。就个人而言,我倾向于这样做以确保继承可以工作,允许子类根据需要覆盖设置的方法,而无需诉诸讨厌的黑客来使其工作,但这就是我......
  • @RohitJain:如果所有的设置器都是最终的(或者如果类本身是最终的),那么这个问题就会消失。
  • 您可能希望使用构建器

标签: java constructor mutators


【解决方案1】:

正如 cmets 所建议的,这可能存在问题。特别是,您可能想看看What's wrong with overridable method calls in constructors?。底线大致是:有人可能会以意想不到的方式覆盖set... 方法,并引用该类的其他(未初始化的)字段,这可能会导致各种错误。

可以选择专用的验证方法。但是这些可能会被多次调用,即使不需要验证。

您可以通过将set... 方法设为final 来缓解大部分问题。无论如何,这是一个很好的做法。正如 Joshua Bloch 在他的书“Effective Java”第 17 项中所说:

“为继承设计和记录,否则禁止”

这意味着你应该使 every 方法final,除非你明确地想要允许它被覆盖,并且记录它应该如何被覆盖(或者,或者,让整个班级final)。

【讨论】:

  • 是的。又名“让this escape 从构造函数中”
【解决方案2】:

您可以在类中创建一个checkInvariant() 方法,而不是在构造函数中进行验证,该方法验证所有字段。

class MyClass {
    private int num;
    private String value;

    public void checkInvariants() {
        assertNotEmpty(value, "String value cannot be empty");
        assertPositive(num, "Number num should be non-negative");
    }
}

然后在其他地方,您可能会将此类的实例作为参数传递,首先调用此方法以确保不变量成立:

class SomeOtherClass {
    public void doSomethingWithMyClass(MyClass myClass) {
        myClass.checkInvariants();
        // Proceed with work.
    }
}

【讨论】:

    【解决方案3】:

    您的变量可以在类中的任何位置访问,因此无需使用 mutator 方法来初始化您的变量。

    如果您想对输入参数进行一些验证,请使用另一种方法来执行所需的所有验证。

    在构造函数中调用验证方法。

    【讨论】:

    • 如果我这样做的话,需要调用两次这个方法。一次用于设置器,一次用于构造器。除了向类添加一个方法之外,该方法仍然需要 final 正如@Marco13 在他的回答中提到的那样,以防止构造函数中出现意外错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-01
    • 1970-01-01
    • 2021-04-11
    • 1970-01-01
    • 2017-06-14
    • 2018-10-15
    相关资源
    最近更新 更多