在 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)
假设我们的User
和Location
实体返回它们相应的资源ID(分别为UserResourceId
和LocationResourceId
),在任何ResourceId
上调用toString()
可能会产生一个非常漂亮的小表示,通常是有效的对于所有子类型:/users/4587
、/locations/23
等。不幸的是,由于没有继承自抽象基 ResourceId
的覆盖 toString()
方法的子类型,调用 toString()
实际上会导致不太漂亮的表示:@987654338 @,<LocationResourceId(id=LocationId(value=23))>
还有其他方法可以对上述内容进行建模,但这些方法要么迫使我们使用非数据类(错过了数据类的许多好处),要么我们最终复制/重复 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 中扩展数据类的主要内容,如果未能解决你的问题,请参考以下文章