What\'s the difference between a default constructor and just initializing an object\'s fields directly?
What reasons are there to prefer one of the following examples over the other?
Example 1
public class Foo
{
private int x = 5;
private String[] y = new String[10];
}
Example 2
public class Foo
{
private int x;
private String[] y;
public Foo()
{
x = 5;
y = new String[10];
}
}
Initialisers are executed before constructor bodies. (Which has implications if you have both initialisers and constructors, the constructor code executes second and overrides an initialised value)
Initialisers are good when you always need the same initial value (like in your example, an array of given size, or integer of specific value), but it can work in your favour or against you:
If you have many constructors that initialise variables differently (i.e. with different values), then initialisers are useless because the changes will be overridden, and wasteful.
On the other hand, if you have many constructors that initialise with the same value then you can save lines of code (and make your code slightly more maintainable) by keeping initialisation in one place.
Like Michael said, there\'s a matter of taste involved as well - you might like to keep code in one place. Although if you have many constructors your code isn\'t in one place in any case, so I would favour initialisers.
The reason to prefer Example one is that it\'s the same functionality for less code (which is always good).
Apart from that, no difference.
However, if you do have explicit constructors, I\'d prefer to put all initialization code into those (and chain them) rather than splitting it up between constructors and field initializers.
Should we favor field initializer or constructor to give a default value to a field?
I will not consider exceptions that may rise during field instantiation and field lazy/eager instantiation that touch other concerns than readability and maintainability concerns.
For two codes that perform the same logic and produce the same result, the way with the best readability and maintainability should be favored.
TL;DR
choosing the first or the second option is before all a question of code organization, readability and maintainability.
keep a consistency in the way of choosing (it makes overall application code clearer)
don\'t hesitate to use field initializers to instantiate Collection
fields to prevent NullPointerException
don\'t use field initializers for fields that may be overwritten by constructors
in classes with a single constructor, the field initializer way is generally more readable and less verbose
in classes with multiple constructors where constructors have no or very few coupling between them, the field initializer way is generally more readable and less verbose
in classes with multiple constructors where constructors have coupling between them, none of the two ways is really better but whatever the chosen way, combining it with the chaining constructor is the way (see use case 1).
OP Question
With a very simple code, the assignment during the field declaration seems better and it is.
This is less verbose and more straight :
public class Foo {
private int x = 5;
private String[] y = new String[10];
}
than the constructor way :
public class Foo{
private int x;
private String[] y;
public Foo(){
x = 5;
y = new String[10];
}
}
In real classes with so real specificities, things are different.
In fact, according to specificities encountered, a way, the other one or anyone of them should be favored.
More elaborated examples to illustrate
Study case 1
I will start from a simple Car
class that I will update to illustrate these points.
Car
declare 4 fields and some constructors that have relation between them.
1.Giving a default value in field intializers for all fields is undesirable
public class Car {
private String name = \"Super car\";
private String origin = \"Mars\";
private int nbSeat = 5;
private Color color = Color.black;
...
...
// Other fields
...
public Car() {
}
public Car(int nbSeat) {
this.nbSeat = nbSeat;
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color;
}
}
Default values specified in the fields declaration are not all reliable.
Only name
and origin
fields have really default values.
nbSeat
and color
fields are first valued in their declaration, then these may be overwritten in the constructors with arguments.
It is error-prone and besides with this way of valuing fields, the class decreases its reliability level. How could
rely on any default value assigned during the fields declaration while it has proven to be not reliable for two fields ?
2.Using constructor to value all fields and relying on constructors chaining is fine
public class Car {
private String name;
private String origin;
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
this(5, Color.black);
}
public Car(int nbSeat) {
this(nbSeat, Color.black);
}
public Car(int nbSeat, Color color) {
this.name = \"Super car\";
this.origin = \"Mars\";
this.nbSeat = nbSeat;
this.color = color;
}
}
This solution is really fine as it doesn\'t create duplication, it gathers all the logic at a place : the constructor with the maximum number of parameters.
It has a single drawback : the requirement to chain the call to another constructor.
But is it a drawback ?
3.Giving a default value in field intializers for fields which constructors don\'t assign to them a new value is better but has still duplication issues
By not valuing nbSeat
and color
fields in their declaration, we distinguish clearly fields with default values and fields without.
public class Car {
private String name = \"Super car\";
private String origin = \"Mars\";
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
nbSeat = 5;
color = Color.black;
}
public Car(int nbSeat) {
this.nbSeat = nbSeat;
color = Color.black;
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color;
}
}
This solution is rather fine but it repeats the instantiation logic in each Car
constructor contrary to the previous solution with constructor chaining.
In this simple example, we could start to understand the duplication issue but it seems only a little annoying.
In real cases, the duplication may be much important as the constructor may perform computation and validation.
Having a single constructor performing the instantiation logic becomes so very helpful.
So finally the assignment in the fields declaration will not always spare the constructor to delegate to another constructor.
Here is an improved version.
4.Giving a default value in field intializers for fields which constructors don\'t assign to them a new value and relying on constructors chaining is fine
public class Car {
private String name = \"Super car\";
private String origin = \"Mars\";
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
this(5, Color.black);
}
public Car(int nbSeat) {
this(nbSeat, Color.black);
}
public Car(int nbSeat, Color color) {
// assignment at a single place
this.nbSeat = nbSeat;
this.color = color;
// validation rules at a single place
...
}
}
Study case 2
We will modify the original Car
class.
Now,Car
declare 5 fields and 3 constructors that have no relation between them.
1.Using constructor to value fields with default values is undesirable
public class Car {
private String name;
private String origin;
private int nbSeat;
private Color color;
private Car replacingCar;
...
...
// Other fields
...
public Car() {
initDefaultValues();
}
public Car(int nbSeat, Color color) {
initDefaultValues();
this.nbSeat = nbSeat;
this.color = color;
}
public Car(Car replacingCar) {
initDefaultValues();
this.replacingCar = replacingCar;
// specific validation rules
}
private void initDefaultValues() {
name = \"Super car\";
origin = \"Mars\";
}
}
As we don\'t value name
and origin
fields in their declaration and we have not a common constructor naturally invoked by other constructors, we are forced to introduce a initDefaultValues()
method and invoke it in each constructor.
So we have not to forget to call this method.
Note that we could inline initDefaultValues()
body in the no arg constructor but invoking this()
with no arg from the other constructor is not necessary natural and may be easily forgotten :
public class Car {
private String name;
private String origin;
private int nbSeat;
private Color color;
private Car replacingCar;
...
...
// Other fields
...
public Car() {
name = \"Super car\";
origin = \"Mars\";
}
public Car(int nbSeat, Color color) {
this();
this.nbSeat = nbSeat;
this.color = color;
}
public Car(Car replacingCar) {
this();
this.replacingCar = replacingCar;
// specific validation rules
}
}
2.Giving a default value in field initializers for fields which constructors don\'t assign to them a new value is fine
public class Car {
private String name = \"Super car\";
private String origin = \"Mars\";
private int nbSeat;
private Color color;
private Car replacingCar;
...
...
// Other fields
...
public Car() {
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color;
}
public Car(Car replacingCar) {
this.replacingCar = replacingCar;
// specific validation rules
}
}
Here we don\'t need to have a initDefaultValues()
method or a no arg constructor to call.
Field initializers is perfect.
Conclusion
In any cases) Valuing fields in field initializers should not be performed for all fields but only for those that cannot be overwritten by a constructor.
Use case 1) In case of multiple constructors with common processing between them, it is primarily opinion-based.
Solution 2 (Using constructor to value all fields and relying on constructors chaining) and solution 4 (Giving a default value in field intializers for fields which constructors don\'t assign to them a new value and relying on constructors chaining) appear as the most readable, maintainable and robust solutions.
Use case 2) In case of multiple constructors with no common processing/relation between them as in the single constructor case, solution 2 (Giving a default value in field intializers for fields which constructors don\'t assign to them a new value) looks better.
I prefer field initializers and resort to a default constructor when there is complex initialization logic to perform (e.g. populate a map, one ivar depends on another through a series of heuristic steps to execute, etc).
@Michael B said:
... I\'d prefer to put all initialization code into those (and chain them) rather than splitting it up between constructors and field initializers.
MichaelB (I bow to the 71+ K rep) makes perfect sense, but my tendency is to keep the simple initializations in the inline final initializers and do the complex part of the initializations in the constructor.
The only difference that I can think of is that if you were to add another constructor
public Foo(int inX){
x = inX;
}
then in the first example, you would no longer have a default constructor whereas in the 2nd example, you would still have the default constructor (and could even make a call to it from inside our new constructor if we wanted to)