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

时间:2015-06-12 12:55:07

标签: java validation object constructor

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

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

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

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

7 个答案:

答案 0 :(得分:28)

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

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;
  }
}

附注:要验证参数不是null,这很常见,您可以使用:

import static java.util.Objects.requireNonNull;

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

更新

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

//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);

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

  • 您在使用之前验证了用户输入(您应该始终这样做 - 用户非常擅长打字错误)
  • 您确定如果YourClass被误用,则会抛出异常并且它会帮助您检测错误

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

答案 1 :(得分:9)

我只是在构造函数本身中抛出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;
}

答案 2 :(得分:7)

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

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

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

静态工厂方法可以帮助一点,它们的好处正在详细阐述另一个问题:How to use “Static factory methods” instead of constructors。但是,如果事情无效(或者返回null,信息量较少),它们就不会提供强有力的验证选项,而不会依赖异常。

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

答案 3 :(得分:4)

构造函数可以抛出异常(请参阅Can constructors throw exceptions in Java?),以便在传递无效值时让构造函数抛出异常。您还可以将构造函数设置为私有,并使用静态方法创建执行检查的对象。这可能更清洁。

答案 4 :(得分:1)

确保传递给构造函数的有效参数的一种方法是使用仅接受所需参数的构造函数创建父类,然后创建最终用户使用的子类。如果强制用户调用super()并传入所需的参数,那么他们必须至少传入正确的数据对象。就这些参数的有效值而言,您是否希望在父类构造函数中包含验证并抛出运行时异常或其他内容,这取决于您。

这是超类/子类事物的一个例子。让我们调用superlcass SomeShape和子类Triangle。对于任何SomeShape对象,您将强制使用&#34; user&#34;提供多个边和边长。这就是......

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;
    }   
}

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

答案 5 :(得分:0)

如果您不想从构造函数中抛出异常,则可以使构造函数为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.
}

答案 6 :(得分:-2)

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

如果构造函数对所有输入组合都不有效,那么创建一个进行验证的工厂方法会更加清晰,并使构造函数变为私有。如果真的有失败的可能性(也就是说,由于编程错误导致失败),那么返回Optional可能是合适的。