在构造函数中使用 set 方法来初始化 class 的字段是一个好习惯吗?
Is it a good practice to use set methods in the constructor to initialize class's fields?
我想在构造函数中使用 class 的 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
}
}
输出:
Ordered 8 x Souvlaki
Exception Caught: Number must be positive
Exception in thread "main" java.lang.IllegalArgumentException: string
cannot be empty at MyClass.setString(MyClass.java:23) at
MyClass.(MyClass.java:10) at MyClass.main(MyClass.java:40)
输出正是我想要的。创建的第一个对象使用适当的值进行初始化。调用 toString() 方法隐式证明了这一点。
第二个和第三个对象由于初始化错误而抛出异常。
捕获第一个异常是为了让程序继续执行。第二个异常没有被捕获,为了输出打印出的错误信息是异常。
所以一切似乎都是正确的,但这是一种好的编程技术还是它隐藏了一些错误?
您可以在 class 中创建一个 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 的实例作为参数传递,首先调用此方法以确保不变量保持:
class SomeOtherClass {
public void doSomethingWithMyClass(MyClass myClass) {
myClass.checkInvariants();
// Proceed with work.
}
}
您的变量可以在 class 内的任何地方访问,因此无需使用 mutator 方法来初始化您的变量。
如果您想对输入参数进行一些验证,请使用另一种方法来执行所有需要的验证。
在构造函数中调用验证方法。
正如评论所暗示的那样,这可能存在问题。特别是,您可能想看看 What's wrong with overridable method calls in constructors?。底线大致是:有人可能会以意想不到的方式覆盖 set...
方法,并引用 class 的其他(未初始化的)字段,这可能会导致各种错误。
专用验证方法可能是一种选择。但是这些可能会被调用多次,即使不需要验证。
您可以通过 set...
方法 final
来缓解大部分问题。无论如何,这是一个很好的做法。正如 Joshua Bloch 在他的书“Effective Java”中所说,第 17 项:
"Design and document for inheritance or else prohibit it"
这意味着你应该制作 every 方法 final
,除非你明确希望允许它被覆盖,并且 document 它应该如何被覆盖(或者,或者,使整个 class final
)。
我想在构造函数中使用 class 的 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
}
}
输出:
Ordered 8 x Souvlaki
Exception Caught: Number must be positive
Exception in thread "main" java.lang.IllegalArgumentException: string cannot be empty at MyClass.setString(MyClass.java:23) at MyClass.(MyClass.java:10) at MyClass.main(MyClass.java:40)
输出正是我想要的。创建的第一个对象使用适当的值进行初始化。调用 toString() 方法隐式证明了这一点。 第二个和第三个对象由于初始化错误而抛出异常。 捕获第一个异常是为了让程序继续执行。第二个异常没有被捕获,为了输出打印出的错误信息是异常。
所以一切似乎都是正确的,但这是一种好的编程技术还是它隐藏了一些错误?
您可以在 class 中创建一个 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 的实例作为参数传递,首先调用此方法以确保不变量保持:
class SomeOtherClass {
public void doSomethingWithMyClass(MyClass myClass) {
myClass.checkInvariants();
// Proceed with work.
}
}
您的变量可以在 class 内的任何地方访问,因此无需使用 mutator 方法来初始化您的变量。
如果您想对输入参数进行一些验证,请使用另一种方法来执行所有需要的验证。
在构造函数中调用验证方法。
正如评论所暗示的那样,这可能存在问题。特别是,您可能想看看 What's wrong with overridable method calls in constructors?。底线大致是:有人可能会以意想不到的方式覆盖 set...
方法,并引用 class 的其他(未初始化的)字段,这可能会导致各种错误。
专用验证方法可能是一种选择。但是这些可能会被调用多次,即使不需要验证。
您可以通过 set...
方法 final
来缓解大部分问题。无论如何,这是一个很好的做法。正如 Joshua Bloch 在他的书“Effective Java”中所说,第 17 项:
"Design and document for inheritance or else prohibit it"
这意味着你应该制作 every 方法 final
,除非你明确希望允许它被覆盖,并且 document 它应该如何被覆盖(或者,或者,使整个 class final
)。