With code
class A {
val x = run { y }
val y = 1
}
I decompile the class file with CFR and got the following java source:
Source: A.java
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* kotlin.Metadata
*/
import kotlin.Metadata;
@Metadata(mv={1, 6, 0}, k=1, xi=48, d1={"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0004\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002R\u0011\u0010\u0003\u001a\u00020\u0004\u00a2\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u0014\u0010\u0007\u001a\u00020\u0004X\u0086D\u00a2\u0006\b\n\u0000\u001a\u0004\b\b\u0010\u0006"}, d2={"LA;", "", "()V", "x", "", "getX", "()I", "y", "getY"})
public final class A {
private final int x;
private final int y;
public A() {
A $this$x_u24lambda_u2d0 = this;
A a = this;
boolean bl = false;
int n = $this$x_u24lambda_u2d0.getY();
a.x = n;
this.y = 1;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
It seems like the final field x
is set to this.y
before the field x being
set to the wanted value 1
. JVM will give all uninitialized field, in this example
it is y
, with a default value. And the default value for type int
is 0
.
Plus, the default value for an object is null
, this make your String
set to null
.
And compare to source code:
class B {
val x = 1
val y = run { x }
}
and the decompile output:
Source: B.java
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* kotlin.Metadata
*/
import kotlin.Metadata;
@Metadata(mv={1, 6, 0}, k=1, xi=48, d1={"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0004\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002R\u0014\u0010\u0003\u001a\u00020\u0004X\u0086D\u00a2\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u0011\u0010\u0007\u001a\u00020\u0004\u00a2\u0006\b\n\u0000\u001a\u0004\b\b\u0010\u0006"}, d2={"LB;", "", "()V", "x", "", "getX", "()I", "y", "getY"})
public final class B {
private final int x;
private final int y;
public B() {
this.x = 1;
B $this$y_u24lambda_u2d0 = this;
B b = this;
boolean bl = false;
int n = $this$y_u24lambda_u2d0.getX();
b.y = n;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
I see that the statement val y = run { x }
was compiled to
B $this$y_u24lambda_u2d0 = this;
B b = this;
boolean bl = false;
int n = $this$y_u24lambda_u2d0.getX();
b.y = n;
I also create two inline functions:
inline fun <T, R> T.func1(a: T.() -> R): R = a(this)
inline fun <T, R> T.func2(a: R): R = a
With func1
in C.kt
class C {
val x = func1 { y }
val y = 1
}
We get decompile output
Source: C.kt
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* kotlin.Metadata
*/
import kotlin.Metadata;
@Metadata(mv={1, 6, 0}, k=1, xi=48, d1={"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0004\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002R\u0011\u0010\u0003\u001a\u00020\u0004\u00a2\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u0014\u0010\u0007\u001a\u00020\u0004X\u0086D\u00a2\u0006\b\n\u0000\u001a\u0004\b\b\u0010\u0006"}, d2={"LC;", "", "()V", "x", "", "getX", "()I", "y", "getY"})
public final class C {
private final int x;
private final int y;
/*
* WARNING - void declaration
*/
public C() {
void $this$x_u24lambda_u2d0;
int n;
C $this$func1$iv = this;
boolean $i$f$func1 = false;
C c = $this$func1$iv;
C c2 = this;
boolean bl = false;
c2.x = n = $this$x_u24lambda_u2d0.getY();
this.y = 1;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
And with func2
in D.kt
class D {
val x = func2(y)
val y = 1
}
Now we get the wanted compile error
D.kt:2:19: error: variable 'y' must be initialized
val x = func2(y)
Note that run
accept a function as argument, this make the difference.
But why?
The inline function run
does not call the function at compile time, but insert the argument function call into the generated byte code. So the result is the function passed to run
is called when the class constructs, when the final field x
is not initialized but JVM has given it a default value.
For func1
, we can pass a function that returns y
to x
, kotlin will call the function directly in the constructor.
For func2
, it is clear that we cannot get y
as argument because it is not initialized yet.
Build a function gets y
is possible, but get y
as an argument is impossible.