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)
}
Essentially, no. As a concept this doesn't scale. In two ways.
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.
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)
.
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:
()
(int a)
(int a, int b)
(int a, int b, String c)
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.
The only constructors that exist is the no-args one because various tools need it, and the all-args one.
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.
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:
fac(n)
explosion - it's linear to the number of fields..buildDate(LocalDate.of(1937, 4, 19))
and also .buildDate(1937, Month.APRIL, 19)
- if you really want that, at least it doesn't make the combinatory explosion effect even worse.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.