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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/581873/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:30:06  来源:igfitidea点击:

Best way to handle multiple constructors in Java

javaconstructorfield

提问by

I've been wondering what the best (i.e. cleanest/safest/most efficient) way of handling multiple constructors in Java is? Especially when in one or more constructors not all fields are specified:

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

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

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

    ...     

}

What should I do when fields are not specified? I've so far been using default values in the class so that a field is never null, but is that a "good" way of doing things?

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

回答by Craig P. Motlin

A slightly simplified answer:

一个稍微简化的答案:

public class Book
{
    private final String title;

    public Book(String title)
    {
      this.title = title;
    }

    public Book()
    {
      this("Default Title");
    }

    ...
}

回答by kgrad

Consider using the Builder pattern. It allows for you to set default values on your parameters and initialize in a clear and concise way. For example:

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


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

Edit: It also removes the need for multiple constructors with different signatures and is way more readable.

编辑:它还消除了对具有不同签名的多个构造函数的需要,并且更具可读性。

回答by Luc Touraille

You need to specify what are the class invariants, i.e. properties which will always be true for an instance of the class (for example, the title of a book will never be null, or the size of a dog will always be > 0).

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

These invariants should be established during construction, and be preserved along the lifetime of the object, which means that methods shall not break the invariants. The constructors can set these invariants either by having compulsory arguments, or by setting default values:

这些不变量应该在构造过程中建立,并在对象的生命周期中保留,这意味着方法不应破坏不变量。构造函数可以通过强制参数或设置默认值来设置这些不变量:

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
}

However, the choice of these invariants highly depends on the class you're writing, how you'll use it, etc., so there's no definitive answer to your question.

但是,这些不变量的选择在很大程度上取决于您正在编写的类、您将如何使用它等,因此您的问题没有明确的答案。

回答by Steve Kuo

Another consideration, if a field is required or has a limited range, perform the check in the constructor:

另一个注意事项,如果某个字段是必需的或范围有限,请在构造函数中执行检查:

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

回答by Scott Stanchfield

Some general constructor tips:

一些通用的构造器提示:

  • Try to focus all initialization in a single constructor and call it from the other constructors
    • This works well if multiple constructors exist to simulate default parameters
  • Never call a non-final method from a constructor
    • Private methods are final by definition
    • Polymorphism can kill you here; you can end up calling a subclass implementation before the subclass has been initialized
    • If you need "helper" methods, be sure to make them private or final
  • Be explicit in your calls to super()
    • You would be surprised at how many Java programmers don't realize that super() is called even if you don't explicitly write it (assuming you don't have a call to this(...) )
  • Know the order of initialization rules for constructors. It's basically:

    1. this(...) if present (justmove to another constructor)
    2. call super(...) [if not explicit, call super() implicitly]
    3. (construct superclass using these rules recursively)
    4. initialize fields via their declarations
    5. run body of current constructor
    6. return to previous constructors (if you had encountered this(...) calls)
  • 尝试将所有初始化集中在一个构造函数中并从其他构造函数调用它
    • 如果存在多个构造函数来模拟默认参数,这很有效
  • 永远不要从构造函数调用非最终方法
    • 私有方法根​​据定义是最终的
    • 多态可以在这里杀死你;您可以在子类初始化之前调用子类实现
    • 如果您需要“辅助”方法,请确保将它们设为私有或最终
  • 在调用 super() 时要明确
    • 您会惊讶于有多少 Java 程序员没有意识到 super() 被调用,即使您没有明确地编写它(假设您没有调用 this(...) )
  • 了解构造函数初始化规则的顺序。它基本上是:

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

The overall flow ends up being:

整个流程最终是:

  • move all the way up the superclass hierarchy to Object
  • while not done
    • init fields
    • run constructor bodies
    • drop down to subclass
  • 一直向上移动超类层次结构到对象
  • 虽然没有完成
    • 初始化字段
    • 运行构造函数体
    • 下拉到子类

For a nice example of evil, try figuring out what the following will print, then run it

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

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

I'll add comments describing why the above works as it does... Some of it may be obvious; some is not...

我将添加评论来描述为什么上述工作如此有效......其中一些可能是显而易见的;有些不是……

回答by TofuBeer

I would do the following:

我会做以下事情:

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

I am making the assumption here that:

我在这里假设:

1) the title will never change (hence title is final) 2) the isbn will never change (hence isbn is final) 3) that it is not valid to have a book without both a title and an isbn.

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

Consider a Student class:

考虑一个学生类:

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

There a Student must be created with an id, a first name, and a last name. The student ID can never change, but a persons last and first name can change (get married, changes name due to losing a bet, etc...).

必须使用 id、名字和姓氏创建学生。学生证永远不能改变,但一个人的姓氏和名字可以改变(结婚、因赌输而改名等......)。

When deciding what constrructors to have you really need to think about what makes sense to have. All to often people add set/get methods because they are taught to - but very often it is a bad idea.

在决定使用什么构造函数时,您确实需要考虑使用什么是有意义的。人们经常添加 set/get 方法,因为他们被教导 - 但通常这是一个坏主意。

Immutable classes are much better to have (that is classes with final variables) over mutable ones. This book: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1(Effective Java) has a good discussion on immutability. Look at items 12 and 13.

不可变类(即具有最终变量的类)比可变类要好得多。这本书:http: //books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1(Effective Java)对不变性有很好的讨论。查看第 12 项和第 13 项。

回答by Craig P. Motlin

Several people have recommended adding a null check. Sometimes that's the right thing to do, but not always. Check out this excellent article showing why you'd skip it.

有几个人建议添加空检查。有时这是正确的做法,但并非总是如此。查看这篇出色的文章,了解您为什么要跳过它。

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

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

回答by Lawrence Dol

You should always construct a valid and legitimate object; and if you can't using constructor parms, you should use a builder object to create one, only releasing the object from the builder when the object is complete.

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

On the question of constructor use: I always try to have one base constructor that all others defer to, chaining through with "omitted" parameters to the next logical constructor and ending at the base constructor. So:

关于构造函数使用的问题:我总是尝试使用一个所有其他构造函数都遵循的基本构造函数,将“省略”参数链接到下一个逻辑构造函数并在基本构造函数处结束。所以:

class SomeClass
{
SomeClass() {
    this("DefaultA");
    }

SomeClass(String a) {
    this(a,"DefaultB");
    }

SomeClass(String a, String b) {
    myA=a;
    myB=b;
    }
...
}

If this is not possible, then I try to have an private init() method that all constructors defer to.

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

And keep the number of constructors and parameters small - a max of 5 of each as a guideline.

并保持构造函数和参数的数量较少——每个最多 5 个作为准则。

回答by Krzysiek Przygudzki

It might be worth considering the use of a static factory method instead of constructor.

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

I'm saying instead, but obviously you can't replacethe constructor. What you can do, though, is hide the constructor behind a static factory method. This way, we publish the static factory method as a part of the class API but at the same time we hide the constructor making it private or package private.

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

It's a reasonably simple solution, especially in comparison with the Builder pattern (as seen in Joshua Bloch's Effective Java 2nd Edition– beware, Gang of Four's Design Patternsdefine a completely different design pattern with the same name, so that might be slightly confusing) that implies creating a nested class, a builder object, etc.

这是一个相当简单的解决方案,尤其是与 Builder 模式相比(如 Joshua Bloch 的Effective Java 2nd Edition 中所见- 请注意,Gang of Four 的设计模式定义了一个完全不同的同名设计模式,因此可能会有点混乱)意味着创建嵌套类、构建器对象等。

This approach adds an extra layer of abstraction between you and your client, strengthening encapsulation and making changes down the road easier. It also gives you instance-control – since the objects are instantiated inside the class, you and not the client decide when and how these objects are created.

这种方法在您和您的客户之间增加了一个额外的抽象层,加强了封装并使以后的更改更容易。它还为您提供实例控制——因为对象是在类内部实例化的,您而不是客户端决定何时以及如何创建这些对象。

Finally, it makes testing easier – providing a dumb constructor, that just assigns the values to the fields, without performing any logic or validation, it allows you to introduce invalid state into your system to test how it behaves and reacts to that. You won't be able to do that if you're validating data in the constructor.

最后,它使测试更容易——提供一个愚蠢的构造函数,它只是将值分配给字段,而不执行任何逻辑或验证,它允许您将无效状态引入系统以测试它的行为和反应。如果您在构造函数中验证数据,您将无法做到这一点。

You can read much more about that in (already mentioned) Joshua Bloch's Effective Java 2nd Edition– it's an important tool in all developer's toolboxes and no wonder it's the subject of the 1st chapter of the book. ;-)

您可以在(已经提到的)Joshua Bloch 的Effective Java 2nd Edition 中阅读更多相关内容——它是所有开发人员工具箱中的一个重要工具,难怪它是本书第一章的主题。;-)

Following your example:

按照你的例子:

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

    ...

}

}

Whichever way you choose, it's a good practice to have one mainconstructor, that just blindly assigns all the values, even if it's just used by another constructors.

无论您选择哪种方式,拥有一个构造函数都是一种很好的做法,它只是盲目地分配所有值,即使它只是被另一个构造函数使用。