在Kotlin中扩展数据类

数据类似乎是Java中老式pojo的替代品。这些类允许继承是可以预料的,但我看不出扩展数据类的方便方法。我需要的是这样的东西:

open data class Resource (var id: Long = 0, var location: String = "")
data class Book (var isbn: String) : Resource()

上面的代码失败是因为component1()方法的冲突。只在一个类中留下data注释也不能完成这项工作。

也许还有另一种扩展数据类的习惯用法?

UPD:我可能只注释了一个子子类,但是data注释只处理构造函数中声明的属性。也就是说,我必须声明所有父属性open并重写它们,这很难看:

open class Resource (open var id: Long = 0, open var location: String = "")
data class Book (
override var id: Long = 0,
override var location: String = "",
var isbn: String
) : Resource()
188947 次浏览

事实是:数据类不能很好地处理继承。我们正在考虑禁止或严格限制数据类的继承。例如,我们知道没有办法在非抽象类的层次结构中正确实现equals()

因此,我所能提供的是:不要对数据类使用继承。

在构造函数外的超类中将属性声明为抽象,并在子类中重写它们。

abstract class Resource {
abstract var id: Long
abstract var location: String
}


data class Book (
override var id: Long = 0,
override var location: String = "",
var isbn: String
) : Resource()

上述方案使用抽象类实际生成相应的类,并让数据类从它扩展。

如果你不喜欢抽象类,使用接口如何?

Kotlin中的接口可以具有属性,如下面的这篇文章..

interface History {
val date: LocalDateTime
val name: String
val value: Int
}


data class FixedHistory(override val date: LocalDateTime,
override val name: String,
override val value: Int,
val fixedEvent: String) : History

我很好奇Kotlin是如何编译的。下面是等价的Java代码(使用Intellij [Kotlin字节码]特性生成):

public interface History {
@NotNull
LocalDateTime getDate();


@NotNull
String getName();


int getValue();
}


public final class FixedHistory implements History {
@NotNull
private final LocalDateTime date;
@NotNull
private final String name;
private int value;
@NotNull
private final String fixedEvent;


// Boring getters/setters as usual..
// copy(), toString(), equals(), hashCode(), ...
}

正如您所看到的,它的工作方式完全像一个普通的数据类!

可以从非数据类继承数据类。不允许从另一个数据类继承数据类,因为在继承的情况下,无法使编译器生成的数据类方法一致且直观地工作。

@Željko trogrliic的答案是正确的。但是我们必须像在抽象类中一样重复相同的字段。

同样,如果在抽象类中有抽象的子类,则在数据类中不能从这些抽象子类扩展字段。我们应该首先创建数据子类,然后定义字段。

abstract class AbstractClass {
abstract val code: Int
abstract val url: String?
abstract val errors: Errors?


abstract class Errors {
abstract val messages: List<String>?
}
}






data class History(
val data: String?,


override val code: Int,
override val url: String?,
// Do not extend from AbstractClass.Errors here, but Kotlin allows it.
override val errors: Errors?
) : AbstractClass() {


// Extend a data class here, then you can use it for 'errors' field.
data class Errors(
override val messages: List<String>?
) : AbstractClass.Errors()
}

可以从非数据类继承数据类。

基类

open class BaseEntity (


@ColumnInfo(name = "name") var name: String? = null,
@ColumnInfo(name = "description") var description: String? = null,
// ...
)

子类

@Entity(tableName = "items", indices = [Index(value = ["item_id"])])
data class CustomEntity(


@PrimaryKey
@ColumnInfo(name = "id") var id: Long? = null,
@ColumnInfo(name = "item_id") var itemId: Long = 0,
@ColumnInfo(name = "item_color") var color: Int? = null


) : BaseEntity()

它工作。

Kotlin Traits可以提供帮助。

interface IBase {
val prop:String
}


interface IDerived : IBase {
val derived_prop:String
}

数据类

data class Base(override val prop:String) : IBase


data class Derived(override val derived_prop:String,
private val base:IBase) :  IDerived, IBase by base

示例使用

val b = Base("base")
val d = Derived("derived", b)


print(d.prop) //prints "base", accessing base class property
print(d.derived_prop) //prints "derived"

这种方法也可以用来解决@Parcelize的继承问题

@Parcelize
data class Base(override val prop:Any) : IBase, Parcelable


@Parcelize // works fine
data class Derived(override val derived_prop:Any,
private val base:IBase) : IBase by base, IDerived, Parcelable

虽然在层次结构中正确实现equals()确实相当棘手,但支持继承其他方法仍然很好,例如:toString()

为了更具体一点,让我们假设我们有以下构造(显然,它不起作用,因为toString()没有继承,但如果它继承了不是很好吗?):

abstract class ResourceId(open val basePath: BasePath, open val id: Id) {


// non of the subtypes inherit this... unfortunately...
override fun toString(): String = "/${basePath.value}/${id.value}"
}
data class UserResourceId(override val id: UserId) : ResourceId(UserBasePath, id)
data class LocationResourceId(override val id: LocationId) : ResourceId(LocationBasePath, id)

假设我们的UserLocation实体返回它们相应的资源id(分别为UserResourceIdLocationResourceId),在任何ResourceId上调用toString()都可以得到一个相当漂亮的小表示,通常对所有子类型都有效:/users/4587/locations/23等。不幸的是,由于被重写的toString()方法没有继承自抽象基类ResourceId的子类型,因此调用toString()实际上会导致一个不太漂亮的表示:Location1, Location2

还有其他方法可以对上述模型进行建模,但这些方法要么迫使我们使用非数据类(错过了数据类的许多好处),要么我们最终在所有数据类中复制/重复toString()实现(没有继承)。

我发现在DTO中使用继承的最好方法是在java中使用Lombok插件创建数据类。

不要忘记在注释中将lombok.equalsAndHashCode.callSuper设置为true

data class User(val id:Long, var name: String)
fun main() {
val user1 = User(id:1,name:"Kart")
val name = user1.name
println(name)
user1.name = "Michel"
val  user2 = User(id:1,name:"Michel")
println(user1 == user2)
println(user1)
val updateUser = user1.copy(name = "DK DK")
println(updateUser)
println(updateUser.component1())
println(updateUser.component2())
val (id,name) = updateUser
println("$id,$name") }

//这里是检查图像为什么它显示错误id:1(编译器说,使用=而不是双点,我插入的值)下面的输出

我是怎么做到的。

open class ParentClass {
var var1 = false
var var2: String? = null
}


data class ChildClass(
var var3: Long
) : ParentClass()

它工作得很好。

实现继承的另一种方法是使用带有抽象值的类

    sealed class Parent {
    

abstract val someVal: String
    

data class Child1(
override val someVal: String,
) : Parent()
    

data class Child2(
override val someVal: String,
) : Parent()
}

我想要继承的数据类没有不应该封装在接口中的行为。带有私有数据类的“common”;作为接口的实现者,所有对象都可以享受data的好处,而它感觉像是UnitVector扩展了V

interface Vector {
companion object {
fun build(x : Float ...) : Vector = V(x ...)
private data class V(override val x : Float ...) : Vector
}
val x : Float
//functions, y etc.
}
data class UnitVector(override var x : Float ...) : Vector {
init {
//special behavior
}
}