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

时间:2018-03-08 08:57:18

标签: enums kotlin sealed-class

我想基于Int创建一个颜色对象。我可以使用sealed classenum获得相同的结果,并且想知道一个是否优于另一个。

使用sealed class

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

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方法来实现相同的结果?

2 个答案:

答案 0 :(得分:7)

<input [disabled]="true" id="name" type="text"> 类被认为是&#34;枚举类的扩展&#34; 。它们可以存在于包含状态的多个实例中。由于您不需要多次实例化值并且它们不提供特殊行为,枚举应该恰好适用于用例并且更容易理解。

在您的示例中,根本不需要sealed

答案 1 :(得分:1)

让我们用对比示例讨论各个方面枚举和密封类之间的区别。这将帮助您根据用例选择一个。


属性

枚举

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

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

这里,我们仅需要trackingIdDISPATCHED的{​​{1}},DELIVERED的值必须为PREPARING

密封类

对于密封类,对于每个子类型,我们可以具有不同的属性:

null

在这里,每个子类型都有不同的属性。 sealed class DeliveryStatus class Preparing() : DeliveryStatus() class Dispatched(val trackingId: String) : DeliveryStatus() class Delivered(val trackingId: String, val receiversName: String) : DeliveryStatus() 在我们的用例中不需要属性,因此我们可以灵活地不指定任何属性,而与枚举中的强制Preparing值不同。 null具有一个属性,而Dispatched具有两个属性。

考虑问题中的示例Delivered,您对所有常量都有一个公用的Color(val value: Int)属性,并且由于不需要为不同的常量使用不同的属性,因此在这种情况下,应使用枚举。 / p>


功能

枚举

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

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() } 函数abstract,我们必须在每个枚举值中cancelOrder()。也就是说,对于不同的枚举值,我们不能具有不同的功能。

用法:

override

密封类

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

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") } 的{​​{1}},cancelOrder()的{​​{1}}和Preparing的{​​{1}}。这样可以使意图更清晰并使代码更具可读性,并且如果我们不想这样做,我们可以选择不具有该功能。

用法:

rejectDelivery()

如果我们希望为所有子类型提供一个通用函数,例如枚举示例,则可以在密封类中通过在密封类本身中定义它,然后在子类型中覆盖它来实现该功能:

Dispatched

对所有类型都具有通用功能的优点是,我们不必使用returnItem()运算符进行类型检查。我们可以简单地使用枚举示例的Delivered类中所示的多态性。


继承

枚举

由于class DeliveryManager { fun cancelOrder(status: DeliveryStatus) = when(status) { is Preparing -> status.cancelOrder() is Dispatched -> status.rejectDelivery() is Delivered -> status.returnItem() } } 是对象,因此无法扩展它们:

sealed class DeliveryStatus {
    abstract fun cancelOrder()
}

is是隐式DeliveryManager,因此不能被其他类扩展:

enum

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

class LocallyDispatched : DeliveryStatus.DISPATCHED { }    // Error

密封类

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

enum class

密封类本身可以扩展!:

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"),
}

在此示例中,我们可以创建DISPATCHED// Single instance val dispatched1 = DeliveryStatus.DISPATCHED // OK // Another instance val dispatched2 = DeliveryStatus.DISPATCHED("45234") // Error 的多个实例。注意,我们已经利用了密封类的子类型作为单例object,常规sealed class DeliveryStatus object Preparing : DeliveryStatus() class Dispatched(val trackingId: String) : DeliveryStatus() data class Delivered(val receiversName: String) : DeliveryStatus() Dispatched的能力。 Delivered只能有一个object,就像枚举值一样:

class

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


可序列化和可比较

枚举

Kotlin中的每个object都由抽象类// Multiple Instances val dispatched1 = Dispatched("27211") // OK val dispatched2 = Dispatched("45234") // OK // Single Instance val preparing1 = Preparing // OK val preparing2 = Preparing() // Error 隐式扩展。因此,所有枚举值都自动具有DispatchedtrackingIdenum classjava.lang.Enumequals()的实现。我们不必定义它们。

密封类

对于密封类,我们需要手动定义它们,或者对自动toString()hashCode()Serializable使用Comparable,然后实现data class和{{ 1}}。


性能

枚举

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

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

缺点是,即使不使用枚举,枚举也会保留在内存中,这可以使内存始终处于占用状态。

如果您的应用中有100到200个枚举,则无需担心所有这些。但是,如果您拥有的资源不止于此,则可以决定是否要使用枚举,具体取决于诸如枚举数,枚举数是否一直使用以及分配给JVM的内存量之类的事实。

equals()表达式中,枚举值的比较更快,因为在幕后,它使用toString()来比较对象。因此,对于问题中给出的示例,应该首选枚举,因为在这种情况下它们会更快。

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

密封类

密封类只是常规类,唯一的例外是它们需要在同一文件中扩展。因此,它们的性能相当于常规类。

密封类的子类型的对象像常规类的对象一样被垃圾收集。因此,您必须承担垃圾收集和对象创建的成本。

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

如果您使用hashcode()声明来扩展密封类,则这些对象将作为单例对象使用,并且不会像枚举一样被垃圾回收。

密封类的类型比较在Serializable表达式中比较慢,因为在幕后它使用Comparable比较类型。但是,在这种情况下,枚举和密封类之间的速度差异很小。仅当您在循环中比较数千个常量时,它才重要。


就是这样!希望这将使您可以更轻松地选择一个。