Basically, you already list the bad things that can happen, so you partially answer your own question. I will give details about what you mentioned:
Issuing this before initializing final fields
For example, if your class has a finite (non-static) field, then you can usually depend on the fact that it is set to a value and never changes.
When the object you are looking at is currently executing its constructor, this guarantee is no longer valid.
Like this?
Pretty simple: if you pass this before setting the final field, then it will not be set yet:
class X{ final int i; X(){ new Y(this);
Pretty simple, right? Class Y sees a X with an uninitialized final field. This is a big no no!
Java usually ensures that the final field is initialized exactly once and is not read before it is initialized. This warranty disappears after this leak.
Note that the same problem occurs for non- final fields that are equally bad. However, people are more surprised if the final field is found uninitialized.
subclasses
The problems with subclassification are quite similar to the problems described above: the base classes are initialized before the derived classes, so if you skip the this link in the constructor of the base class, you will leak an object that has not yet initialized its derived fields. This can become very unpleasant in case of polymorphic methods, as this example shows:
class A{ static void doFoo(X x){ x.foo(); } } class X{ X(){ A.doFoo(this);
So, as you can see, there is class X with the foo method. X leak into A into its constructor, and A will call foo . For X classes, this works fine. But for classes Y a NullPointerException . The reason is because Y overrides foo and uses one of its fields ( stream ) in it. Since stream is not yet initialized when A calls foo , you get an exception.
This example shows the following problem with the leak of this: even if your base class can work fine when this leaked, the class inherits from your base class (which may not be written by you, and someone else who does not know this leak) can blow everything up.
This leak to yourself
This section does not exactly talk about a problem of its type, but you need to keep in mind: even calling one of your own methods can be considered a leak of this , since it brings similar problems, like a link leak to another class. For example, consider the previous example with another constructor X :
X(){
Now we do not leak this into A , but we leak it to ourselves by calling foo . Again, the same bad things happen: the class Y , which overrides foo() and uses one of its fields, will lead to chaos.
Now consider our first example with the final field. Again, leaking itself using a method may allow you to find the final field uninitialized:
class X{ final int i; X(){ foo(); i = 5; } void foo(){ assert(i == 5);
Of course, this example is completely built. Each programmer will notice that the first call to foo and then setting i is incorrect. But now consider inheritance again: your X.foo() method may not even use i , so it should be called before i initialized. However, a subclass can override foo() and use i in it, again breaking everything.
Also note that the overridden foo() method can flow this even further by passing it to other classes. Therefore, while we only intended to leak this ourselves by calling foo() , a subclass can override foo() and publish this all over the world.
If calling one of your own methods is considered to be missed by this , it may be controversial. However, as you can see, this leads to similar problems, so I wanted to discuss it here, even if many people cannot agree that calling their own method is considered a leak of this .
If you really need to call your own methods in the constructor, then either use the final or static methods, since they cannot be overridden by an innocent derived class.
Concurrency
The final fields in the Java memory model have a good property: they can be read simultaneously without blocking. The JVM must ensure that even concurrent unlocked access will always see a fully initialized final field. This can be done, for example, by adding a memory barrier to the end of the constructor, which assigns trailing fields. However, this guarantee disappears as soon as you give out this too soon. Again an example:
class X{ final int i; X(Y y){ i = 5; yx = this;
As you can see, we again distribute this too soon, but only after initializing i . Thus, in one streaming environment, everything is in order. But now enter concurrency: we create a static Y (which receives a corrupted instance of X ) in one thread and access it in the second thread. Now the statement may fail again, because the compiler or processor that is not executing the order is now allowed to reorder the destination i = 5 and the destination Yy = new Y() .
To make things clearer, suppose the JVM makes all the calls, so the code
{ Yy = new Y(); }
will be first nested in ( rX - local registers):
{ r1 = 'allocate memory for Y' // Constructor of Y r1.x = new X(r1); // Constructor of Y Yy = r1; }
now we would also include the call to new X() :
{ r1 = 'allocate memory for Y' // constructor of Y r2 = 'allocate memory for X' // constructor of X r2.i = 5; // constructor of X r1.x = r2; // constructor of X Yy = r1; }
So far, everything is in order. But now reordering is allowed. We (i.e. JVM or CPU) reorder r2.i = 5 to the end:
{ r1 = 'allocate memory for Y' // 1. r2 = 'allocate memory for X' // 2. r1.x = r2; // 3. Yy = r1; // 4. r2.i = 5; // 5. }
Now we can observe the incorrect behavior: consider that thread 1 performs all the steps up to 4. and then is interrupted (before setting the final field!). Now thread 2 executes all the code and therefore its assert(Yyx == 5); fails.
Other problems may occur.
Basically, the three problems that you talked about, and I explained above, are the worst. Of course, there are many different aspects in which these problems can arise so that thousands of examples can be built. As long as your program is single-threaded, distributing this early may be ok (but don't do it anyway!). Once concurrency comes into play, never do this, you will get strange behavior, because the JVM basically allows you to reorder things as you wish in this case. Instead of remembering the details of the various specific problems that may occur, just remember the two conceptual things that may occur:
- The constructor usually first carefully constructs the object, and then passes it to the caller. A
this , which comes out of the constructor, is a partially constructed object, and usually you never want to have partially constructed objects because they are difficult to reason about (see My first example). Especially when inheritance comes into play, things get even more complicated. Just remember: leak this + inheritance = Here are the dragons. - The memory model refuses most guarantees after you pass
this to the constructor, so crazy reordering can lead to very strange executions that are almost impossible to debug. Just remember: leak this + concurreny = Here are the dragons.