我一直想知道在Java中处理多个构造函数的最佳方法(即最干净/最安全/最有效)是什么?特别是在一个或多个构造函数中,并未指定所有字段:
public class Book
{
private String title;
private String isbn;
public Book()
{
//nothing specified!
}
public Book(String title)
{
//only title!
}
...
}
未指定字段时该怎么办?到目前为止,我一直在使用类中的默认值,以便字段永远不为空,但这是一种“好”的做事方式吗?
答案 0 :(得分:136)
一个稍微简化的答案:
public class Book
{
private final String title;
public Book(String title)
{
this.title = title;
}
public Book()
{
this("Default Title");
}
...
}
答案 1 :(得分:34)
考虑使用Builder模式。它允许您在参数上设置默认值,并以简洁明了的方式初始化。例如:
Book b = new Book.Builder("Catcher in the Rye").Isbn("12345")
.Weight("5 pounds").build();
编辑:它还消除了对具有不同签名的多个构造函数的需求,并且更具可读性。
答案 2 :(得分:19)
您需要指定什么是类不变量,即对于类的实例始终为true的属性(例如,书的标题永远不会为null,或者狗的大小将始终为> ; 0)。
这些不变量应该在构造期间建立,并且在对象的生命周期中保留,这意味着方法不应该破坏不变量。构造函数可以通过使用强制参数或设置默认值来设置这些不变量:
class Book {
private String title; // not nullable
private String isbn; // nullable
// Here we provide a default value, but we could also skip the
// parameterless constructor entirely, to force users of the class to
// provide a title
public Book()
{
this("Untitled");
}
public Book(String title) throws IllegalArgumentException
{
if (title == null)
throw new IllegalArgumentException("Book title can't be null");
this.title = title;
// leave isbn without value
}
// Constructor with title and isbn
}
然而,这些不变量的选择在很大程度上取决于你正在写的课程,你将如何使用它等等,所以对你的问题没有明确的答案。
答案 3 :(得分:9)
您应该始终构建一个有效且合法的对象;如果你不能使用构造函数参数,你应该使用一个构建器对象来创建一个,只在对象完成时从构建器中释放对象。
关于构造函数的使用问题:我总是尝试使用一个所有其他人都遵循的基本构造函数,将“省略”参数链接到下一个逻辑构造函数并以基础构造函数结束。所以:
class SomeClass
{
SomeClass() {
this("DefaultA");
}
SomeClass(String a) {
this(a,"DefaultB");
}
SomeClass(String a, String b) {
myA=a;
myB=b;
}
...
}
如果这不可能,那么我尝试使用所有构造函数都遵循的私有init()方法。
并保持构造函数和参数的数量很少 - 每个最多5个作为指导。
答案 4 :(得分:6)
一些常规构造函数提示:
了解构造函数的初始化规则的顺序。基本上是这样的:
整体流程最终成为:
有关邪恶的一个很好的例子,请尝试弄清楚下面会打印什么,然后运行它
package com.javadude.sample;
/** THIS IS REALLY EVIL CODE! BEWARE!!! */
class A {
private int x = 10;
public A() {
init();
}
protected void init() {
x = 20;
}
public int getX() {
return x;
}
}
class B extends A {
private int y = 42;
protected void init() {
y = getX();
}
public int getY() {
return y;
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
System.out.println("x=" + b.getX());
System.out.println("y=" + b.getY());
}
}
我会添加评论,说明为什么上面的工作原理......其中一些可能很明显;有些不是......
答案 5 :(得分:3)
另一个考虑因素,如果需要字段或范围有限,请在构造函数中执行检查:
public Book(String title)
{
if (title==null)
throw new IllegalArgumentException("title can't be null");
this.title = title;
}
答案 6 :(得分:1)
可能值得考虑使用静态工厂方法而不是构造函数。
我说而不是,但显然你无法替换构造函数。但是,你可以做的是隐藏静态工厂方法背后的构造函数。这样,我们将静态工厂方法发布为类API的一部分,但同时我们隐藏了构造函数,使其成为私有或包私有。
这是一个相当简单的解决方案,特别是与Builder模式相比(如Joshua Bloch&#39> Effective Java 2nd Edition 中所见 - 请注意,四人帮' s 设计模式定义了一个完全不同的具有相同名称的设计模式,因此可能会有些混乱),这意味着创建嵌套类,构建器对象等。
这种方法在您和您的客户之间增加了一层额外的抽象,加强了封装并使更改变得更容易。它还为您提供实例控制 - 因为对象在类中实例化,您而不是客户端决定何时以及如何创建这些对象。
最后,它使测试更容易 - 提供一个愚蠢的构造函数,只是将值分配给字段,而不执行任何逻辑或验证,它允许您将无效状态引入系统以测试它的行为方式并对其作出反应。如果你在构造函数中验证数据,那么你将无法做到这一点。
你可以在(已经提到过)Joshua Bloch的 Effective Java 2nd Edition 中阅读更多相关内容 - 它是所有开发人员工具箱中的重要工具,难怪它是本书第一章的主题。 ; - )
按照你的例子:
public class Book {
private static final String DEFAULT_TITLE = "The Importance of Being Ernest";
private final String title;
private final String isbn;
private Book(String title, String isbn) {
this.title = title;
this.isbn = isbn;
}
public static Book createBook(String title, String isbn) {
return new Book(title, isbn);
}
public static Book createBookWithDefaultTitle(String isbn) {
return new Book(DEFAULT_TITLE, isbn);
}
...
}
无论您选择哪种方式,拥有一个主构造函数都是一种很好的做法,只需盲目地分配所有值,即使它只是被其他构造函数使用。< / p>
答案 7 :(得分:0)
我会做以下事情:
public class Book { private final String title; private final String isbn; public Book(final String t, final String i) { if(t == null) { throw new IllegalArgumentException("t cannot be null"); } if(i == null) { throw new IllegalArgumentException("i cannot be null"); } title = t; isbn = i; } }
我在这里假设:
1)标题永远不会改变(因此标题是最终的) 2)isbn永远不会改变(因此isbn是最终的) 3)没有标题和isbn的书都是无效的。
考虑学生班:
public class Student { private final StudentID id; private String firstName; private String lastName; public Student(final StudentID i, final String first, final String last) { if(i == null) { throw new IllegalArgumentException("i cannot be null"); } if(first == null) { throw new IllegalArgumentException("first cannot be null"); } if(last == null) { throw new IllegalArgumentException("last cannot be null"); } id = i; firstName = first; lastName = last; } }
必须使用id,名字和姓氏创建学生。学生ID永远不会改变,但是姓氏和姓氏可以改变(结婚,因失去赌注而更改姓名等)。
在决定什么样的建构时,你真的需要考虑什么才有意义。通常人们都会添加set / get方法,因为他们被教导 - 但通常这是一个坏主意。
不可变类比可变类更好(即具有最终变量的类)。本书:http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1(Effective Java)对不变性进行了很好的讨论。看看第12和第13项。
答案 8 :(得分:0)
有些人建议添加空检查。有时这是正确的做法,但并非总是如此。看看这篇优秀的文章,说明你跳过它的原因。
http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/