当前位置: 首页 > 知识库问答 >
问题:

使用关联类型时密封类与枚举

葛越
2023-03-14

我想创建一个基于int的颜色对象。我可以使用密封类枚举获得相同的结果,我想知道其中一个是否比另一个更好。

使用密封类:

sealed class SealedColor(val value: Int) {
    class Red : SealedColor(0)
    class Green : SealedColor(1)
    class Blue : SealedColor(2)

    companion object {
        val map = hashMapOf(
            0 to Red(),
            1 to Green(),
            2 to Blue()
        )
    }
}

val sealedColor: SealedColor = SealedColor.map[0]!!
when (sealedColor) {
                is SealedColor.Red -> print("Red value ${sealedColor.value}")
                is SealedColor.Green -> print("Green value ${sealedColor.value}")
                is SealedColor.Blue -> print("Blue value ${sealedColor.value}")
            }
enum class EnumColor(val value: Int) {
    Red(0),
    Green(1),
    Blue(2);

    companion object {
        fun valueOf(value: Int): EnumColor {
            return EnumColor
                .values()
                .firstOrNull { it.value == value }
                    ?: throw NotFoundException("Could not find EnumColor with value: $value")
        }
    }
}

val enumColor: EnumColor = EnumColor.valueOf(0)
when (enumColor) {
            EnumColor.Red -> print("Red value ${enumColor.value}")
            EnumColor.Green -> print("Green value ${enumColor.value}")
            EnumColor.Blue -> print("Blue value ${enumColor.value}")
        }

它们在性能方面相等吗?有没有更好的kotlin方式达到同样的结果?

共有1个答案

卢鸿博
2023-03-14

让我们通过对比示例讨论枚举和密封类在各个方面的区别。这将帮助您根据您的用例选择一个而不是另一个。

枚举

在枚举类中,每个枚举值不能具有自己的唯一属性。您必须为每个枚举值具有相同的属性:

enum class DeliveryStatus(val trackingId: String?) {
    PREPARING(null),
    DISPATCHED("27211"),
    DELIVERED("27211"),
}

在密封类的情况下,我们可以为每个子类型具有不同的属性:

sealed class DeliveryStatus
class Preparing() : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val trackingId: String, val receiversName: String) : DeliveryStatus()

这里我们对每个子类型都有不同的属性。准备不需要我们的用例的属性,所以我们可以灵活地不指定任何属性,这与枚举中强制的null值不同。subsective有一个属性,而delivered有两个属性。

考虑问题中的示例color(valvalue:Int),所有常量都有一个公共的value:Int属性,由于不同的常量不需要不同的属性,因此在本例中应该使用枚举。

枚举

枚举可以有抽象函数,也可以有常规函数。但与属性一样,每个枚举值也必须具有相同的功能:

enum class DeliveryStatus {
    PREPARING {
        override fun cancelOrder() = println("Cancelled successfully")
    },
    DISPATCHED {
        override fun cancelOrder() = println("Delivery rejected")
    },
    DELIVERED {
        override fun cancelOrder() = println("Return initiated")
    };

    abstract fun cancelOrder()
}

在本例中,我们有一个抽象函数cancelorder(),我们必须在每个枚举值中重写。这意味着,我们不能对不同的枚举值有不同的函数。

class DeliveryManager {
    fun cancelOrder(status: DeliveryStatus) {
        status.cancelOrder()
    }
}

在密封类中,我们可以对不同的子类型具有不同的功能:

sealed class DeliveryStatus

class Preparing : DeliveryStatus() {
    fun cancelOrder() = println("Cancelled successfully")
}

class Dispatched : DeliveryStatus() {
    fun rejectDelivery() = println("Delivery rejected")
}

class Delivered : DeliveryStatus() {
    fun returnItem() = println("Return initiated")
}

这里我们有不同的函数:CancelOrder()用于准备RejectDelivery()用于调度Returnitem()用于Delivery。这使得意图更清晰,代码更易读,而且我们可以选择不拥有函数,以防我们不想。

用法:

class DeliveryManager {
    fun cancelOrder(status: DeliveryStatus) = when(status) {
        is Preparing -> status.cancelOrder()
        is Dispatched -> status.rejectDelivery()
        is Delivered -> status.returnItem()
    }
}

如果我们希望所有子类型都有一个通用函数,就像枚举示例中那样,我们可以通过在sealed类本身中定义它,然后在子类型中重写它来在sealed类中使用它:

sealed class DeliveryStatus {
    abstract fun cancelOrder()
}

对于所有类型都有一个通用函数的好处是,我们不必使用is运算符进行类型检查。我们可以简单地使用多态性,如枚举示例的DeliveryManager类所示。

枚举

由于枚举值是对象,因此不能扩展它们:

class LocallyDispatched : DeliveryStatus.DISPATCHED { }    // Error

枚举类隐式为final,因此不能由其他类扩展:

class FoodDeliveryStatus : DeliveryStatus() { }            // Error

枚举类不能扩展其他类,它们只能扩展接口:

open class OrderStatus { }
interface Cancellable { }

enum class DeliveryStatus : OrderStatus() { }              // Error
enum class DeliveryStatus : Cancellable { }                // OK

密封类

由于密封类的子类型是类型,因此可以扩展它们:

class LocallyDispatched : Dispatched() { }                 // OK

当然,密封类本身可以扩展!:

class PaymentReceived : DeliveryStatus()                   // OK

密封类可以扩展其他类以及接口:

open class OrderStatus { }
interface Cancellable { }

sealed class DeliveryStatus : OrderStatus() { }           // OK
sealed class DeliveryStatus : Cancellable { }             // OK

枚举

由于枚举值是对象而不是类型,我们不能创建它们的多个实例:

enum class DeliveryStatus(val trackingId: String?) {
    PREPARING(null),
    DISPATCHED("27211"),
    DELIVERED("27211"),
}

在本例中,subjectived是一个对象而不是类型,因此它只能作为单个实例存在,我们不能从它创建更多的实例:

// Single instance
val dispatched1 = DeliveryStatus.DISPATCHED               // OK

// Another instance
val dispatched2 = DeliveryStatus.DISPATCHED("45234")      // Error

密封类

sealed class DeliveryStatus
object Preparing : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
data class Delivered(val receiversName: String) : DeliveryStatus()

在本例中,我们可以创建多个discepteddelivered实例。注意,我们已经利用了密封类的子类型作为单个对象、常规数据类的能力。准备只能有一个对象,就像枚举值一样:

// Multiple Instances
val dispatched1 = Dispatched("27211")                     // OK
val dispatched2 = Dispatched("45234")                     // OK

// Single Instance
val preparing1 = Preparing                                // OK
val preparing2 = Preparing()                              // Error

还请注意,在上面的代码中,squisited的每个实例都可以具有trackingid属性的不同值。

枚举

Kotlin中的每个枚举类都由抽象类java.lang.enum隐式扩展。因此,所有枚举值都自动具有equals()tostring()hashcode()serializablecarable的实现。我们不必给它们下定义。

密封类

枚举

枚举不会被收集垃圾,它们会在应用程序的生命周期内留在内存中。这可能是一个好的方面,也可能是一个坏的方面。

垃圾收集过程是昂贵的。对象创建也是如此,我们不想一次又一次地创建相同的对象。因此,使用枚举,可以节省垃圾收集和对象创建的成本。这是好处。

在Android中,当优化启用时,Proguard会将没有函数和属性的枚举转换为整数,这样您就可以在编译时获得枚举的类型安全性,并在运行时获得ints的性能!

密封类

密封类只是常规类,唯一的例外是它们需要在同一个包和同一个编译单元中进行扩展。所以,他们的表现相当于普通班级。

当内存限制较低时,如果需要数千个对象,可以考虑使用密封类而不是枚举。因为垃圾回收器可以在对象不使用时释放内存。

如果您使用object声明来扩展密封类,那么这些对象将充当单例,它们不会像枚举一样被垃圾回收。

When表达式中,密封类的类型比较慢,因为它使用instanceof来比较类型。在本例中,枚举和密封类之间的速度差异非常小。只有当你在一个循环中比较数以千计的常量时,它才有意义。

就是这样!希望,这将使您更容易选择一个而不是另一个。

 类似资料:
  • 问题内容: tl; dr 是否可以实例化具有类型关联值的通用Swift 4枚举成员? 背景 我正在使用一个简单的 Result 枚举(类似于典型的Result): 现在,我想使用这个枚举来表示操作的结果,该操作不会产生实际的结果值;该操作是 成功 还是 失败 。为此,我将类型定义为,但是我在如何创建Result实例方面苦苦挣扎,这都不可行。 问题答案: 在Swift 3中,您可以省略type的关联

  • 下面的代码在无法通过条件颜色时编译。深色和彩色。浅色,因为这两个类是抽象的。 我错过什么了吗?

  • 枚举是为了让程序可读性更好,比如用来描述用户的角色,普通的会员、付费的会员等,同时也限定了用户角色的种类,保证安全性,不会出现上帝角色这种乱入的东西。 枚举的类别与写法 默认值从0开始,依次递增,这个你应该还记得。 普通的枚举 let str = 'something' enum test{ test01, } enum FileAccess { None, Read

  • 主要内容:数据类,密封类数据类 Kotlin 可以创建一个只包含数据的类,关键字为 data: 编译器会自动的从主构造函数中根据所有声明的属性提取以下函数: / 格式如 对应于属性,按声明顺序排列 函数 如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。 为了保证生成代码的一致性以及有意义,数据类需要满足以下条件: 主构造函数至少包含一个参数。 所有的主构造函数的参数必须标识为 或者 ; 数据类不可

  • 数据类 Kotlin 可以创建一个只包含数据的类,关键字为 data: data class User(val name: String, val age: Int) 编译器会自动的从主构造函数中根据所有声明的属性提取以下函数: equals() / hashCode() toString() 格式如 "User(name=John, age=42)" componentN() functio

  • 关联类型 定义一个协议时, 有时在协议定义里声明一个或多个关联类型是很有用的. 关联类型给协议中用到的类型一个占位符名称. 直到采纳协议时, 才指定用于该关联类型的实际类型. 关联类型通过associatedtype关键字指定. 关联类型的应用 protocol Container { associatedtype ItemType mutating func append(_ i