在 Kotlin 中扩展数据类

Posted

技术标签:

【中文标题】在 Kotlin 中扩展数据类【英文标题】:Extend data class in Kotlin 【发布时间】:2014-12-14 03:59:07 【问题描述】:

数据类似乎是 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()

【问题讨论】:

Kotlin 隐式创建方法componentN(),返回第 N 个属性的值。请参阅 Multi-Declarations 上的文档 打开属性,也可以做Resource抽象或者使用编译器插件。 Kotlin 对开放/封闭原则非常严格。 @Dmitry 由于我们无法扩展数据类,您的“解决方案”是否会保持父类变量打开并简单地在子类中覆盖它们? 【参考方案1】:

事实是:数据类在继承方面表现不佳。我们正在考虑禁止或严格限制数据类的继承。例如,众所周知,没有办法在非抽象类的层次结构中正确实现equals()

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

【讨论】:

我不相信这个问题有很多解决方案。到目前为止,我的观点是数据类根本不能有数据子类。 如果我们有一个库代码,例如一些 ORM,并且我们想扩展它的模型以拥有我们的持久数据模型,该怎么办? @AndreyBreslav Docs on Data classes 不反映 Kotlin 1.1 之后的状态。自 1.1 以来,数据类和继承如何协同工作? @EugenPechanec 见这个例子:kotlinlang.org/docs/reference/… 如果我们不能对数据类使用继承,这意味着当逻辑相同而数据不同时会出现大量重复代码......由于缺乏继承支持,我正在重复大量代码,非常非常糟糕【参考方案2】:

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

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()

【讨论】:

这似乎是最灵活的。我非常希望我们可以让数据类相互继承...... 您好先生,感谢您处理数据类继承的简洁方式。当我将抽象类用作通用类型时,我遇到了一个问题。我收到 Type Mismatch 错误:“必需的 T,找到:资源”。你能告诉我如何在泛型中使用它吗? 我几乎失去了希望。谢谢! 复制参数似乎是实现继承的一种糟糕方式。从技术上讲,由于 Book 继承自 Resource,它应该知道 id 和 location 存在。真的不需要指定这些。 @androidDev 它们不存在,因为它们是抽象的。【参考方案3】:

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

如果你不喜欢抽象类,使用接口怎么样?

Kotlin 中的接口可以具有 属性,如 this article..

所示
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(), ...

如您所见,它的工作方式与普通数据类完全一样!

【讨论】:

不幸的是,为数据类实现接口模式不适用于 Room 的架构。 @AdamHurwitz 太糟糕了.. 我没注意到! @Adam Hurwitz 刚遇到这个问题,能解释一下原因吗?【参考方案4】:

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

【讨论】:

【参考方案5】:

@Željko Trogrlić 的答案是正确的。但是我们必须重复与抽象类中相同的字段。

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

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()

【讨论】:

我们可以将 History.Errors 移动到 AbstractClass.Errors.Companion.SimpleErrors 或外部,并在数据类中使用它,而不是在每个继承数据类中复制它? @TWiStErRob,很高兴听到这么有名的人!我的意思是 History.Errors 可以在每个类中更改,因此我们应该覆盖它(例如,添加字段)。【参考方案6】:

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

基类

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()

成功了。

【讨论】:

除了现在不能设置name和description属性,如果添加到构造函数中,数据类需要val/var,会覆盖基类属性。 不幸的是,将为该数据类生成的equals()hashCode()toString() 不包含来自基类的属性。这消除了在此处使用数据类的好处。【参考方案7】:

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

【讨论】:

【参考方案8】:

虽然在层次结构中正确实现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 等。不幸的是,由于没有继承自抽象基 ResourceId 的覆盖 toString() 方法的子类型,调用 toString() 实际上会导致不太漂亮的表示:@987654338 @,&lt;LocationResourceId(id=LocationId(value=23))&gt;

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:
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") 

//这里是check the image why it shows error id:1 (compiler says that use = insted of double dot where i insert the value)下面的输出

【讨论】:

嘿,我知道你是新来的。回答问题时请详细说明。结帐***.com/help/how-to-answer了解如何回答问题。【参考方案11】:

我是怎么做到的。

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


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

一切正常。

【讨论】:

如果您想要求构造每个 ChildClass 并传递 var1 和 var2 的值,您将如何构造 ChildClass?

以上是关于在 Kotlin 中扩展数据类的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin初识Kotlin之扩展函数与属性

通用扩展类 AND 在 Kotlin 中实现接口

Kotlin 类扩展

Kotlin 类扩展

Kotlin基础-扩展

Kotlin中扩展函数和infix关键字详解