Reputation: 40336
I realize there are special classes for which this general question doesn't apply, but for the simple ones, when we have multiple constructors, and the parameters of one are a clean subset of another, is it better to call the constructor with the longer list from the one with the shorter list, or vice versa? Why?
public class A {
int x;
int y;
int z;
public A() {
this(0);
}
public A(int x) {
this (x, 0);
}
public A(int x, int y) {
this(x, y, 0);
}
public A(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
// some setup stuff needed for all A
}
}
Or
public class A {
int x;
int y;
int z;
public A(int x, int y, int z) {
this(x, y);
this.z = z;
}
public A(int x, int y) {
this(x);
this.y = y;
}
public A(int x) {
this();
this.x = x;
}
public A() {
// some setup stuff needed for all A
}
}
Upvotes: 6
Views: 429
Reputation: 298153
Have a look at the second variant:
public A(int x, int y, int z) {
this(x, y);
this.z = z;
}
public A(int x, int y) {
this(x);
this.y = y;
}
public A(int x) {
this();
this.x = x;
}
public A() {
// some setup stuff needed for all A
}
Note that this “stuff needed for all A” is impossible to set up, if it requires the actual values of x
, y
, z
. The only way to fix that, is to let the default constructor do that work using the default values of x
, y
, z
and then overwrite its results in the calling constructor, using the specified non-default values. That’s a no-go if these setup work has noticeable side effects, but even without side effects it may also have a negative effect on the performance, considering the worst case, that the A(int x, int y, int z)
constructor performs that work four times.
Besides that, there are (at least) three scenarios, where your second variant doesn’t work, even without such setup work:
As already explained by Codebender, the parameter lists are not required to be a subset of each other.
public A(TypeA a) {
this(a, DEFAULT_B);
}
public A(TypeB b) {
this (DEFAULT_A, b);
}
public A(TypeA a, TypeB b) {
…
}
the fields might be final
. Then, the last constructor in the chain, which does not invoke another constructor of this class, must initialize all final
fields while the delegating constructors are not allowed to write into these final
fields at all.
public class A {
final int x, y, z;
public A() {
this(0);
}
public A(int x) {
this (x, 0);
}
public A(int x, int y) {
this(x, y, 0);
}
public A(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
// optionally, some setup stuff needed for all A
}
}
Some of the fields are defined in the superclass and required to be initialized via the constructor. Similarly to the final
field initialization, only the last constructor in the chain can invoke the super
constructor while the others can’t invoke a super
constructor, so only a constructor knowing the appropriate values can be that last one.
public class A extends B{
int z;// B has x and y
public A() {
this(0);
}
public A(int x) {
this (x, 0);
}
public A(int x, int y) {
this(x, y, 0);
}
public A(int x, int y, int z) {
super(x, y);
this.z = z;
// optionally, some setup stuff needed for all A
}
}
Since there are a lot of scenarios where the second variant doesn’t work, I would not use it in working scenarios either, as whenever something is merely a stylistic question, you should strive for consistency.
Upvotes: 4
Reputation: 2842
At the end of the day there isn't much difference. I find that most people chain up, I can give three reasons I guess.
One reason I would give is simply because the functionality or logic for a class is totally in one area. And that is the constructor with the full variables.
Imagine:
this.x = 2*x;
or
if(x>20 && y>20)....
The biggest issue is that constructors further down won't have access to "z" since it is only initialized AFTER the chain.
Also many times we don't JUST save variables we set things up based on the variables. This way all the code logic for setup is in one place where variables are also setup.
It would be odd to have all of that code in a constructor where those are not arguments. Can it be done? YES! But a little odd...
The second reason is that when the variable types are different you could chain up, but not necessarily chain down.
public example(String aString){
this(aString,0);
}
public example(int aNumber){
this(null, aNumber);
}
public example(String aString, int aNumber){
...
}
If chaining down:
public example(String aString, int aNumber){
this(aString);
this.aNumber = aNumber;
/**
You can only call one constructor lower, and you lose "aNumber"
if you initialize here then your doubling that code and changing
it means changing it twice.
Of course this is ONLY true, if you want to also have the constructor
for just aNumber, if not there is no issue and it is like your example
**/
}
public example(String aString){
this(0);
this.aString = aString;
}
public example(int aNumber){
this();
this.aNumber = aNumber;
//only an issue if you want both this constructor and "aString" constructor
}
The third reason I can think of is that its also what one might expect, both in terms of code and in terms of errors... People will be confused with readability since they aren't used to it.
Similarly, Imagine you get an error from the simple constructor class, it is a little odd, why was a simpler version called? Do you assume it has the same functionality do all constructors lead to the simple constructor or do some of them behave differently? For some reason I would expect all constructors lead to the more complex version, but I am not sure I would assume the opposite.
More importantly I would be nervous that the simple constructor is being called because, some arguments are not even dealt with and maybe this constructor with arguments is just a stub to be developed later.
These are always concerns to be fair, but I think it might be a warning sign if a simpler constructor was called instead in a stack trace, at least to me.
Upvotes: 2
Reputation: 14471
Going from subset to superset would be much tidier in my opinion.
That way, we can have all the logic in one place. And if a logic needs to change, it would be much easier to modify.
Also, most of the time coming from larger constructor to subset won't be possible.
Let's take an example
class Student {
int id;
String name;
public Student(int id) {
this(id, null);
}
public Student(String name) {
this(0, name);
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
Now, it would not be possible to write this the other way.
You can go from superset to subset only if each constructor is a superset of another constructor except one. And that may make it difficult to add a new constructor in the future.
Upvotes: 1