前言
data object (数据单例) 是 Kotlin 1.9 中预定引入的新特性 ,但其实从 1.7.20 开始就可以预览了。启动预览需要在 gradle 中升级 KotlinCompileVersion:
tasks.withType<KotlinCompile> {
kotlinOptions.languageVersion = "1.9"
}
接下来让我们看看它有哪些特点。
提升 toString() 可读性
data object
相对于普通 object
,在调用 toString()
的时候,前者的可读性更好,输出类名,不再携带 HashCode
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
}
此时,只有调用 ===
才会认为是两个地址,返回 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 规范,两个实例的 equlas
相等,则 hashCode 也必须相等,可以看到 hashCode 是一个常量值,所以自然相等。
Sealed 的好伴侣
我们在经常会使用 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
都是 data class ,toString()
经过优化,会打印出 Success(response= ...)
,Error(reason=...)
等有效信息。而 Loading
则打印出毫无意义的 HashCode 等对象信息。
而 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 class 的区别
data object
兼具了 data class
和 object
的特性。但是 data class
编译期会生成 copy
以及 componentN
方法。而 data object
是个单例,压根没有构造函数,自然不存在“拷贝构造”和“解构”的需求,不会生成 copy
和 componentN
方法