What exactly is the danger of porting 'this' from the Java constructor?

So it seems like a bad idea to pass this from the constructor in Java.

 class Foo { Foo() { Never.Do(this); } } 

My simple question is: why?

There are some related questions in Stackoverflow, but none of them provide an exhaustive list of issues that may arise.

For example, in this question , which asks for a workaround to this problem, one of the answers indicates:

For example, if your class has a finite (non-static) field, 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?

Also, I understand that subclassing is a big problem because the superclass constructor is always called before the subclass constructor and can cause problems.

In addition, I read that Java Memory Model (JMM) problems can occur , such as differences in thread visibility and reordering of memory access . no details about this.

What other problems may arise and you can elaborate on the above problems?

+7
java
source share
1 answer

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); // ouch, don't do this! i = 5; } } class Y{ Y(X x){ assert(xi == 5);//This assert should be true, since i is final field, but it fails here } } 

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); // ouch, don't do this! } void foo(){ System.out.println("Leaking this seems to work!"); } } class Y extends X { PrintStream stream; Y(){ this.stream = System.out; } @Overload // Polymorphism ruins everything! void foo(){ // NullPointerException; stream not yet initialized stream.println("Leaking + Polymorphism == NPE"); } } 

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(){ // A.doFoo(); foo(); // ouch, don't do this! } 

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); // Fails, of course } } 

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; // ouch, don't do this! } } class Y{ public static Y y; public X x; Y(){ new X(this); } } //... Some code in one thread { Yy = new Y(); } //... Some code in another thread { assert(Yyxi == 5); // May fail! } 

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.
+17
source share

All Articles