Reputation: 829
In my Kotlin project, I have a DefaultError enum
enum class DefaultError {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR
}
I would like to extend them so that I have
enum class NfcAndDefaultError : DefaultError {
//DefaultError inherited plus
NFC_ERROR
}
and another enum
enum class KameraAndDefaultError : DefaultError {
//DefaultError inherited plus
CAM_ERROR
}
Now, I have
enum class NfcDefaultError {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR,
NFC_ERROR
}
and
enum class KameraAndDefaultError {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR,,
CAM_ERROR
}
I bet Kotlin has a nice way there?
Upvotes: 30
Views: 57155
Reputation: 1971
Simple solution with no inheritance, sealed class, or interface
enum class Fruit(val emoji: String) {
APPLE("🍎"),
ORANGE("🍊");
}
enum class ExtendedFruit(emoji: String) {
APPLE(Fruit.APPLE.emoji),
ORANGE(Fruit.ORANGE.emoji),
BANANA("🍌");
companion object {
fun create(fruit: Fruit) = entries.first { it.name == fruit.name }
}
}
Upvotes: 0
Reputation: 577
No easy way as of now.
Go with a separate enum and type it with a marker interface
interface Error // marker interface
enum class DefaultError: Error {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR
}
enum class KameraAndDefaultError: Error {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR,
CAM_ERROR
}
enum class NfcDefaultError: Error {
INTERNET_ERROR,
BLUETOOTH_ERROR,
TEMPERATURE_ERROR,
NFC_ERROR
}
Usage:
val error: Error = KameraAndDefaultError.INTERNET_ERROR
Upvotes: 1
Reputation: 83
You can use sealed interfaces since Kotlin 1.5 to achieve what you want :)
You can find some examples here: https://quickbirdstudios.com/blog/sealed-interfaces-kotlin/
Just ping me if you need some more explanation!
Upvotes: 2
Reputation: 6436
There is more to the reason why enum inheritance is not supported than "inheritance is evil". In fact, a very practical reason:
enum class BaseColor { BLUE, GREEN, RED }
val x: BaseColor = ... // must be one of the 3 enums, right?
// e.g. when {} can be done exhaustively with BLUE, GREEN, RED
enum class DerivedColor : BaseColor { YELLOW }
val y: BaseColor = ... // now, it can also be YELLOW
// here, you lose the guarantee that it's a value in a limited set
// and thus the main advantage of enums
There are multiple options to achieve what you like:
1. Different enums implement a common interface
I would refer you to this answer.
Interfaces are a very flexible solution and allow you to derive an unlimited number of enums. If this is what you need, go for it.
In Kotlin, sealed classes are a generalization of enums, that allows you to retain state in each value. All derived classes of a sealed class must be known up-front and declared in the same file. The advantage compared to interfaces is that you can limit the sealed class to a fixed set of possible types. This allows you to omit the else
branch in when
, for example. The drawback is that it's not possible to add types outside the sealed class
by design.
Semantically, you have an enum of enums: the first level determines which enum class
type is used, and the second level determines which enumerator (constant) inside that enum class
is used.
enum class DefaultError { INTERNET_ERROR, BLUETOOTH_ERROR, TEMPERATURE_ERROR }
enum class NfcError { NFC_ERROR }
enum class CameraError { CAM_ERROR }
sealed class Error {
data class Default(val error: DefaultError) : Error()
data class Nfc(val error: NfcError) : Error()
data class Camera(val error: CameraError) : Error()
}
fun test() {
// Note: you can use Error as the abstract base type
val e: Error = Error.Default(DefaultError.BLUETOOTH_ERROR)
val str: String = when (e) {
is Error.Default -> e.error.toString()
is Error.Nfc -> e.error.toString()
is Error.Camera -> e.error.toString()
// no else!
}
}
Upvotes: 38
Reputation: 122
The simple answer is that you can't extend enums in Kotlin the way you would want to.
I have to agree with Miha_x64's comment stating that inheritance is "evil" and should only be used when it legitimately makes sense (yes, there are still situations when inheritance is the way to go). I believe that instead of actually trying to work around the design of enums in Kotlin, why don't we design our solution differently? I mean: Why do you think you need such an enum hierarchy, to begin with? What's the benefit? Why not simply have some "common errors" instead and specific errors for whichever concrete area needs very specific errors? Why even use enums?
If you are dead set on using enums, then Januson's solution might be your best bet, but do please use meaningful error codes because using "1001", "1002", "233245" is so 1980s and utterly horrible to read and work with. I find "INTERNET_ERROR" and "BLUETOOTH_ERROR", etc. just as cryptic... can we really not do any better and be more specific about what went wrong so that whoever reads the error code CAN actually understand what is wrong without needing to dig through the internet or through some hefty documentation for the next many minutes/hours? (except, of course, if there are some legitimate reasons why the code needs to be as small as possible - e.g. message size limitations, bandwidth limitations, etc.)
In case you are not dead set on using enums, then you could consider the following:
data class ErrorCode(
val code: String,
val localeKey: String,
val defaultMessageTemplate: String
)
val TENANT_ACCESS_FORBIDDEN = ErrorCode(
"TENANT_ACCESS_FORBIDDEN",
"CommonErrorCodes.TENANT_ACCESS_FORBIDDEN",
"Not enough permissions to access tenant ''{0}''."
)
val NO_INTERNET_CONNETION = ErrorCode(
"NO_INTERNET_CONNETION",
"DeviceErrorCodes.NO_INTERNET_CONNETION",
"No internet connection."
)
val NO_BLUETOOTH_CONNECTION = ErrorCode(
"NO_BLUETOOTH_CONNECTION",
"DeviceErrorCodes.NO_BLUETOOTH_CONNECTION",
"No bluetooth connection."
)
val TEMPERATURE_THRESHOLD_EXCEEDED = ErrorCode(
"TEMPERATURE_THRESHOLD_EXCEEDED",
"DeviceErrorCodes.TEMPERATURE_THRESHOLD_EXCEEDED",
"Temperature ''{0}'' exceeds the maximum threshold value of ''{1}''."
)
Because all the above codes, in essence, act as static constants, comparing against them is as easy as comparing enums (e.g.: if (yourException.errorCode == NO_INTERNET_CONNECTION) { // do something }
).
Inheritance is really not needed, what you really need is a clear separation between common and non-common error codes.
Upvotes: 0
Reputation: 4841
You can extend an Enum. Kind of. But not with inheritance. Enums can implement an interface. That means to extend it you would simply add another enum implementing the same interface.
Lets say you have an error. This error has an error code. Default error are implemented as DefaultError enum and can be extended by adding aditional enums implementing the Error interface.
interface Error {
fun code(): Int
}
enum class DefaultError(private val code: Int) : Error {
INTERNET_ERROR(1001),
BLUETOOTH_ERROR(1002),
TEMPERATURE_ERROR(1003);
override fun code(): Int {
return this.code
}
}
enum class NfcError(private val code: Int) : Error {
NFC_ERROR(2001);
override fun code(): Int {
return this.code
}
}
enum class KameraError(private val code: Int) : Error {
CAM_ERROR(3001);
override fun code(): Int {
return this.code
}
}
Upvotes: 32