Java - 如何仅创建具有有效属性的对象?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30803650/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java - How to only create an object with valid attributes?
提问by Tiago Duque
I'm doing a basic Java course and I came to a problem: How do I create an object only if I have passed valid parameters to the Constructor?
我正在学习基本的 Java 课程,但遇到了一个问题:如何仅在将有效参数传递给构造函数的情况下创建对象?
Should I make an alternate class and call the constructor from there after the validation is realized?
我应该创建一个替代类并在实现验证后从那里调用构造函数吗?
Or should/could I use a static method in the class for the validation?
或者我应该/可以在类中使用静态方法进行验证吗?
What is the best practice in this case?
在这种情况下,最佳做法是什么?
回答by assylias
The standard practice is to validate the arguments in the constructor. For example:
标准做法是验证构造函数中的参数。例如:
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;
}
}
Side note: to verify that arguments are not null, which is fairly common, you can use:
旁注:要验证参数不为空,这很常见,您可以使用:
import static java.util.Objects.requireNonNull;
Constructor(Object o) {
this.o = requireNonNull(o); //throws a NullPointerException if 'o' is null
}
UPDATE
更新
To reply to your specific comment about social security number. One way would be to add a method to the class:
回复您关于社会安全号码的具体评论。一种方法是向类添加一个方法:
//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
}
The calling code could then look like:
调用代码可能如下所示:
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);
With that design, you have achieved two things:
通过该设计,您实现了两件事:
- you validate the user input before using it (which you should always do - users are very good at typos)
- you have made sure that if
YourClass
is misused an exception is thrown and it will help you detect bugs
- 您在使用之前验证用户输入(您应该始终这样做 - 用户非常擅长打字错误)
- 你已经确定如果
YourClass
被误用会抛出异常,它会帮助你检测错误
You could go further by creating a SSN
class that holds the SSN and encapsulates the validation logic. YourClass
would then accept a SSN
object as an argument which is always a valid SSN by construction.
您可以通过创建一个SSN
包含 SSN 并封装验证逻辑的类来更进一步。YourClass
然后将接受一个SSN
对象作为参数,该对象在构造上始终是有效的 SSN。
回答by Mureinik
I'd just throw an IllegalArgumentException
in the constructor itself:
我只是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;
}
回答by Danikov
A well-known truism in programming is 'Don't use Exceptions for flow control'. Your code should be aware of the restrictions and guard against them before calling the constructor rather than handling errors. Exceptions exist for expectational circumstances, especially ones that cannot be predicted or guarded against (for example, an IO stream may become invalid during writing, despite being OK during a previous check).
编程中的一个众所周知的真理是“不要将异常用于流量控制”。在调用构造函数而不是处理错误之前,您的代码应该了解这些限制并加以防范。预期情况存在例外,尤其是无法预测或防范的情况(例如,IO 流在写入过程中可能变得无效,尽管在之前的检查中是 OK 的)。
While you can throw exceptions in your constructor, this is not always ideal. If you are writing public objects that you expect to be used/reused by others, Exceptions are the only real option for public constructors, however such limitations and their result (e.g. what exception will be thrown) should be clearly documented in the javadoc for the class.
虽然您可以在构造函数中抛出异常,但这并不总是理想的。如果您正在编写希望被其他人使用/重用的公共对象,则异常是公共构造函数的唯一真正选择,但是此类限制及其结果(例如将抛出什么异常)应在 javadoc 中清楚地记录在班级。
For internal classes, assertions are more appropriate. As Oracle states: "Assertions... should be used to check for cases that should never happen, check assumptions about data structures, or enforcing constraints on arguments of private methods."—Using Assertions in Java Technology. You probably should still document your expectations for the class, but your application should internally do any checks beforehand rather than relying on any Exceptions being thrown.
对于内部类,断言更合适。正如 Oracle 所说:“断言...应该用于检查不应该发生的情况,检查有关数据结构的假设,或对私有方法的参数实施约束。”—在 Java 技术中使用断言。您可能仍然应该记录您对类的期望,但是您的应用程序应该在内部预先进行任何检查,而不是依赖于抛出的任何异常。
Static factory methods can help a little, their benefits are elaborating upon a bit by another question: How to use “Static factory methods” instead of constructors. However, they don't give strong validation options without, again, relying on Exceptions when things are not valid (that, or returning null, which is less informative).
静态工厂方法可以提供一点帮助,它们的好处正在通过另一个问题详细说明:如何使用“静态工厂方法”而不是构造函数。但是,它们不会提供强大的验证选项,并且在事情无效时再次依赖异常(即,或返回 null,信息较少)。
Your ideal solution is the Builder pattern. Not only does it allow for a greater deal of flexibility in managing your arguments, you may validate each one individually, or have a validate method that can evaluate all the fields at once. A builder can and should be used to hide the object's actual constructor, enjoying sole access to it and preventing any unwanted values from ever being submitted, while assertions can guard against 'the builder should never submit these values'.
您的理想解决方案是Builder 模式。它不仅在管理参数方面提供了更大的灵活性,您还可以单独验证每个参数,或者拥有一个可以一次评估所有字段的验证方法。构建器可以并且应该用于隐藏对象的实际构造函数,享受对其的唯一访问权并防止提交任何不需要的值,而断言可以防止“构建器不应该提交这些值”。
回答by JP Moresmau
Constructors can throw exceptions (see Can constructors throw exceptions in Java?) so you can have your constructor throwing an exception if invalid values are passed. You can also make your constructor private and use the static method to create your object, that performs the checks. This might be cleaner.
构造函数可以抛出异常(请参阅Java 中的构造函数能否抛出异常?),因此您可以让构造函数在传递无效值时抛出异常。您还可以将构造函数设为私有并使用静态方法创建执行检查的对象。这可能更干净。
回答by Chamatake-san
One way to make sure you have valid parameters passed to the constructor is to create the parent class with constructors that only accept the parameters you require, then create a subclass that your end-users use. If you force your user to call super() and pass in your required parameters, then they have to at least pass in the right data objects. As far as valid values for those parameters, that's up to you whether you want to include validation in the parent class constructor and throw runtime exceptions or whatnot.
确保将有效参数传递给构造函数的一种方法是使用仅接受所需参数的构造函数创建父类,然后创建最终用户使用的子类。如果你强迫你的用户调用 super() 并传入你需要的参数,那么他们至少必须传入正确的数据对象。至于这些参数的有效值,这取决于您是否要在父类构造函数中包含验证并抛出运行时异常或诸如此类。
Here's an example of the superclass / subclass thing. Let's call the superlcass SomeShape
and the subclass Triangle
. For any SomeShape
object, you are going to force the "user" to provide a number of sides and a side length. This is how...
这是超类/子类事物的示例。让我们调用 superlcassSomeShape
和 subclass 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;
}
}
Aside from hard-coding a bunch of logic and exception throwing into your constructor, this is a relatively clean way to enforce what parameters are required to create the object.
除了将一堆逻辑和异常抛出硬编码到构造函数中之外,这是一种相对干净的方法来强制执行创建对象所需的参数。
回答by MusicMaster
If you don't want to throw an exception from the constructor, you could make the constructor private and create a static method that returns a new instance of the object, or null if the arguments are invalid. The caller of this method would have to check if the result is null or not, however.
如果您不想从构造函数抛出异常,您可以将构造函数设为私有并创建一个返回对象新实例的静态方法,如果参数无效,则返回 null。但是,此方法的调用者必须检查结果是否为空。
Example:
例子:
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);
}
}
}
Usage
用法
Foo object = Foo.construct(1, new Bar());
if (object == null) {
// handle error here.
}
回答by MattPutnam
It's bad practice to throw an exception out of a constructor. You end up with a partially initialized object, which is probably going to break all kinds of contracts.
从构造函数中抛出异常是不好的做法。您最终会得到一个部分初始化的对象,这可能会破坏各种合同。
If a constructor isn't valid for all combinations of inputs, it's cleaner to create a factory method that does the validation, and make the constructor private. If there's a real possibility of failure (that is, the failure isn't due to a programming error), then it might be appropriate to return an Optional
.
如果构造函数对所有输入组合都无效,则创建一个进行验证的工厂方法并将构造函数设为私有会更简洁。如果确实存在失败的可能性(也就是说,失败不是由于编程错误造成的),那么返回一个Optional
.