The specific use where I thought of this problem is as follows, but it's much more generalized.
I have a custom JFrame
class which also serves as an ActionListener
for its components. So my constructor looks something like the following:
private JButton myButton;
public MyCustomFrame() {
super();
myButton.addActionListener(this);
// ... more stuff
}
My question is, how does this actually work behind the scenes? If the constructor is what "creates" the object which is referenced by this
, how can I use this
before the constructor has returned? The code compiles and works perfectly fine (as far as I can tell), so the object must already "exist" in some sense, but I'm concerned that this may cause unforeseen issues. Is there any danger in passing a "partially constructed" reference to addActionListener()
(or just performing any logic with it in general)? Or is there some behind-the-curtain magic happening that keeps me safe?
For example, what about things which don't have default values and must be supplied by the constructor? If I have private final String SOME_VALUE;
declared, I understand that this should default to null
, but the object isn't supposed to be fully formed until the constant is supplied a value within the constructor. So would the reference, despite being final, possibly have changing values?
That Java Language Specification specifies the steps of instance creation
[...]
Next, space is allocated for the new class instance. If there is
insufficient space to allocate the object, evaluation of the class
instance creation expression completes abruptly by throwing an
OutOfMemoryError.
The new object contains new instances of all the fields declared in
the specified class type and all its superclasses. As each new field
instance is created, it is initialized to its default value (§4.12.5).
Next, the actual arguments to the constructor are evaluated,
left-to-right. If any of the argument evaluations completes abruptly,
any argument expressions to its right are not evaluated, and the class
instance creation expression completes abruptly for the same reason.
Next, the selected constructor of the specified class type is invoked.
This results in invoking at least one constructor for each superclass
of the class type. This process can be directed by explicit
constructor invocation statements (§8.8) and is described in detail in
§12.5.
So by the time the constructor (which is a method) is invoked, your instance exists with default values.
For final
fields, it appears those are defaulted as well if you try to access them. For example
public class Driver {
public static void main(String[] args) {
new Driver();
}
final int value;
public Driver() {
print(this);
value = 3;
}
static void print(Driver driver) {
System.out.println(driver.value);
}
}
will print 0. I'll be right back with the JLS entry if I can find it.
I couldn't find anything more specific then what is above. Maybe in 4.12.4. final
Variables
A final variable may only be assigned to once.
You can take to mean that default initialization puts the value to 0 or null
and the assignment changes it.
Once you call your constructor, your object already exists from the start, and you're just populating it with values.
The danger of this comes if the method you're passing your object to tries to use a value that you haven't declared yet in your constructor.
You also want to avoid having your constructor(and other methods for that matter) behave in such a way that the user of the constructor wouldn't expect.
If the person instantiating your object doesn't have a reason to expect the constructor to automatically bind that object to a button, than maybe you shouldn't do that.
this
does actually exist before the constructor finishes. However, allowing a reference to this
to escape your object before the constructor is complete can present a danger.
What if you passed your this
reference to a method that assumes your object is fully formed and ready to go? Maybe this is fine in the case of your object, but in many cases, this could be dangerous. Allowing other methods access to an object before it is ready to be used would present a serious threat to your program being able to run reliably.
You are absolutely right, that is a bad thing to do as this
may only be partially initialized at the time you use it.
This is why many compilers will give a warning about doing it.
Don't escape this from the constructor because if another thread read variables of the instance which construction hasn't finished yet, the thread may read unexpected value.
A example is below.
public class A {
private final int value;
public A(int value) {
this.value = value;
new Thread(new Runnable() { // this escape implicitly
public void run() {
System.out.println(value);
}
}).start();
}
public static void main(String[] args) {
new A(10);
}
}
This program may show a value other than 10 from Java Memory Model specification.