Kotlin 1.9 新特性预览:data object (数据单例)

前言

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,因为是单例,所以使用 == 进行比较时通常返回 truedata 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 objectequals 反编译后的实现,原来是通过 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
}

SuccessError 都是 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 classobject 的特性。但是 data class 编译期会生成 copy 以及 componentN 方法。而 data object 是个单例,压根没有构造函数,自然不存在“拷贝构造”和“解构”的需求,不会生成 copycomponentN 方法

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/131526602
1.9