2016/10/05 このエントリーをはてなブックマークに追加 はてなブックマーク - Kotlinのインスタンス生成時のコンストラクタとクラスフィールド初期化の罠っぽいもの

Kotlinのインスタンス生成時のコンストラクタとクラスフィールド初期化の罠っぽいもの

カテゴリ:


Twitterで流れてきたので。

@omochimetaruさんに怒られたらブログ取り下げます。


class A() {
init {
val s: String = getStr()
println("${s.length}") // java.lang.NullPointerException
}
fun getStr(): String {
return str
}
private val str: String = "aaa"
}
fun main(args: Array<String>) {
A()
}
view raw kotlin-bug.kt hosted with ❤ by GitHub

バグっぽいと言われているコードをdecompileするとこんな感じになります。

// HogeKt.java
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 2},
bv = {1, 0, 1},
k = 2,
d1 = {"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005¨\u0006\u0006"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "production sources for module Kebab"}
)
public final class HogeKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
new A();
}
}
// A.java
import kotlin.Metadata;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 2},
bv = {1, 0, 1},
k = 1,
d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0005\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004X\u0082D¢\u0006\u0002\n\u0000¨\u0006\u0006"},
d2 = {"LA;", "", "()V", "str", "", "getStr", "production sources for module Kebab"}
)
public final class A {
private final String str;
@NotNull
public final String getStr() {
return this.str;
}
public A() {
String s = this.getStr();
String var2 = String.valueOf(s.length());
System.out.println(var2);
this.str = "aaa";
}
}
view raw kotlin-bug.java hosted with ❤ by GitHub

つまり、init節が優先されてクラスフィールドの初期化があとになっちゃってるんですね。


宣言順が想定通りになるようこんな感じにすると良いです。

class A() {
private val str: String
init {
str = "aaa"
val s: String = getStr()
println("${s.length}") // java.lang.NullPointerException
}
fun getStr(): String {
return str
}
}
fun main(args: Array<String>) {
A()
}
class A() {
private val str: String = "aaa"
init {
val s: String = getStr()
println("${s.length}") // java.lang.NullPointerException
}
fun getStr(): String {
return str
}
}
fun main(args: Array<String>) {
A()
}

うーん、なんとも微妙なKotlinの仕様って感じがしますね。
nullを返さないはずのgetStr関数の振る舞いやvalでnot nullのクラスフィールドからnullを参照できてしまうというのは。


0 件のコメント:

コメントを投稿

GA