Kotlin sealed class with data classes not recognizing subclass

Kotlin sealed class with data classes not recognizing subclass

我正在尝试定义一个 Kotlin 密封 class,它由许多数据 class 组成。后者用于定义表示房间数据库中 mySQL 表的数据传输对象 (DTO)。我引入了密封 class 来概括不同的 DTO,并能够通过它们的超类型(DTO - 每个特定 DTO 具有的共同属性,例如“id”等)来引用它们。

编译没问题,但我不认为 Kotlin 理解数据 classes 是密封 class 的“subclasses”——不管我是否将它们全部定义在与密封(父)class 相同的文件中,或者 - 首选 - 在同一个包中......根据 Kotlin documentation,这两个选项都应该是有效的选择。 =14=]

知道我哪里出错了吗?谢谢

代码:

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 没有在密封的 class 和数据 classes (= subclasses) 之间建立所需的联系,因为它仍然询问我在“when”表达式中的“else”分支作用于密封 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

}

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

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/

因此,要解决您的问题,只需将方法签名更改为 return 一种 DTO 而不是 DTO?

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

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

这是一个基于您的代码的精简示例,无需 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"
        }

    }
}