preface

The enumeration and sealing classes in Kotlin have duplicate functions. How to choose? Here’s a look at the two to help you understand the relationship. This article was translated from Stack Overflow.

A, features,

The enumeration

In an enumerated class, each enumerated value cannot have its own unique attribute. You are forced to set the same property for each enumerated value:

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

Only DISPATCHED and DELIVERED require the trackingId attribute, but PREPARING is enforced with a null value.

Seal type

In the case of sealed classes, we can set different properties for each subtype:

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

Here, each subtype has a different attribute. In our example, Preparing does not require properties, so we have the flexibility not to specify any. Dispatched has one attribute and Delivered has two.

Second, the function

The enumeration

Enumerations can have abstract functions as well as regular functions. But like attributes, each enumerated value must have the same function:

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

In this case, we have an abstract function cancelOrder(), which we must override in each enumerated value. This means that we cannot use different functions for different enumerated values.

Usage:

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

Seal type

In sealed classes, we can use different functions for different subtypes:

sealed class DeliveryStatus

class Preparing : DeliveryStatus() {
    fun cancelOrder(a) = println("Cancelled successfully")}class Dispatched : DeliveryStatus() {
    fun rejectDelivery(a) = println("Delivery rejected")}class Delivered : DeliveryStatus() {
    fun returnItem(a) = println("Return initiated")}Copy the code

Here we have different functions: cancelOrder() in Preparing, rejectDelivery() in Dispatched, and returnItem() in Delivered. This makes the intent clearer and makes the code more readable, although we can choose not to use the function if we don’t want to.

Usage:

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

If we want a generic function for all subtypes, as in the enumeration example, we can use it in a sealed class by defining it in the sealed class itself and then overwriting it in the subtype:

sealed class DeliveryStatus {
    abstract fun cancelOrder(a)
}
Copy the code

The advantage of having a universal function for all types is that we don’t have to use the IS operator for type checking. We can simply use polymorphism, as shown in the class of the DeliveryManager enumeration example.

Three, inheritance,

The enumeration

Since enumerated values are objects, they cannot be extended:

class LocallyDispatched : DeliveryStatus.DISPATCHED { } // Error
Copy the code

The enumeration class is final by default, so it cannot be extended by other classes:

class FoodDeliveryStatus : DeliveryStatus() {}// Error
Copy the code

Enumerated classes cannot inherit from other classes, they can only inherit from interfaces:

open class OrderStatus {}interface Cancellable {}enum class DeliveryStatus : OrderStatus() {}// Error
enum class DeliveryStatus : Cancellable { } // OK
Copy the code

Seal type

Since the subtypes of sealed classes are types, they can be inherited:

class LocallyDispatched : Dispatched() {}// OK
Copy the code

The sealed class itself can of course be inherited:

class PaymentReceived : DeliveryStatus(a)// OK
Copy the code

Sealed classes can inherit from other classes as well as interfaces:

open class OrderStatus {}interface Cancellable {}sealed class DeliveryStatus : OrderStatus() {}// OK
sealed class DeliveryStatus : Cancellable { } // OK
Copy the code

Number of instances

The enumeration

Since enumerated values are objects and not types, we cannot create multiple instances of them:

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

In this example, DISPATCHED is an object and not a type, so it only exists as an instance and we cannot create more instances from it:

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

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

Seal type

The subtypes of sealed classes are types, so we can create multiple instances of these types. We can also use the object declaration to make a type have only one instance:

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

In this example, we can create multiple instances for Health and Delivered. Note that we take advantage of the ability of a subtype of a sealed class to be a singleton, a regular class, or a data type. Preparing can have only one object, like enumeration values:

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

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

Also note that in the code above, each instance of Dispatched can have a different value for the trackingId attribute.

Serializable and comparable

The enumeration

Each enumerated class in Kotlin is implicitly inherited from the abstract java.lang.Enum class. Therefore, all enumerations automatically have implementations of equals(), toString(), hashCode(), Serializable, and Comparable. We don’t have to define them.

Seal type

For sealed classes, we need to define them manually or use data class’s automatic equals(), toString() and HashCode () and then implement Serializable and Comparable manually.

Six, performance

The enumeration

Enumerations are not garbage collected, and they remain in memory throughout the life of your application. This could be a plus or a minus.

The garbage collection process is expensive. The same goes for object creation; we don’t want to create the same object over and over again. Therefore, with enumerations, you can save on the cost of garbage collection and object creation. That’s the benefit.

The disadvantage is that enumerations remain in memory even when they are not in use, which keeps memory tied up.

If you have 100 to 200 enums in your application, you don’t need to worry about all of this. However, when you have more, you can decide whether you should use enumerations based on facts, such as how many enumerations there are, whether they will always be used, and the amount of memory allocated to the JVM.

The comparison of the enumerated values in the WHEN expression is faster because, behind the scenes, it uses tableswitch to compare objects.

In Android, Proguard converts enumerations without functions and attributes to integers when optimization is enabled, so you can get type safety at compile time and integer performance at run time!

Seal type

Sealed classes are normal classes, with the exception that they need to be extended in the same package and compilation unit. As a result, their performance is comparable to that of the average class.

Objects of subtypes of sealed classes are garbage collected just like objects of regular classes. Therefore, you must bear the cost of garbage collection and object creation.

When you have low memory limits, if you need thousands of objects, you can consider using sealed classes instead of enumerations. Because the garbage collector can free memory when objects are not in use.

If you inherit sealed classes with the Object declaration, the object will act as a singleton and will not be garbage collected like enumerations.

The comparison of types of sealed classes is slower in the WHEN expression because underneath it is used for instanceof comparison types. In this case, the speed difference between enumeration and sealed classes is small. This is only important if you are comparing thousands of constants in a loop.