Kotlin 密封类的数据类无法识别子类
我试图定义一个由许多数据类组成的 Kotlin 密封类。后者用于定义表示房间数据库中的 mySQL 表的数据传输对象 (DTO)。我引入了密封类来概括不同的 DTO,并能够通过其超类型来引用它们(DTO - 每个特定 DTO 具有的公共属性,例如“id”等)。
这个编译没问题,但我不认为 Kotlin 理解数据类是密封类的“子类” - 无论我是否将它们全部定义在与密封(父)类相同的文件中,或者 - 首选- 在同一个包中...根据 Kotlin 文档。
知道吗,我哪里出错了?谢谢。
代码:
package com.tanfra.shopmob.smob.data.local.dto
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.tanfra.shopmob.smob.data.local.utils.*
/**
* supertype, common to all DTO types - generic part of any DTO class
* (properties declared abstract --> implementation delegated to inheriting concrete class)
*/
sealed class Dto {
abstract val id: String
abstract var itemStatus: SmobItemStatus
abstract var itemPosition: Long
}
@Entity(tableName = "smobGroups")
@RewriteQueriesToDropUnusedColumns
data class SmobGroupDTO(
@PrimaryKey @ColumnInfo(name = "groupId") override val id: String = "invalid smob group entry",
@ColumnInfo(name = "groupItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "groupItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "groupName") var name: String = "",
@ColumnInfo(name = "groupDescription") var description: String? = "",
@ColumnInfo(name = "groupType") var type: GroupType = GroupType.OTHER,
@ColumnInfo(name = "groupMembers") var members: List<String> = listOf(),
@ColumnInfo(name = "groupActivityDate") var activityDate: String = "",
@ColumnInfo(name = "groupActivityReps") var activityReps: Long = 0,
) : Dto()
@Entity(tableName = "smobLists")
@RewriteQueriesToDropUnusedColumns
data class SmobListDTO(
@PrimaryKey @ColumnInfo(name = "listId") override val id: String = "invalid smob list id",
@ColumnInfo(name = "listItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "listItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "listName") var name: String = "",
@ColumnInfo(name = "listDescription") var description: String? = "",
@ColumnInfo(name = "listItems") var items: List<SmobListItem> = listOf(),
@ColumnInfo(name = "listMembers") var members: List<String> = listOf(),
@ColumnInfo(name = "listLifecycleStatus") var lcStatus: SmobItemStatus = SmobItemStatus.OPEN,
@ColumnInfo(name = "listLifecycleCompletion") var lcCompletion: Double = -1.0,
) : Dto()
@Entity(tableName = "smobProducts")
@RewriteQueriesToDropUnusedColumns
data class SmobProductDTO(
@PrimaryKey @ColumnInfo(name = "productId") override val id: String = "invalid smob product id",
@ColumnInfo(name = "productItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "productItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "productName") var name: String = "",
@ColumnInfo(name = "productDescription") var description: String? = "",
@ColumnInfo(name = "productImageUrl") var imageUrl: String? = "",
@ColumnInfo(name = "productCategoryMain") var categoryMain: ProductMainCategory = ProductMainCategory.OTHER,
@ColumnInfo(name = "productCategorySub") var categorySub: ProductSubCategory = ProductSubCategory.OTHER,
@ColumnInfo(name = "productActivityDate") var activityDate: String = "",
@ColumnInfo(name = "productActivityReps") var activityReps: Long = 0L,
@ColumnInfo(name = "productInShopCategory") var inShopCategory: ShopCategory = ShopCategory.OTHER,
@ColumnInfo(name = "productInShopName") var inShopName: String = "dummy shop",
@ColumnInfo(name = "productInShopLocation") var inShopLocation: ShopLocation = ShopLocation(0.0, 0.0),
) : Dto()
@Entity(tableName = "smobShops")
@RewriteQueriesToDropUnusedColumns
data class SmobShopDTO(
@PrimaryKey @ColumnInfo(name = "shopId") override val id: String = "invalid smob shop id",
@ColumnInfo(name = "shopItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "shopItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "shopName") var name: String = "",
@ColumnInfo(name = "shopDescription") var description: String? = "",
@ColumnInfo(name = "shopImageUrl") var imageUrl: String? = "",
@ColumnInfo(name = "shopLocationLatitude") var locLat: Double = 0.0,
@ColumnInfo(name = "shopLocationLongitude") var locLong: Double = 0.0,
@ColumnInfo(name = "shopType") var type: ShopType = ShopType.INDIVIDUAL,
@ColumnInfo(name = "shopCategory") var category: ShopCategory = ShopCategory.OTHER,
@ColumnInfo(name = "shopBusiness") var business: List<String> = listOf()
) : Dto()
@Entity(tableName = "smobUsers")
@RewriteQueriesToDropUnusedColumns
data class SmobUserDTO(
@PrimaryKey @ColumnInfo(name = "userId") override val id: String = "invalid smob user id",
@ColumnInfo(name = "userItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "userItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "userUsername") var username: String = "",
@ColumnInfo(name = "userName") var name: String = "",
@ColumnInfo(name = "userEmail") var email: String = "",
@ColumnInfo(name = "userImageUrl") var imageUrl: String? = ""
) : Dto()
我相信 Kotlin 没有在密封类和数据类(=子类)之间建立所需的连接的原因是它仍然要求我在“when”中提供“else”分支作用于密封类成员的表达式:
package com.tanfra.shopmob.smob.data.net.nto2dto
import com.tanfra.shopmob.smob.data.local.dto.*
import com.tanfra.shopmob.smob.data.net.nto.*
import com.tanfra.shopmob.smob.data.repo.ato.Ato
// ATO --> DTO
fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO? {
return when (d) {
is SmobGroupDTO -> {
SmobGroupDTO(
id = (this as SmobGroupNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
type = this.type,
members = this.members,
activityDate = this.activity.date,
activityReps = this.activity.reps,
) as DTO
}
is SmobListDTO -> {
SmobListDTO(
id = (this as SmobListNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
items = this.items,
members = this.members,
lcStatus = this.lifecycle.status,
lcCompletion = this.lifecycle.completion,
) as DTO
}
is SmobProductDTO -> {
SmobProductDTO(
id = (this as SmobProductNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
categoryMain = this.category.main,
categorySub = this.category.sub,
activityDate = this.activity.date,
activityReps = this.activity.reps,
inShopCategory = this.inShop.category,
inShopName = this.inShop.name,
inShopLocation = this.inShop.location,
) as DTO
}
is SmobShopDTO -> {
SmobShopDTO(
id = (this as SmobShopNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
locLat = this.location.latitude,
locLong = this.location.longitude,
type = this.type,
category = this.category,
business = this.business,
) as DTO
}
is SmobUserDTO -> {
SmobUserDTO(
id = (this as SmobUserNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
username = this.username,
name = this.name,
email = this.email,
imageUrl = this.imageUrl,
) as DTO
}
else -> null
} // when(DTO) ... resolving generic type to concrete type
}
I am trying to define a Kotlin sealed class which consists of a number of data classes. The latter are used to define data transfer objects (DTO) representing the mySQL tables in a room database. I introduced the sealed class to generalize the different DTOs and be able to refer to them all by their supertype (DTO - the common properties each specific DTO has, eg. "id", etc.).
This compiles alright, but I don't think Kotlin understands that the data classes are the "subclasses" of the sealed class - no matter whether I defined them all in the same file as the sealed (parent) class, or - the preferred choice - in the same package... both options should be valid choices, according to the Kotlin documentation.
Any idea, where I'm going wrong here? Thanks.
Code:
package com.tanfra.shopmob.smob.data.local.dto
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.tanfra.shopmob.smob.data.local.utils.*
/**
* supertype, common to all DTO types - generic part of any DTO class
* (properties declared abstract --> implementation delegated to inheriting concrete class)
*/
sealed class Dto {
abstract val id: String
abstract var itemStatus: SmobItemStatus
abstract var itemPosition: Long
}
@Entity(tableName = "smobGroups")
@RewriteQueriesToDropUnusedColumns
data class SmobGroupDTO(
@PrimaryKey @ColumnInfo(name = "groupId") override val id: String = "invalid smob group entry",
@ColumnInfo(name = "groupItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "groupItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "groupName") var name: String = "",
@ColumnInfo(name = "groupDescription") var description: String? = "",
@ColumnInfo(name = "groupType") var type: GroupType = GroupType.OTHER,
@ColumnInfo(name = "groupMembers") var members: List<String> = listOf(),
@ColumnInfo(name = "groupActivityDate") var activityDate: String = "",
@ColumnInfo(name = "groupActivityReps") var activityReps: Long = 0,
) : Dto()
@Entity(tableName = "smobLists")
@RewriteQueriesToDropUnusedColumns
data class SmobListDTO(
@PrimaryKey @ColumnInfo(name = "listId") override val id: String = "invalid smob list id",
@ColumnInfo(name = "listItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "listItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "listName") var name: String = "",
@ColumnInfo(name = "listDescription") var description: String? = "",
@ColumnInfo(name = "listItems") var items: List<SmobListItem> = listOf(),
@ColumnInfo(name = "listMembers") var members: List<String> = listOf(),
@ColumnInfo(name = "listLifecycleStatus") var lcStatus: SmobItemStatus = SmobItemStatus.OPEN,
@ColumnInfo(name = "listLifecycleCompletion") var lcCompletion: Double = -1.0,
) : Dto()
@Entity(tableName = "smobProducts")
@RewriteQueriesToDropUnusedColumns
data class SmobProductDTO(
@PrimaryKey @ColumnInfo(name = "productId") override val id: String = "invalid smob product id",
@ColumnInfo(name = "productItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "productItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "productName") var name: String = "",
@ColumnInfo(name = "productDescription") var description: String? = "",
@ColumnInfo(name = "productImageUrl") var imageUrl: String? = "",
@ColumnInfo(name = "productCategoryMain") var categoryMain: ProductMainCategory = ProductMainCategory.OTHER,
@ColumnInfo(name = "productCategorySub") var categorySub: ProductSubCategory = ProductSubCategory.OTHER,
@ColumnInfo(name = "productActivityDate") var activityDate: String = "",
@ColumnInfo(name = "productActivityReps") var activityReps: Long = 0L,
@ColumnInfo(name = "productInShopCategory") var inShopCategory: ShopCategory = ShopCategory.OTHER,
@ColumnInfo(name = "productInShopName") var inShopName: String = "dummy shop",
@ColumnInfo(name = "productInShopLocation") var inShopLocation: ShopLocation = ShopLocation(0.0, 0.0),
) : Dto()
@Entity(tableName = "smobShops")
@RewriteQueriesToDropUnusedColumns
data class SmobShopDTO(
@PrimaryKey @ColumnInfo(name = "shopId") override val id: String = "invalid smob shop id",
@ColumnInfo(name = "shopItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "shopItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "shopName") var name: String = "",
@ColumnInfo(name = "shopDescription") var description: String? = "",
@ColumnInfo(name = "shopImageUrl") var imageUrl: String? = "",
@ColumnInfo(name = "shopLocationLatitude") var locLat: Double = 0.0,
@ColumnInfo(name = "shopLocationLongitude") var locLong: Double = 0.0,
@ColumnInfo(name = "shopType") var type: ShopType = ShopType.INDIVIDUAL,
@ColumnInfo(name = "shopCategory") var category: ShopCategory = ShopCategory.OTHER,
@ColumnInfo(name = "shopBusiness") var business: List<String> = listOf()
) : Dto()
@Entity(tableName = "smobUsers")
@RewriteQueriesToDropUnusedColumns
data class SmobUserDTO(
@PrimaryKey @ColumnInfo(name = "userId") override val id: String = "invalid smob user id",
@ColumnInfo(name = "userItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
@ColumnInfo(name = "userItemPosition") override var itemPosition: Long = -1L,
@ColumnInfo(name = "userUsername") var username: String = "",
@ColumnInfo(name = "userName") var name: String = "",
@ColumnInfo(name = "userEmail") var email: String = "",
@ColumnInfo(name = "userImageUrl") var imageUrl: String? = ""
) : Dto()
The reason, I believe Kotlin didn't make the desired connection between the sealed class and the data classes (= subclasses) is that it still asks me for an "else" branch in "when" expressions which act upon the members of the sealed class:
package com.tanfra.shopmob.smob.data.net.nto2dto
import com.tanfra.shopmob.smob.data.local.dto.*
import com.tanfra.shopmob.smob.data.net.nto.*
import com.tanfra.shopmob.smob.data.repo.ato.Ato
// ATO --> DTO
fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO? {
return when (d) {
is SmobGroupDTO -> {
SmobGroupDTO(
id = (this as SmobGroupNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
type = this.type,
members = this.members,
activityDate = this.activity.date,
activityReps = this.activity.reps,
) as DTO
}
is SmobListDTO -> {
SmobListDTO(
id = (this as SmobListNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
items = this.items,
members = this.members,
lcStatus = this.lifecycle.status,
lcCompletion = this.lifecycle.completion,
) as DTO
}
is SmobProductDTO -> {
SmobProductDTO(
id = (this as SmobProductNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
categoryMain = this.category.main,
categorySub = this.category.sub,
activityDate = this.activity.date,
activityReps = this.activity.reps,
inShopCategory = this.inShop.category,
inShopName = this.inShop.name,
inShopLocation = this.inShop.location,
) as DTO
}
is SmobShopDTO -> {
SmobShopDTO(
id = (this as SmobShopNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
locLat = this.location.latitude,
locLong = this.location.longitude,
type = this.type,
category = this.category,
business = this.business,
) as DTO
}
is SmobUserDTO -> {
SmobUserDTO(
id = (this as SmobUserNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
username = this.username,
name = this.name,
email = this.email,
imageUrl = this.imageUrl,
) as DTO
}
else -> null
} // when(DTO) ... resolving generic type to concrete type
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这是由于您在方法签名上使用泛型引起的:
Reddit 上有一个很好的线程,它非常像您的示例。请参阅此处:
https://www.reddit.com/r/Kotlin/ comments/ei8zh5/kotlin_requires_else_branch_in_when_statement/
所以,要解决你的问题,只需更改方法签名以返回
DTO
类型而不是DTO?
这几乎就像当您将 DTO 设为密封类时,编译器忘记了 DTO 是一个密封类。通用参数,因此您需要进行详尽的检查。
当您在
when
语句中使用is
时,Kotlin 无论如何都会将 DTO 智能转换为正确的类型,因此不需要泛型参数。这是一个基于您的代码的简化示例,无需其他即可工作:
It's caused by your use of generics on the method signature :
There's a good thread on Reddit which is very like your example. See here:
https://www.reddit.com/r/Kotlin/comments/ei8zh5/kotlin_requires_else_branch_in_when_statement/
So, to solve your problem, just change the method signature to return a type of
DTO
notDTO?
It's almost as if the compiler is forgetting that the DTO is a sealed class when you make it a generic parameter, so you need an exhaustive check.
As you as using
is
in awhen
statement Kotlin will smart cast the DTO to the right type anyway, so no need for the generic argument.Here's a cut down example based on your code that works without the else: