Should I instantiate instance variables on declaration or in the constructor?
Is there any advantage for either approach?
Example 1:
class A {
B b = new B();
}
Example 2:
class A {
B b;
A() {
b = new B();
}
}
Solution 1:
- There is no difference - the instance variable initialization is actually put in the constructor(s) by the compiler.
- The first variant is more readable.
- You can't have exception handling with the first variant.
-
There is additionally the initialization block, which is as well put in the constructor(s) by the compiler:
{ a = new A(); }
Check Sun's explanation and advice
From this tutorial:
Field declarations, however, are not part of any method, so they cannot be executed as statements are. Instead, the Java compiler generates instance-field initialization code automatically and puts it in the constructor or constructors for the class. The initialization code is inserted into a constructor in the order it appears in the source code, which means that a field initializer can use the initial values of fields declared before it.
Additionally, you might want to lazily initialize your field. In cases when initializing a field is an expensive operation, you may initialize it as soon as it is needed:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
And ultimately (as pointed out by Bill), for the sake of dependency management, it is better to avoid using the new
operator anywhere within your class. Instead, using Dependency Injection is preferable - i.e. letting someone else (another class/framework) instantiate and inject the dependencies in your class.
Solution 2:
Another option would be to use Dependency Injection.
class A{
B b;
A(B b) {
this.b = b;
}
}
This removes the responsibility of creating the B
object from the constructor of A
. This will make your code more testable and easier to maintain in the long run. The idea is to reduce the coupling between the two classes A
and B
. A benefit that this gives you is that you can now pass any object that extends B
(or implements B
if it is an interface) to A
's constructor and it will work. One disadvantage is that you give up encapsulation of the B
object, so it is exposed to the caller of the A
constructor. You'll have to consider if the benefits are worth this trade-off, but in many cases they are.
Solution 3:
I got burned in an interesting way today:
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
See the mistake? It turns out that the a = null
initializer gets called after the superclass constructor is called. Since the superclass constructor calls init(), the initialization of a
is followed by the a = null
initialization.
Solution 4:
my personal "rule" (hardly ever broken) is to:
- declare all variables at the start of a block
- make all variables final unless they cannot be
- declare one variable per line
- never initialize a variable where declared
- only initialize something in a constructor when it needs data from the constructor to do the initialization
So I would have code like:
public class X
{
public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
private static final int A;
private final int b;
private int c;
static
{
A = 42;
}
{
b = 7;
}
public X(final int val)
{
c = val;
}
public void foo(final boolean f)
{
final int d;
final int e;
d = 7;
// I will eat my own eyes before using ?: - personal taste.
if(f)
{
e = 1;
}
else
{
e = 2;
}
}
}
This way I am always 100% certain where to look for variables declarations (at the start of a block), and their assignments (as soon as it makes sense after the declaration). This winds up potentially being more efficient as well since you never initialize a variable with a value that is not used (for example declare and init vars and then throw an exception before half of those vars needed to have a value). You also do not wind up doing pointless initialization (like int i = 0; and then later on, before "i" is used, do i = 5;.
I value consistency very much, so following this "rule" is something I do all the time, and it makes it much easier to work with the code since you don't have to hunt around to find things.
Your mileage may vary.