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

我一直在想,在Java中处理多个构造函数的最好方法(最干净/最安全/最有效)是什么? 特别是当在一个或多个构造函数中并不是所有的字段都被指定时

public class Book { private String title; private String isbn; public Book() { //nothing specified! } public Book(String title) { //only title! } ... } 

当字段没有被指定时我应该怎么做? 我到目前为止一直在类中使用默认值,这样一个字段永远不会为空,但这是一个“好”的做事方式?

一个简单的答案:

 public class Book { private final String title; public Book(String title) { this.title = title; } public Book() { this("Default Title"); } ... } 

考虑使用Builder模式。 它允许您在参数上设置默认值,并以清晰简洁的方式进行初始化。 例如:

 Book b = new Book.Builder("Catcher in the Rye").Isbn("12345") .Weight("5 pounds").build(); 

编辑:它也消除了不同签名的多个构造函数的需要,方式更具可读性。

您需要指定什么是类不variables,即对类的实例始终为真的属性(例如,书的标题永远不会为空,或者狗的大小将始终为> 0)。

这些不variables应该在施工过程中build立,并且在物体的整个生命周期中保存下来,这意味着方法不应该破坏不variables。 构造函数可以通过强制参数或通过设置默认值来设置这些不variables:

 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 } 

然而,这些不variables的select很大程度上取决于你正在写的课程,你将如何使用它,所以你的问题没有明确的答案。

您应该始终构build一个有效的合法对象; 并且如果不能使用构造函数参数,则应该使用构build器对象来创build一个,只有在对象完成时才从构build器释放该对象。

关于构造函数的使用问题:我总是试着让所有其他的构造函数都遵循这个构造函数,通过“省略”参数链接到下一个逻辑构造函数,并以基础构造函数结束。 所以:

 class SomeClass { SomeClass() { this("DefaultA"); } SomeClass(String a) { this(a,"DefaultB"); } SomeClass(String a, String b) { myA=a; myB=b; } ... } 

如果这是不可能的,那么我尝试使用所有构造函数都遵循的私有init()方法。

并保持构造函数和参数的数量小 – 每个最多5个作为指导。

一些一般的构造函数技巧:

  • 尝试将所有初始化集中在一个构造函数中,并从其他构造函数中调用它
    • 如果存在多个构造函数来模拟默认参数,这个效果很好
  • 切勿从构造函数中调用非final方法
    • 私人方法是最终的定义
    • 多态可以在这里杀死你; 您可以最终在子类初始化之前调用子类实现
    • 如果你需要“帮手”的方法,一定要使他们私人或最终
  • 在你的super()调用中明确表示
    • 你会惊讶于有多lessJava程序员没有意识到super()被调用,即使你没有明确的写(假设你没有调用这个(…))
  • 知道构造函数的初始化规则的顺序。 这基本上是:

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

整体stream程结束于:

  • 将超类层次结构一直移动到Object
  • 而没有完成
    • init字段
    • 运行构造函数体
    • 下拉到子类

对于邪恶的一个很好的例子,试着弄清楚下面会打印什么,然后运行它

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

我将添加评论,说明为什么上述工作,因为它…有些可能是显而易见的; 有些不是…

另一个考虑,如果一个字段是必需的或有一个有限的范围,在构造函数中执行检查:

 public Book(String title) { if (title==null) throw new IllegalArgumentException("title can't be null"); this.title = title; } 

我会做以下几点:

公共课本
 {
    私人最终string标题;
    私人最终stringisbn;

    公共图书(最终stringt,最终stringi)
     {
        如果(t == null)
         {
            抛出新的IllegalArgumentException(“t不能为null”);
         }

        如果(i == null)
         {
            抛出新的IllegalArgumentException(“我不能为null”);
         }

         title = t;
         isbn = i;
     }
 }

我在这里假设:

1)标题永远不会改变(因此标题是最后的)2)isbn永远不会改变(因此isbn是最终的)3)没有标题和isbn的书是无效的。

考虑一个学生class:

公开课学生
 {
    私人最终学生ID号;
    私人string名字;
    私人string姓氏;

    公共学生(最终StudentID我,
                   最后string第一,
                   最后一个String最后)
     {
        如果(i == null)
         {
            抛出新的IllegalArgumentException(“我不能为null”); 
         }

        如果(first == null)
         {
            抛出新的IllegalArgumentException(“首先不能为null”); 
         }

        如果(last == null)
         {
            抛出新的IllegalArgumentException(“最后不能为null”); 
         }

         id = i;
         firstName = first;
         lastName = last;
     }
 }

有一个学生必须创build一个ID,一个名字,和一个姓氏。 学生证不能改变,但最后一个名字可能会改变(结婚,因失败而改名)等等。

当决定什么是constrructors你真的需要考虑什么是有道理的。 所有经常人们添加set / get方法,因为他们被教导 – 但很多时候这是一个坏主意。

不可变的类更好(具有最终variables的类)优于可变类。 本书: http : //books.google.com/books? id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1(Effective Java)对于不变性有很好的讨论。 看项目12和13。

有几个人build议添加一个空检查。 有时候这是正确的做法,但并非总是如此。 看看这个优秀的文章,显示为什么你会跳过它。

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/

可能值得考虑使用静态工厂方法而不是构造函数。

我只是说,但显然你不能取代构造函数。 但是,您可以做的是将构造函数隐藏在静态工厂方法的后面。 这样,我们将静态工厂方法作为类API的一部分发布,但同时我们隐藏构造函数使其成为私有的或私有的。

这是一个相当简单的解决scheme,尤其是与Builder模式相比(如Joshua Bloch的Effective Java第2版所示 – 要小心,四人组的devise模式定义了一个完全不同的同名devise模式,所以可能会有些混淆)意味着创build一个嵌套类,一个构build器对象等

这种方法为您和您的客户之间增加了一个额外的抽象层,加强了封装并使得更改变得更容易。 它也给你实例控制 – 因为对象是在类中实例化的,所以你而不是客户决定何时以及如何创build这些对象。

最后,它使得testing变得更简单 – 提供一个愚蠢的构造函数,只是将值分配给字段,而不执行任何逻辑或validation,它允许您将无效状态引入到系统中,以testing其行为并对其做出反应。 如果您在构造函数中validation数据,那么您将无法做到这一点。

在Joshua Bloch的Effective Java第2版中 ,您可以阅读更多关于(已经提到)的内容 – 这是所有开发人员工具箱中的重要工具,难怪这是本书第1章的主题。 😉

遵循你的例子:

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

}

无论您select哪种方式,最好有一个构造函数,即使只是盲目分配所有的值,即使它只是被另一个构造函数使用。