为何Java代码输出0而非23?this逃逸引发的对象初始化问题咨询
Let's break down exactly what's happening here, starting with the code in question:
class A { final int finalValue; public A(B b) { super(); b.doSomething(this); // this escapes! finalValue = 23; } int getTheValue() { return finalValue; } } class B { void doSomething(A a) { System.out.println(a.getTheValue()); } }
Step-by-step execution breakdown
When you create an instance of A with a B instance, here's the order of operations:
- The
Aconstructor starts, first callingsuper()(which initializes the baseObjectportion of theAinstance). - Before we assign the intended value
23tofinalValue, we passthis—the partially initializedAobject—toB.doSomething(). doSomethingimmediately callsa.getTheValue(), which returns the current state offinalValue.- Since
finalValueis anint(a primitive type), its default uninitialized value is0—that's what gets printed. - Only after
doSomethingreturns does the constructor finally assign23tofinalValue.
The core problem: This escaping breaks initialization guarantees
This is a classic example of this escaping—when you expose a reference to an object before its constructor has finished running. Here's why this is such a critical issue:
- Java promises that
finalfields are initialized exactly once and become immutable after construction completes. But this guarantee only holds if the object isn't leaked during construction. - The Java Memory Model (JMM) doesn't ensure that code accessing an escaped, partially initialized object will see the correct field values. Even
finalfields can appear to be in their default uninitialized state to code that accesses the object before the constructor finishes. - In multi-threaded environments, this becomes exponentially riskier: other threads might see a half-initialized object with default values, leading to unpredictable bugs that are extremely hard to debug and reproduce.
How to fix this
The fix is simple: never pass this to external methods, start threads, or make the object accessible to other code before the constructor finishes executing. A common pattern to enforce this is using a factory method instead of a public constructor:
class A { final int finalValue; // Make constructor private to force use of factory method private A() { super(); finalValue = 23; // No this escape possible here } int getTheValue() { return finalValue; } // Factory method: create fully initialized object before exposing it public static A createA(B b) { A a = new A(); b.doSomething(a); // Now a is fully initialized return a; } }
With this approach, doSomething only gets access to the A instance after all its fields (including finalValue) have been properly set, so it will print 23 as expected.
内容的提问来源于stack exchange,提问作者Milan Srdić

