Best way to handle multiple constructors in Java

I was wondering what is the best (i.e. the cleanest / safest / most efficient) way to handle multiple constructors in Java? Especially if not all fields are specified in one or several constructors:

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

What to do if fields are not specified? I still used the default values ​​in the class so the field was never null, but is this a β€œgood” way to do things?

+62
java constructor field
Feb 24 '09 at 14:10
source share
9 answers

A slightly simplified answer:

 public class Book { private final String title; public Book(String title) { this.title = title; } public Book() { this("Default Title"); } ... } 
+124
Feb 24 '09 at 14:14
source share

Consider using the Builder pattern. It allows you to set default values ​​for 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 eliminates the need for multiple constructors with different signatures and is more readable.

+32
Feb 24 '09 at 14:19
source share

You need to specify what class invariants are, that is, properties that will always be true for an instance of the class (for example, the name of the book will never be null or the size of the dog will always be> 0).

These invariants must be set during construction and stored over the lifetime of the object, which means that the methods must not violate the invariants. Constructors can set these invariants either by using the required 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 is highly dependent on the class you write, how you use it, etc., so there is no final answer to your question.

+17
Feb 24 '09 at 14:36
source share

You must always create a valid and legal object; and if you cannot use constructor constructors, you should use the constructor object to create it, only freeing the object from the builder when the object is complete.

On the question of using the constructor: I always try to have one basic constructor, which everyone else puts off, clinging to the next logical constructor with the "omitted" parameters and ending with the basic 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, I try to get a private init () method, which all constructors put off.

And keep the number of constructors and parameters small - a maximum of 5 of them as a guide.

+9
Feb 24 '09 at 17:57
source share

Some general constructor tips:

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

    • this (...) if present (just go to another constructor)
    • calling super (...) [if not explicit, calling super () implicitly]
    • (build a superclass using these rules recursively)
    • initialize fields through your ads
    • run the body of the current constructor
    • return to previous constructors (if you came across these (...) calls)

The total flow ends:

  • move the entire superclass hierarchy to Object
  • not done yet
    • initialization fields
    • run constructors
    • drop out in a subclass

For a good example of evil, try to figure out what prints the following: 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 will add comments describing why this works as it is. Some of them may be obvious; some don't ...

+6
Feb 24 '09 at 15:56
source share

Another consideration, if a field is required or has a limited range, do a constructor check:

 public Book(String title) { if (title==null) throw new IllegalArgumentException("title can't be null"); this.title = title; } 
+3
Feb 24 '09 at 15:32
source share

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 make the assumption here that:

1) the title will never change (therefore, the title is final) 2) isbn will never change (hence, isbn is final) 3) that it is unacceptable to have a book without a title and is abundant.

Consider the 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, the Student should be created with an identifier, first name and last name. The student ID can never change, but the last name and first name of the person can change (get married, change the name due to loss of bid, etc.).

When you decide which constructors you should have, you really need to think about what makes sense. All often, people add set / get methods because they are taught - but very often this is a bad idea.

Immutable classes are much better to have (i.e. classes with finite 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 of immutability. Look at points 12 and 13.

0
Feb 24 '09 at 17:51
source share

Several people have recommended adding a zero check. Sometimes this is correct, but not always. Check out this great article that shows why you skipped it.

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

0
Feb 24 '09 at 17:53
source share

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

I say instead, but obviously you cannot replace the constructor. However, you can hide the constructor behind the static factory method. Thus, we publish the static factory method as part of the class API, but at the same time we hide the constructor making it a private or closed package.

This is a fairly simple solution, especially compared to the Builder pattern (as seen from Joshua Bloch Effective Java 2nd Edition), be careful the Gang of Four Design Patterns define a completely different design pattern with the same name, so this can be a bit confusing). which means creating a nested class, a builder, etc.

This approach adds an extra layer of abstraction between you and your client, simplifies encapsulation, and makes it easier to make changes to the road. It also gives you instance control β€” since objects are created inside the class, you, not the client, decide when and how these objects are created.

Finally, it simplifies testing - providing a dumb constructor that simply assigns values ​​to the fields without performing any logic or verification allows you to enter an invalid state in your system to check how it behaves and reacts to it, you won’t be able to do it, if you are checking the data in the constructor.

You can read a lot more about this in the (already mentioned) Joshua Bloch Effective Java 2nd Edition - this is an important tool in all developer toolkits, and it is not surprising that this is the topic of the first 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); } ... 

}

Whatever method you choose, it is good practice to have a main constructor that simply blindly assigns all values, even if it is just used by other constructors.

0
Sep 14 '17 at 16:19
source share



All Articles