序文
データ オブジェクト (データ シングルトン) はKotlin 1.9 で導入予定の新機能ですが、1.7.20 からプレビュー可能です。プレビューを開始するには、gradle で KotlinCompileVersion をアップグレードする必要があります。
tasks.withType<KotlinCompile> {
kotlinOptions.languageVersion = "1.9"
}
どのような特徴があるのか見てみましょう。
toString() の読みやすさの向上
data object
通常の と比較してobject
、 を呼び出すときtoString()
、前者はクラス名を出力し、ハッシュコードを持たなくなり、より読みやすくなります。
object SampleObject
data object SampleDataObject
fun main() {
println(SampleObject) // => SampleObject@58ceff1
println(SampleDataObject) // => SampleDataObject
}
逆コンパイルされたコードは次のとおりです
public final class SampleDataObject {
// ...
@NotNull
public String toString() {
return "SampleDataObject";
}
}
equals() は常に true
これは通常、object class
シングルトンであるため、と==
比較したときに返されます。true
data object
object SampleObject
data object SampleDataObject
fun main() {
println(SampleObject == SampleObject) // => true
println(SampleDataObject == SampleDataObject) // => true
}
object class
逆コンパイル後は、実際には次のようにコンストラクターのプライベート化を備えたシングルトン モードになります。
//反编译 "object SampleObject"
public final class SampleObject {
@NotNull
public static final SampleObject INSTANCE;
private SampleObject() {
// private constructor
}
static {
SampleDataObject var0 = new SampleObject();
INSTANCE = var0;
}
}
したがって、リフレクションを通じてコンストラクターを呼び出すことで、さまざまなインスタンスを作成できます object class
。このとき==
インスタンスが違うため戻ってくる可能性がありますfalse
object SampleObject
fun main() {
val newInstance = (SampleObject.javaClass.declaredConstructors[0].apply {
isAccessible = true
} as Constructor<SampleObject>).newInstance()
println(SampleObject == newInstance) // => false
}
ただしdata object
、 の場合は、上記のように呼び出しても が==
返されますtrue
。
data object SampleDataObject
fun main() {
val newInstance = (SampleDataObject.javaClass.declaredConstructors[0].apply {
isAccessible = true
} as Constructor<SampleDataObject>).newInstance()
println(SampleDataObject == newInstance) // => true
println(SampleDataObject === newInstance) // => false
}
現時点では、呼び出しのみが===
2 つのアドレスを考慮して を返しますfalse
。
Kotlin のものは==
実際には equlas を呼び出しているので、逆コンパイルされた の実装を見てみましょうdata object
。equals
これはinstanceOf
によって比較されているため、当然定数であることがわかります。true
public final class SampleDataObject {
// ...
public int hashCode() {
return 1802936564;
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (!(var1 instanceof SampleDataObject)) {
return false;
}
}
return true;
}
}
Java の仕様によれば、2 つのインスタンスequlas
が等しい場合、hashCode も等しくなければなりませんが、hashCode は定数値なので、当然等しいことがわかります。
シールドの良き相棒
列挙可能な状態を定義するためによく使用されますSealed class/interface
。この時によく使われるのがobject class
以下のようなものです。
sealed class State {
object Loading : State()
data class Success<T>(val response: Response<T>) : State()
data class Error(val reason: Throwable): State()
}
requestData().subscribe {
state ->
println("requestData: $state") // requestState: State$Loading@34ce8af7
}
Success
と はError
両方ともデータ クラスであり、最適化後、などの有効な情報がtoString()
出力されます。そして、HashCodeなどの無意味なオブジェクト情報を出力します。Success(response= ...)
Error(reason=...)
Loading
また、出力をより意味のあるものにするために、data object
より適切に調整することができます。Sealed class/interface
sealed class State {
data object Loading : State()
data class Success<T>(val response: Response<T>) : State()
data class Error(val reason: Throwable): State()
}
val state = State.Loading
println("State: $state") // State: Loading
今後Sealed
使用しない場合はdata object
、IDE によって警告が表示されます。
データクラスとの違い
data object
data class
と の両方の特性を備えていますobject
。ただし、data class
コンパイル時にメソッドが生成されcopy
ますcomponentN
。シングルトンなdata object
のでコンストラクターは一切なく、当然「コピー構築」や「分解」の必要がなく、メソッドcopy
やcomponentN
メソッドも生成されません。