javaconstructoroverloadingfactory-methodconstructor-overloading

Do I need to overload the constructor | factory method for each combination of constructor's | factory method's arguments?


I have 4 instance variables that can be initialized by default, is the code written below appropriate? The number of constructors is growing a terrible pace, will I use the Builder object that have getters and setters?

 class Example {
    private int a;
    private String s;
    private Object object;
    private boolean aBoolean;


    public Example(int a1, String s1, Object object1, boolean aBoolean1) {
        a = a1;
        s = s1;
        object = object1;
        aBoolean = aBoolean1;
    }

    public Example(int a1, String s1, Object object1) {
        this(a1, s1, object1, true);
    }

    public Example(int a1, String s1) {
        this(a1, s1, new Object(), true);
    }

    public Example(int a1,boolean aBoolean1) {
        this(a1, "a String", new Object(), aBoolean1);
    }
// ...(for all other combinations)
}

Solution

  • Essentially, no. As a concept this doesn't scale. In two ways.

    Why your style doesn't work

    Combinatory explosion

    Imagine you have 6 fields, and they all have default values. That means you're creating 2-to-the-6 different constructors (64 constructors). Aside from the fact that this is nuts on its face and certainly unmaintainable, even if you decide to use some automated tooling or annotation processors to generate them, there's a 65536 limit to the number you can have due to class file limits.

    Type conflicts

    Imagine you have 2 fields (a and b), and they all have default values. Both fields are of type int. You can't do it - the constructor that takes only a and leaves b at default value conflicts with the constructor that takes only b and leaves a at default value - both have the signature YourType(int).

    The common solutions

    Left-to-right only

    Instead of creating the combinatory explosion, only pad on. So, given a hypothetical class with 3 fields (int a; int b; String c;), you have 4 constructors:

    In other words, a given 'field' is required if any field 'below it' is also provided. Then order your fields in a sensible order. This keeps the number of constructors you have managable (if they're all defaulted, 1+n, which is linear to the number of fields you have, instead of a fac(n) explosion). It's also more or less what java programmers expect, and it matches various other languages that work similarly.

    Only full-and-none

    The only constructors that exist is the no-args one because various tools need it, and the all-args one.

    Sensible

    Think about the API, about how code that uses your class would operate. Is there a limited set of common and obvious ways to construct it? If yes, write those constructors. By definition there is no way to tell which ones you should write given a list of field types - you need to explain exactly what your type represents and what it does, it's "in the eye of the beholder". API design has a whiff of artistry to it. You decide how you think it should be used.

    Builders

    This is the true fire-and-forget works-for-every-situation solution. You write a builder. The nature of builders means that your API users (the code that invokes stuff on this class / on instances of this class) end up writing something like this.

    // given:
    
    class Bridge {
      int yearBuilt;
      int span;
      String name;
    }
    
    // do not construct like this:
    new Bridge(1937, 1280, "Golden Gate");
    

    The reason you don't build like that is not just because it makes it very tricky to make every argument optional. It's also that the call itself becomes unreadable. Is that a bridge built in 1937 with a max span of 1280 meters, or is that a bridge built in 1280 with a max span of 1937 meters? Can't tell from this call without having the javadoc or getting my IDE to highlight the names of those params.

    instead, you make your library users write this:

    Bridge.builder()
      .buildYear(1937)
      .maxSpan(1280)
      .name("Golden Gate")
      .build();
    

    This has all sorts of advantages:

    The downside is that it's a ton of boilerplate to write a builder and all the boilerplatey methods that are needed to make it work (not just a 'setter' in the build class for every field, but also builder(), build(), a private constructor, and so on).

    Project Lombok generates all that for you though: @Builder documentation. Even if you can't use lombok / do not like it, the docs contain an example of the 'vanilla' version of what @Builder generates, which serves as example of how to do it on your own.