在Java中处理多个构造函数的最佳方法

时间:2009-02-24 14:10:23

标签: java constructor field

我一直想知道在Java中处理多个构造函数的最佳方法(即最干净/最安全/最有效)是什么?特别是在一个或多个构造函数中,并未指定所有字段:

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

    public Book(String title)
    {
      //only title!
    }

    ...     

}

未指定字段时该怎么办?到目前为止,我一直在使用类中的默认值,以便字段永远不为空,但这是一种“好”的做事方式吗?

9 个答案:

答案 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)

一些常规构造函数提示:

  • 尝试将所有初始化集中在一个构造函数中,并从其他构造函数中调用它
    • 如果存在多个构造函数来模拟默认参数
    • ,这很有效
  • 永远不要从构造函数中调用非final方法
    • 根据定义,私人方法是最终的
    • 多态性可以在这里杀死你;你可以在初始化子类之前调用​​子类实现
    • 如果您需要“帮助”方法,请务必将其设为私有或最终
  • 明确调用super()
    • 你会惊讶于有多少Java程序员没有意识到super()被调用,即使你没有明确地写它(假设你没有调用它(...))
  • 了解构造函数的初始化规则的顺序。基本上是这样的:

    1. this(...)如果存在(只是移动到另一个构造函数)
    2. 调用super(...)[如果不显式,则隐式调用super()]
    3. (递归使用这些规则构造超类)
    4. 通过声明初始化字段
    5. 运行当前构造函数的主体
    6. 返回上一个构造函数(如果遇到此(...)调用)

整体流程最终成为:

  • 一直向上移动超类层次结构到Object
  • 虽然没有完成
    • init fields
    • 运行构造函数体
    • 下拉到子类

有关邪恶的一个很好的例子,请尝试弄清楚下面会打印什么,然后运行它

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/