在 Java 中处理多个构造函数的最佳方法
Posted
技术标签:
【中文标题】在 Java 中处理多个构造函数的最佳方法【英文标题】:Best way to handle multiple constructors in Java 【发布时间】:2010-10-09 13:46:00 【问题描述】:我一直想知道在 Java 中处理多个构造函数的最佳(即最干净/最安全/最有效)的方法是什么?尤其是在一个或多个构造函数中没有指定所有字段时:
public class Book
private String title;
private String isbn;
public Book()
//nothing specified!
public Book(String title)
//only title!
...
没有指定字段怎么办?到目前为止,我一直在类中使用默认值,以便字段永远不会为空,但这是一种“好”的做事方式吗?
【问题讨论】:
视情况而定,是否需要所有字段都包含值? 我不喜欢人们提出问题然后不接受答案。 【参考方案1】:稍微简化的答案:
public class Book
private final String title;
public Book(String title)
this.title = title;
public Book()
this("Default Title");
...
【讨论】:
+1 我发现所有构造函数都应该通过一个共同的“阻塞点”是一个很好的经验法则。 另外,总是建议用 this() 或 super() =8-) 开始每个构造函数 +1 用于构造函数链接 - 对于额外的标记,“title”可能被声明为 final,因为它在 Book 对象的生命周期内不太可能改变(不变性非常好!)。 如果 title 在构造函数中为 null,您可能还应该抛出异常,这样您就可以保证它在方法中永远不会为 null(这可能非常有用)。这是一个尽可能做的好习惯(final + not-null)。 小心空检查。 misko.hevery.com/2009/02/09/to-assert-or-not-to-assert【参考方案2】:考虑使用 Builder 模式。它允许您设置参数的默认值并以清晰简洁的方式进行初始化。例如:
Book b = new Book.Builder("Catcher in the Rye").Isbn("12345")
.Weight("5 pounds").build();
编辑:它还消除了对具有不同签名的多个构造函数的需求,并且更具可读性。
【讨论】:
如果您有多个构造函数,这是最好的主意,您可以轻松扩展新类型并且不易出错 @Dinesh - 我同意,我在我的代码中使用了构建器。我喜欢这种模式! 在我看来,它就像一个流畅的界面 (codemonkeyism.com/archives/2007/10/10/…) 但是,如果您想扩展 Builder,它可能会有点麻烦 (***.com/questions/313729/…)。【参考方案3】:您需要指定什么是类不变量,即对于类的实例始终为真的属性(例如,一本书的标题永远不会为空,或者狗的大小永远是> 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
但是,这些不变量的选择很大程度上取决于您正在编写的类、您将如何使用它等,因此您的问题没有明确的答案。
【讨论】:
你想检查 null 没有被传递给第二个构造函数。【参考方案4】:另一个考虑,如果一个字段是必需的或有一个有限的范围,在构造函数中执行检查:
public Book(String title)
if (title==null)
throw new IllegalArgumentException("title can't be null");
this.title = title;
【讨论】:
您应该为所有公共方法执行此操作。始终检查参数,它会为您省去一些麻烦。【参考方案5】:一些通用的构造函数提示:
尝试将所有初始化集中在一个构造函数中,并从其他构造函数中调用它 如果存在多个构造函数来模拟默认参数,这会很有效 永远不要从构造函数调用非最终方法 根据定义,私有方法是最终的 多态可以杀死你;您最终可以在子类初始化之前调用子类实现 如果您需要“帮助”方法,请务必将它们设为私有或最终方法 在调用 super() 时要明确 您会惊讶于有多少 Java 程序员没有意识到 super() 被调用,即使您没有明确编写它(假设您没有调用 this(...))李>了解构造函数的初始化规则顺序。基本上是:
-
this(...) 如果存在(只是移动到另一个构造函数)
调用 super(...) [如果不显式,则隐式调用 super()]
(递归地使用这些规则构造超类)
通过声明初始化字段
当前构造函数的运行体
返回之前的构造函数(如果你遇到过 this(...) 调用)
整个流程最终是:
一直向上移动超类层次结构到 Object 尚未完成时 初始化字段 运行构造函数体 下拉到子类对于邪恶的一个很好的例子,试着弄清楚下面会打印什么,然后运行它
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());
我将添加 cmets 来描述为什么上面的工作原理......其中一些可能是显而易见的;有些不是……
【讨论】:
1.创建 B 时永远不会调用 A 的 init() 方法(B 覆盖它) 2. B的init()方法是从A的构造函数中调用的 B 的 init() 被调用在 B 的初始化器被调用!。 y 被分配给 42 之后 它在 init() 中分配 永远不要说永远 - 有些地方从构造函数调用受保护的方法是完全正确的做法,以便故意将一些构造细节推迟到子类中。 不——我会坚持“从不”。如果您想将构造细节推迟到子类,那就是子类构造函数的用途。从构造函数调用非final的问题是子类还没有被初始化——只是不安全!【参考方案6】:我会做以下事情:
公共课本 私有最终字符串标题; 私有最终字符串 isbn; 公共书(最终字符串 t,最终字符串 i) 如果(t == 空) throw new IllegalArgumentException("t 不能为 null"); 如果(我 == 空) throw new IllegalArgumentException("我不能为空"); 标题 = t; isbn = 我;我在这里假设:
1) 标题永远不会改变(因此标题是最终的) 2) isbn 永远不会改变(因此 isbn 是最终的) 3) 没有书名和 isbn 的书是无效的。
考虑一个学生类:
公开课学生 私人最终学生ID; 私人字符串名; 私人字符串姓氏; 公共学生(最终学生ID i, 最终字符串优先, 最后一个字符串最后) 如果(我 == 空) throw new IllegalArgumentException("我不能为空"); 如果(第一个 == 空) throw new IllegalArgumentException("first cannot be null"); 如果(最后一个 == 空) throw new IllegalArgumentException("last 不能为 null"); 身份证=我; 名字=第一; 姓氏 = 最后一个;必须创建一个带有 id、名字和姓氏的 Student。学生证永远不会改变,但一个人的姓氏和名字可以改变(结婚、因赌输而改名等......)。
在决定拥有哪些构造函数时,您真正需要考虑拥有什么是有意义的。人们经常添加 set/get 方法,因为他们被教导要这样做 - 但通常这是一个坏主意。
不可变类(即具有最终变量的类)比可变类要好得多。这本书:http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1(Effective Java)对不变性有很好的讨论。查看第 12 和 13 项。
【讨论】:
为什么要把参数定为final? 最终参数使您不会意外地做 t = title 例如。【参考方案7】:一些人建议添加一个空检查。有时这是正确的做法,但并非总是如此。查看这篇出色的文章,了解您为什么要跳过它。
http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/
【讨论】:
【参考方案8】:您应该始终构造一个有效且合法的对象;如果你不能使用构造器参数,你应该使用构建器对象来创建一个,只有在对象完成时才从构建器中释放对象。
关于构造函数的使用问题:我总是尝试使用一个所有其他构造函数都遵循的基本构造函数,通过“省略”参数链接到下一个逻辑构造函数并在基本构造函数处结束。所以:
class SomeClass
SomeClass()
this("DefaultA");
SomeClass(String a)
this(a,"DefaultB");
SomeClass(String a, String b)
myA=a;
myB=b;
...
如果这是不可能的,那么我尝试拥有一个所有构造函数都遵循的私有 init() 方法。
并保持构造函数和参数的数量较少 - 作为指导,每个最多 5 个。
【讨论】:
是的,您的构造函数中有五个以上的参数,您应该评估实现构建器对象等...有关更多详细信息,请参阅有效 Java...【参考方案9】:可能值得考虑使用静态工厂方法而不是构造函数。
我说的是而不是,但显然你不能替换构造函数。但是,您可以做的是将构造函数隐藏在静态工厂方法后面。这样,我们将静态工厂方法作为类 API 的一部分发布,但同时隐藏构造函数,使其成为私有或包私有。
这是一个相当简单的解决方案,尤其是与 Builder 模式相比(如 Joshua Bloch 的 Effective Java 2nd Edition 中所见——请注意,Gang of Four 的 Design Patterns 定义了一个具有相同名称的完全不同的设计模式,因此可能会有些混乱)这意味着创建嵌套类、构建器对象等。
这种方法在您和您的客户之间增加了一个额外的抽象层,从而加强了封装性并使更改变得更容易。它还为您提供实例控制——因为对象是在类中实例化的,所以您而不是客户端决定何时以及如何创建这些对象。
最后,它使测试变得更容易——提供了一个愚蠢的构造函数,它只是将值分配给字段,而不执行任何逻辑或验证,它允许您将无效状态引入系统以测试它的行为和反应方式.如果您在构造函数中验证数据,您将无法做到这一点。
您可以在(已经提到的)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);
...
无论您选择哪种方式,最好有一个 main 构造函数,它只是盲目地分配所有值,即使它只是被另一个构造函数使用。
【讨论】:
以上是关于在 Java 中处理多个构造函数的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章