Kotlin 密封类的数据类无法识别子类

发布于 2025-01-13 21:37:17 字数 8797 浏览 6 评论 0原文

我试图定义一个由许多数据类组成的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

◇流星雨 2025-01-20 21:37:17

这是由于您在方法签名上使用泛型引起的:

fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO?

Reddit 上有一个很好的线程,它非常像您的示例。请参阅此处:

https://www.reddit.com/r/Kotlin/ comments/ei8zh5/kotlin_requires_else_branch_in_when_statement/

所以,要解决你的问题,只需更改方法签名以返回 DTO 类型而不是 DTO?

这几乎就像当您将 DTO 设为密封类时,编译器忘记了 DTO 是一个密封类。通用参数,因此您需要进行详尽的检查。

当您在 when 语句中使用 is 时,Kotlin 无论如何都会将 DTO 智能转换为正确的类型,因此不需要泛型参数。

这是一个基于您的代码的简化示例,无需其他即可工作:

package paul.sealed

sealed class DTO {
    abstract val id: String
}

data class SmobGroupDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()
data class SmobListDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()

fun main() {

    fun processDTO(dto: DTO): String {

        return when (dto) {
            is SmobGroupDTO -> "Group"
            is SmobListDTO -> "List"
        }

    }
}

It's caused by your use of generics on the method signature :

fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO?

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 not DTO?

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 a when 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:

package paul.sealed

sealed class DTO {
    abstract val id: String
}

data class SmobGroupDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()
data class SmobListDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()

fun main() {

    fun processDTO(dto: DTO): String {

        return when (dto) {
            is SmobGroupDTO -> "Group"
            is SmobListDTO -> "List"
        }

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文