Java – 如何只创建具有有效属性的对象?

我正在做一个基本的Java课程,我遇到了一个问题:如果我已经将有效参数传递给构造函数,我该如何创建一个对象?

在实现validation后,我应该创建一个替代类并从那里调用构造函数吗?

或者我应该/可以在类中使用静态方法进行validation吗?

在这种情况下,最佳做法是什么?

标准做法是validation构造函数中的参数。 例如:

class Range { private final int low, high; Range(int low, int high) { if (low > high) throw new IllegalArgumentException("low can't be greater than high"); this.low = low; this.high = high; } } 

附注:要validation参数不是null,这是相当常见的,您可以使用:

 import static java.util.Objects.requireNonNull; Constructor(Object o) { this.o = requireNonNull(o); //throws a NullPointerException if 'o' is null } 

UPDATE

回复您关于社会安全号码的具体评论。 一种方法是在类中添加一个方法:

 //constructor public YourClass(String ssn) { if (!isValidSSN(ssn)) throw new IllegalArgumentException("not a valid SSN: " + ssn); this.ssn = ssn; } public static boolean isValidSSN(String ssn) { //do some validation logic } 

调用代码可能如下所示:

 String ssn = getSsnFromUser(); while(!YourClass.isValidSSN(ssn)) { showErrorMessage("Not a valid ssn: " + ssn); ssn = getSsnFromUser(); } //at this point, the SSN is valid: YourClass yc = new YourClass(ssn); 

通过这种设计,您实现了两件事:

  • 你在使用它之前validation用户输入(你应该总是这样做 – 用户非常擅长打字错误)
  • 你已经确定如果YourClass被滥用,则抛出exception并且它将帮助你检测错误

您可以通过创建一个包含SSN的SSN类并封装validation逻辑来​​进一步发展。 然后, YourClass将接受SSN对象作为参数,该参数始终是构造的有效SSN。

我只是在构造函数本身抛出一个IllegalArgumentException

 public class MyClass { private int i; public MyClass (int i) { // example validation: if (i < 0) { throw new IllegalArgumentException ("i mustn't be negatve!"); } this.i = i; } 

编程中众所周知的真理是“不要使用例外进行流量控制”。 您的代码应该在调用构造函数之前了解这些限制并防范它们,而不是处理错误。 预期情况存在例外情况,尤其是那些无法预测或防范的情况(例如,尽管在之前的检查中可以正常,但IO流可能在写入期间变得无效)。

虽然您可以在构造函数中抛出exception,但这并不总是理想的。 如果您正在编写您希望被其他人使用/重用的公共对象,则exception是公共构造函数的唯一真正选项,但是这些限制及其结果(例如,将抛出的exception)应该在javadoc中明确记录。类。

对于内部类,断言更合适。 正如Oracle所述:“断言……应该用于检查不应该发生的情况,检查有关数据结构的假设,或对私有方法的参数强制执行约束。” – 在Java技术中使用断言 。 您可能仍应记录您对该类的期望,但您的应用程序应事先在内部进行任何检查,而不是依赖于抛出的任何exception。

静态工厂方法可以帮助一点,它们的好处正在详细阐述另一个问题: 如何使用“静态工厂方法”而不是构造函数 。 但是,当事情无效时(或返回null,信息量较少),它们不再提供强有力的validation选项。

您理想的解决方案是Builder模式 。 它不仅可以在管理参数时提供更大的灵活性,还可以单独validation每个参数,或者具有可以一次评估所有字段的validation方法。 构建器可以并且应该用于隐藏对象的实际构造函数,享受对它的唯一访问并防止任何不需要的值被提交,而断言可以防止“构建器永远不应该提交这些值”。

构造函数可以抛出exception(请参阅构造函数可以在Java中抛出exception吗? ),这样如果传递了无效值,您的构造函数就会抛出exception。 您还可以将构造函数设为私有,并使用静态方法创建执行检查的对象。 这可能更清洁。

确保传递给构造函数的有效参数的一种方法是使用仅接受所需参数的构造函数创建父类,然后创建最终用户使用的子类。 如果强制用户调用super()并传入所需的参数,那么他们必须至少传入正确的数据对象。 至于这些参数的有效值,由您决定是否要在父类构造函数中包含validation并抛出运行时exception或诸如此类的东西。

这是超类/子类的一个例子。 让我们称之为Superlcass SomeShape和子类Triangle 。 对于任何SomeShape对象,您将强制“用户”提供多个边和边长。 这就是……

 public class SomeShape { private int numSides; private int sideLength; public SomeShape(int mNumSides, int mSideLength) { numSides = mNumSides; sideLength = mSideLength; } } public class Triangle extends SomeShape { private int height; public Triangle(int mNumSides, int mSideLength, int mHeight) { super(mNumSides, mSideLength); height = mHeight; } } 

除了将一堆逻辑和exception硬编码到构造函数中之外,这是一种相对干净的方法来强制创建对象所需的参数。

如果您不想从构造函数中抛出exception,则可以使构造函数为private,并创建一个返回对象的新实例的静态方法,如果参数无效,则为null。 但是,此方法的调用者必须检查结果是否为null。

例:

 public class Foo { private Foo(int arg1, Bar arg2) { // guaranteed to be valid. } public static Foo construct(int arg1, Bar arg2) { // perform validation if (arg1 < 0 || arg2 == null) { return null; } else { return new Foo(arg1, arg2); } } } 

用法

 Foo object = Foo.construct(1, new Bar()); if (object == null) { // handle error here. } 

从构造函数中抛出exception是不好的做法。 你最终会得到一个部分初始化的对象,这可能会打破各种合同。

如果构造函数对所有输入组合无效,则创建一个执行validation的工厂方法并使构造函数成为私有方法更为清晰。 如果存在真正的失败可能性(即失败不是由编程错误引起的),则返回Optional可能是合适的。