在Kotlin中扩展数据类


176

数据类似乎可以替代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()

3
Kotlin隐式创建componentN()返回第N个属性值的方法。请参阅多声明
Dmitry

要打开属性,您还可以将Resource抽象化或使用编译器插件。Kotlin严格遵守开放/关闭原则。
泽利科Trogrlić

@Dmitry既然我们不能扩展数据类,那么保持父类变量打开并在子类中简单地覆盖它们的“解决方案”是否可以解决?
Archie G.Quiñones19年

Answers:


163

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

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


嗨,安德烈,现在在数据类上生成的equals()如何工作?它仅在类型正确且所有公共字段都相等时才匹配,还是仅在字段相等时才匹配?看起来确实如此,因为类继承对于近似代数数据类型很有价值,因此有必要提出解决该问题的方法。有趣的是,粗略的搜索显示了马丁· 奥德斯基
orospakr 2015年

3
我不认为这个问题有很多解决方案。到目前为止,我的意见是数据类完全不能包含数据子类。
安德烈·布雷斯拉夫

3
如果我们有诸如ORM之类的库代码,并且想要扩展其模型以拥有持久数据模型怎么办?
Krupal Shah 2015年

3
@AndreyBreslav 资料类别的文件无法反映Kotlin 1.1之后的状态。从1.1开始,数据类和继承如何一起发挥作用?
Eugen Pechanec '17

2
:@EugenPechanec看到这个例子kotlinlang.org/docs/reference/...
安德烈Breslav

114

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

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

15
这似乎是最灵活的。我确实非常希望我们可以让数据类彼此继承...
Adam

您好主席先生,感谢您提供了处理数据类继承的巧妙方法。当我将抽象类用作泛型类型时,我遇到了一个问题。我收到一个Type Mismatch错误:“所需的T,找到的:资源”。您能告诉我如何在泛型中使用它吗?
ashwin mahajan

我还想知道泛型是否可以跨抽象类使用。例如,如果位置在一个继承的数据类和一个自定义类中是一个字符串(Location(long: Double, lat: Double))在另一个类中说)?
Robbie Cronin

2
我几乎失去了希望。谢谢!
MichałPowłoka

复制参数似乎是实现继承的不良方法。从技术上讲,由于Book继承自Resource,因此它应该知道id和位置。不必真正指定这些。
AndroidDev

23

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

如果您不喜欢抽象类,那么如何使用接口呢?

Kotlin中的Interface可以具有本文所显示的属性

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

我很好奇科特林是如何编译的。以下是等效的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(), ...
}

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


3
不幸的是,为数据类实现接口模式不适用于Room的体系结构。
亚当·赫维兹

@AdamHurwitz太糟糕了..我没有注意到!
Tura

4

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

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

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

@TWiStErRob,很高兴听到这样的名人!我的意思是History.Errors可以在每个类中更改,因此我们应该覆盖它(例如,添加字段)。
CoolMind

4

科特林特质可以提供帮助。

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

2

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


1

尽管实现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分别),要求toString()对任何ResourceId可能导致一个相当不错的小代表,其通常适用于所有亚型/users/4587/locations/23等等。不幸的是,因为亚型的非继承重写toString()从方法抽象的基础ResourceId,呼吁toString()实际上导致不太漂亮的表示:<UserResourceId(id=UserId(value=4587))><LocationResourceId(id=LocationId(value=23))>

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


0

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

基类

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

有效。


除了现在不能设置名称和描述属性,如果将它们添加到构造函数中,则数据类需要val / var,它将覆盖基类属性。
Brill Pappin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.