我想基于Int
创建一个颜色对象。我可以使用sealed class
和enum
获得相同的结果,并且想知道一个是否优于另一个。
使用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方法来实现相同的结果?
答案 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"),
}
这里,我们仅需要trackingId
和DISPATCHED
的{{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
隐式扩展。因此,所有枚举值都自动具有Dispatched
,trackingId
,enum class
,java.lang.Enum
和equals()
的实现。我们不必定义它们。
密封类
对于密封类,我们需要手动定义它们,或者对自动toString()
,hashCode()
和Serializable
使用Comparable
,然后实现data class
和{{ 1}}。
枚举
枚举不会收集垃圾,它们会在应用程序的生命周期内保留在内存中。这可能是一个好消息或一个坏消息。
垃圾收集过程很昂贵。创建对象也是如此,我们不想一次又一次地创建相同的对象。因此,使用枚举可以节省垃圾收集和对象创建的成本。这就是好处。
缺点是,即使不使用枚举,枚举也会保留在内存中,这可以使内存始终处于占用状态。
如果您的应用中有100到200个枚举,则无需担心所有这些。但是,如果您拥有的资源不止于此,则可以决定是否要使用枚举,具体取决于诸如枚举数,枚举数是否一直使用以及分配给JVM的内存量之类的事实。
在equals()
表达式中,枚举值的比较更快,因为在幕后,它使用toString()
来比较对象。因此,对于问题中给出的示例,应该首选枚举,因为在这种情况下它们会更快。
在Android中,启用优化后,Proguard会将没有函数和属性的枚举转换为整数,因此您可以在编译时获得枚举的类型安全性,并在运行时获得int的性能!
密封类
密封类只是常规类,唯一的例外是它们需要在同一文件中扩展。因此,它们的性能相当于常规类。
密封类的子类型的对象像常规类的对象一样被垃圾收集。因此,您必须承担垃圾收集和对象创建的成本。
当内存不足时,如果需要数千个对象,则可以考虑使用密封类而不是枚举。因为垃圾回收器可以在不使用对象时释放内存。
如果您使用hashcode()
声明来扩展密封类,则这些对象将作为单例对象使用,并且不会像枚举一样被垃圾回收。
密封类的类型比较在Serializable
表达式中比较慢,因为在幕后它使用Comparable
比较类型。但是,在这种情况下,枚举和密封类之间的速度差异很小。仅当您在循环中比较数千个常量时,它才重要。
就是这样!希望这将使您可以更轻松地选择一个。