javaでよくあるユーティリティクラスをkotlinに移植しようと思ったときに気づきがあったのでまとめておく。
javaユーティリティクラスのkotlin convert
事の発端はjavaで記述された以下のユーティリティクラス(staticなメンバーのみをもつクラス)をkotlinにconvertしたとき、object SampleUtil
で定義されていたこと。kotlinでobjectを定義するとsingletonになるはずで、本来インスタンスを生成しないはずのものがsingletonに変換されていることを不思議に思った。
例えばaws-android-sdkにあったこのSampleUtil、
これをkotlinにconvertすると、
public class SampleUtil {
private static final String PropertyFile = "aws-iot-sdk-samples.properties";
public static class KeyStorePasswordPair {
public KeyStore keyStore;
public String keyPassword;
public KeyStorePasswordPair(KeyStore keyStore, String keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
}
}
...
}
SampleUtilはobjectで定義されていて、staticだったものがsingletonに変換される。
object SampleUtil {
private val PropertyFile = "aws-iot-sdk-samples.properties"
class KeyStorePasswordPair(var keyStore: KeyStore, var keyPassword: String)
fun getConfig(name: String): String? {
val prop = Properties()
val resource = SampleUtil::class.java.getResource(PropertyFile) ?: return null
try {
resource.openStream().use { stream -> prop.load(stream) }
} catch (e: IOException) {
return null
}
val value = prop.getProperty(name)
return if (value == null || value.trim { it <= ' ' }.length == 0) {
null
} else {
value
}
}
...
}
kotlinでobject
で定義するsingletonになることは知識としては前から知っていたので、javaにおけるユーティリティクラス(staticメンバのみを持つクラス)をkotlinにconvertすると class SampleUtil
内のcompanion object
にメンバーを定義する形になると想像していたが結果は違った。また多くのピュアkotlinで書かれたプロジェクトのユーティリティクラスも同じようにobject
でシングルトンを生成していたことも混乱する原因となった。
objectとcompanion objectのおさらい
詳細は以下にまとまっているが、改めてobject
とcompanion object
のおさらいをする。
[https://kotlinlang.org/docs/reference/object-declarations.html:title]
object
で定義したDataProviderManager
はシングルトンで生成され、
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
DataProviderManager.registerDataProvider(...)
のように使える。
companion object
についても同じように、
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
のように使える。
object
がsingletonということはcompanion object
はstaticだろうと思っていたのが勘違いの始まりでちゃんとドキュメントを読むと以下のようにstaticに見えるメンバーもオブジェクトインスタンスのメンバーであるという記述がある。
Note that, even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces:
objectとcompanion objectはjavaでどう解釈されるか
公式ドキュメントによればobject
もcompanion object
も非staticでありインスタンスは生成されているということだが、実際にjavaではどのように解釈されるのか、
理解を深めるために実際に以下のktクラスをdecompileした。
object
object SampleUtil1 {
fun test() {
Log.e("SampleUtil1", "test")
}
}
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/hoge/util/asset/SampleUtil1;", "", "()V", "test", "", "production sources for module app"}
)
public final class SampleUtil1 {
public static final SampleUtil1 INSTANCE;
public final void test() {
Log.e("SampleUtil2", "test");
}
static {
SampleUtil1 var0 = new SampleUtil1();
INSTANCE = var0;
}
}
companion object
class SampleUtil2 {
companion object {
fun test() {
Log.e("SampleUtil2", "test")
}
}
}
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0003\u0018\u0000 \u00032\u00020\u0001:\u0001\u0003B\u0005¢\u0006\u0002\u0010\u0002¨\u0006\u0004"},
d2 = {"Lcom/hoge/util/asset/SampleUtil2;", "", "()V", "Companion", "production sources for module app"}
)
public final class SampleUtil2 {
public static final SampleUtil2.Companion Companion = new SampleUtil2.Companion((DefaultConstructorMarker)null);
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/hoge/util/asset/SampleUtil2$Companion;", "", "()V", "test", "", "production sources for module app"}
)
public static final class Companion {
public final void test() {
Log.e("SampleUtil2", "test");
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
metadataとかがあって弱化読みにくいが、SampleUtil1については想定どおりでstaticイニシャライザでSampleUtil1をシングルトンで生成している。
一方companion object
のほうはというと、companion object
内で定義したメンバーはsitaticインナークラスで定義されたCompanionクラス内に定義されている。
SampleUtil2自体はインスタンス化されていないが内部のCompanionクラスがシングルトンで生成されるのでやっていることとしてはシングルトンインスタンスを作るということでobject
定義のほうと大きな差はないように思う。
結論
- kotlinではstaticを許容しない
-
companion object
もobject
もシングルトンインスタンスを生成する