Best way to handle multiple constructors in Java

后端 未结 9 1438
没有蜡笔的小新
没有蜡笔的小新 2020-12-02 07:00

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 al

相关标签:
9条回答
  • 2020-12-02 07:33

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

    I'm saying instead, but obviously you can't replace the 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.

    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 Patterns define 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.

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

    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 main constructor, that just blindly assigns all the values, even if it's just used by another constructors.

    0 讨论(0)
  • 2020-12-02 07:33

    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/

    0 讨论(0)
  • 2020-12-02 07:37

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

    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.

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-02 07:42

    A slightly simplified answer:

    public class Book
    {
        private final String title;
    
        public Book(String title)
        {
          this.title = title;
        }
    
        public Book()
        {
          this("Default Title");
        }
    
        ...
    }
    
    0 讨论(0)
  • 2020-12-02 07:44

    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;
    }
    
    0 讨论(0)
提交回复
热议问题