I want an abstract class declaring abstract val values: List<String> and val totalLen: Int. I'd like to initialize totalLen like this:
abstract class MyClass {
abstract val values: List<String>
val totalLen: Int = values.sumOf { it.length }
}
My editor warns me that I'm Accessing non-final property values in constructor . Is this something I can ignore, or will it come back and bite me.
Using init block yields the same warning.
Putting values in primary constructor removes the warning. I'd like to avoid that so that the code extending the class would prettier.
// prettier
abstract class MyClass {
abstract val values: List<String>
val totalLen = values.sumOf {it.length}
}
class Child : MyClass() {
override val values = listOf("a", "b")
...
}
// uglier
abstract class MyClassOther (val values: List<String>) {
val totalLen = values.sumOf {it.length}
}
class ChildOther : MyClassOther(listOf("a", "b")) {
...
}
Here the aesthetic difference isn't big, but if i had more abstact and non abstract properties it would result in crowded call to the super-class constructor in extending class.
Also, using sealed class for parrent doesn't help to remove IDE warning.
EDIT: found out about lazy delegate property. It solves this, but I'd still like to know if there is another solution without using delegates.
The issue is a subtle one, but it can have serious consequences down the line.
Object construction works on the general principle that a superclass should be fully initialised before the subclass is initialised. (That includes any code in the constructor itself, along with any property initialisers and init blocks.)
This means that if you call anything overridable from a constructor, initialisers, or init blocks, it will run before the class is fully initialised, and before anything in any subclasses is initialised. Properties might not have their initial values set, invariants might not be established, and you can't rely on anything working.
The only safe course is never to call anything overridable from a constructor or initialiser. That's what your IDE is warning you about.
(In practice, if you control all the code, you may be able to get away with calling something that has no dependency on any subclass's state, nor on anything in the superclass that's not yet set up. But that's fragile, and can break if someone adds some innocent-looking code in a subclass.)
Have you tried to run your ‘prettier’ case? That demonstrates the problem very neatly, by throwing a NullPointerException when you try to construct a Child! Here's the order in which things would happen:
External code calls the Child() constructor.
That calls the MyClass constructor.
That initialises totalLen.
That gets the value of values. ← NullPointerException
Having completed the MyClass construction, it continues with the Child() constructor.
That initialises values.
I hope you can see the problem: when the MyClass initialiser runs, values is not yet initialised. At that point, its value is undefined; on the JVM, it'll always be null (or 0, or false, depending on the type) — on other platforms, it might be something random or impossible.
Your ‘uglier’ case works around that well: it creates the list before calling the superclass constructor, so you know it's safe to use. But, as you say, it would be pretty unclear if there were multiple properties and/or multiple levels of inheritance along the way.
Unfortunately, I don't know of any general workarounds for this.
As you say, making superclass initialisers lazy is one approach, but it doesn't fit every case.
Spring has a #PostConstruct annotation which lets you specify code to be run after the class (and any subclasses) have all be initialised (including any Spring autowiring and other magic), which can be a good solution if you're using Spring. Other frameworks might provide something similar.
You could do something similar in an ad-hoc way, if you know your lowest-level class(es) will be final: they could call a construction-completed superclass method at the end of their constructors. But of course that's not a general solution.
Ultimately, giving superclasses dependencies on their subclasses (instead of the other way around) seems like a bit of a code smell, and this is only one of the problems it can cause. You might look at your code and see whether inheritance is really needed, or whether composition might be a better approach.
Related
enum class Admin(myName:String, val id:Int, val age:Int){
ROOT_ADMIN ("Pete", 1, 55),
ACADEMIC_ADMIN("Jacob",11,56),
DEPARTMENT_ADMIN("Robin",111,50),
CLASS_ADMIN("Chris",1111,22)
To access the properties of objects of enum class Admin, when I type
Admin.CLASS_ADMIN.____
Naturally, myName to come out in the IDE auto-complete is expected. But its not happening. But id and age does come as they have val keyword associated with them.
But when I add var in front of myName, like:
enum class Admin(var myName:String, val id:Int, val age:Int)
I am now getting myName in auto-complete.
What is the importance of var keyword here?
Note: I am aware of the fact that when we declare variables with var or val keywords in constructor, it declares a property inside that class.
But how this logic relates to this situation?
This is more about Kotlin properties and less about how val/var work with enums. In fact for most of this answer, we can completely ignore the fact that we're even talking about enums, as opposed to any other Kotlin class (but I do have a note at the end on this).
For background, when you create an instance of a class in Kotlin and provide arguments to its constructor, if those arguments have var or val, Kotlin will treat them as properties. If not, it treats them as an argument to the constructor (these can be used in init blocks, for example but do not get turned into properties).
That's what is happening in your case. Kotlin treats myName as a constructor argument and effectively throws it away as you aren't using it. It does not get turned into a property. For id and age, you've specified they are val, so Kotlin turns them into read-only properties.
As for var, when Kotlin sees this it makes them into a read/write property (they can change).
Basically: Kotlin turned id and age into read-only properties and myName was defined as a constructor argument. This is why autocomplete did not offer you myName, it wasn't a property.
Some general advice: I would absolutely not declare any mutable properties on an enum (so, use val only for read-only properties). By using var, you'll get mutable read/write properties. Normally that's fine but with enum specifically there is an expectation that they do not change, ever. You are declaring a fixed set of values (an enumeration of them!) whose internal properties do not change. As a developer if I saw an enum whose internal state was mutable, it would immediately seem wrong.
Since item of enum class is acting like object in Kotlin (just for understanding), if you declare property as var of enum class, you could change the property value and it affects everywhere. This might be hard to understand. You can see below example code.
enum class Test(var a: String) {
A("a"),
B("b");
}
fun main()
{
println(Test.A.a) // a
Test.A.a = "b"
println(Test.A.a) // b
}
Usually, you might not want to declare a property as mutable for the design.
I've written myself into a corner where I want an instance of Class<Foo<Bar>>. While there's no apparent reason that this shouldn't be valid, there seems to be no way to create one. Foo<Bar>::class.java is a syntax error, and Kotlin does not provide a public constructor for Class.
The code I'm writing is an abstraction layer over gson. Below is an overly-simplified example:
class Boxed<T : Any> (val value: T)
class BaseParser<U : Any> (
private val clazz: Class<U>
) {
//This works for 98% of cases
open fun parse(s: String): U {
return gson.fromJson(s, clazz)
}
//Presume that clazz is required for other omitted functions
}
//Typical subclass:
class FooParser : BaseParser<Foo>(Foo::class.java)
// Edge Case
class BarParser : BaseParser<Boxed<Bar>>(Boxed<Bar>::class.java) {
override fun parse(s: String): Boxed<Bar> {
return Boxed(gson.fromJson(s, Bar::class.java))
}
}
// not valid: "Only classes are allowed on the left hand side of a class literal"
In my production code, there are already dozens of subclasses that inherit from the base class, and many that override the "parse" function Ideally I'd like a solution that doesn't require refactoring the existing subclasses.
Actually, there is a reason this is impossible. Class (or Kotlin's KClass) can't hold parameterized types. They can hold e.g. List, but they can't List<String>. To store Foo<Bar> you need Type (or Kotlin's KType) and specifically ParameterizedType. These classes are somewhat more complicated to use and harder to acquire than simple Class.
The easiest way to acquire Type in Kotlin is by using its typeOf() utility:
typeOf<Foo<Bar>>().javaType
Gson supports both Class and Type, so you should be able to use it instead.
The closest you'll get is Boxed::class.java. This is not a language restriction but a JVM restriction. JVM has type erasure, so no generic types exist after compilation (thats also one of the reasons generics cant be primitives, as they need to be reference types to behave).
Does it work with the raw Boxed type class?
For this case, it looks like
BaseParser<Boxed<Bar>>(Boxed::class.java as Class<Boxed<Bar>>)
could work (that is, it will both type-check and succeed at runtime). But it depends on what exactly happens in the "Presume that clazz is required for other omitted functions" part. And obviously it doesn't allow actually distinguishing Boxed<Foo> and Boxed<Bar> classes.
I'd also consider broot's approach if possible, maybe by making BaseParser and new
class TypeBaseParser<U : Any>(private val tpe: Type)
extend a common abstract class/interface.
I learned to program with Kotlin a few months ago, and I wrote a bunch of code with it.
Recently, as I was working on a personal project, I tried something like that :
1 | sealed class Base(derivedRef: Any) {
2 |
3 | init {
4 | println("$derivedRef shouldn't be null !")
5 | }
6 |
7 | object Derived: Base(Derived)
8 | }
9 |
10| fun main() {
11| val neededToInitializeDerived = Base.Derived
12| }
At compile time, no problem: all seems to work as it should, and my IDE (Idea Intellij) doesn't highlight any part of my code in red. But, once compiled, if I try to run it, it prints a weird result :
null shouldn't be null !
After thinking a bit, it appears that derivedRef, typed as Any - and also not-null, thanks to the null safety - is, actually, null.
My theory is that when I'm passing the Derived object itself as a parameter of its own superclass constructor (and in fact the singleton instance of the Derived class, once compiled, through Derived.INSTANCE), Derived.INSTANCE isn't instantiated since the class Derived itself and its superclass Base aren't. It takes also a provisory null value, normally hidden at compile time and no more available at runtime, but that I successfully capture with this specific code snippet.
The problem is that I now have a null value instead of the normal Any one, throwing a NullPointerException as soon as I call a method needing a non-null Any value with it; I have some fun while doing impossible things using this glitch, but I think that it can be dangerous since it won't crash at runtime, letting you run your code free until the said method is called and making your program throw an unexpected error where the compiler ensures you "Don't worry, all is fine right there".
After proceeding to some tests, the bug has further consequences : I'm now able to put null values inside a MutableList<Any> :
val theForbiddenValue: Any // Let's say it's actually null
val myList = mutableListOf<Any>()
myList.add(null) // Normal, won't compile
myList.add(theForbiddenValue) // Compiles ! If I print it, I obtain "[null]" !
And to do other weird things that shouldn't happen at all, such as defining functions supposed to return Any but do not, etc. My last thought - I promise -, by making Base implements List<Any> for example, Derived will also implement it. And thus you can change the type of derivedRef from Any to List<Any>, and also obtain a null value where List<Any> is expected. This also works with every interface and/or non-final class.
So my question is, is it a real bug that I just discovered ? Or is it already known by many developers ? Or is it normal despite the appearances (I will be really surprised in that case) ??
This is about the order of construction — though it's much more subtle than most.
The most common order-of-construction issue is that in general, a constructor shouldn't refer to anything in the class that could be overridden (or changed) by a subclass.
In Java and Kotlin, a constructor always calls the superclass constructor as the very first thing it does. (If you don't write an explicit call, the compiler inserts one for you.) After that returns, it runs any field initialisers, and then the rest of the constructor. So when the superclass constructor runs, none of the subclass initialisers or constructor code have been run yet. This means that non-primitive fields will be null at that point — even if they're of a non-nullable type.
(I'm guessing that this isn't made a compile-time error because there might be circumstances in which it's perfectly fine: for example, if a subclass overrides a superclass method but doesn't refer to any fields which are overridden or set in the subclass constructor. Note that the IDE shows a warning, such as "Accessing non-final property […] in constructor" or "Calling non-final function […] in constructor".)
What's happening in this case, however, is much less obvious, because of the combination of a sealed class and an object subclass. As far as I can tell, the order of events is:
Because main() refers to Derived, it will construct the Derived object.
The first thing that Derived's constructor does is to call the superclass constructor, passing a reference to itself. However, because the object hasn't yet been constructed, its reference seems to be null. ← This is of course the cause of the bug, and ideally would give a compile-time error, or at least a warning.
The superclass constructor runs, and prints out the message you see.
The rest of the subclass constructor runs (and does nothing). By this point, Derived gives a valid references, but by then it's too late.
The rest of the main() method runs, setting its local variable.
Here's a variation which illustrates the order a little better. (I've moved the object outside the sealed class, though that makes no practical difference. I've also made derivedRef a property, so we can see it afterward.)
sealed class Base(val derivedRef: Any) {
init {
println("Base.init: Derived = $Derived, derivedRef = $derivedRef")
}
}
object Derived: Base(Derived) {
init {
println("Derived.init: Derived = $Derived, derivedRef = $derivedRef")
}
}
fun main() {
println("main: Derived = $Derived, derivedRef = ${Derived.derivedRef}")
}
This prints something like:
Base.init: Derived = null, derivedRef = null
Derived.init: Derived = Derived#87aac27, derivedRef = null
main: Derived = Derived#87aac27, derivedRef = null
You can see that the Derived reference is valid once the superclass constructor has finished, even before its own constructor has finished; but of course that's too late for the property, which has already been set to null.
By the way, this doesn't happen if the object is made into a normal class: it would have to pass this when it calls the superclass constructor, but the compiler complains that "'this' is not defined in this context".
Alternatively, if the sealed class is made into a plain open class, then it compiles OK but gives a NullPointerException at runtime.
So it's the combination of sealed class and object subclass that leads to this particular issue. Presumably the compiler can't spot that the object reference isn't valid yet (like it does for an explicit this in the case of a simple subclass).
I'm a beginner in Kotlin and recently read about Sealed Classes. But from the doc the only think I actually get is that they are exist.
The doc stated, that they are "representing restricted class hierarchies". Besides that I found a statement that they are enums with superpower. Both aspects are actually not clear.
So can you help me with the following questions:
What are sealed classes and what is the idiomatic way of using ones?
Does such a concept present in other languages like Python, Groovy or C#?
UPDATE:
I carefully checked this blog post and still can't wrap my head around that concept. As stated in the post
Benefit
The feature allows us to define class hierarchies that are
restricted in their types, i.e. subclasses. Since all subclasses need
to be defined inside the file of the sealed class, there’s no chance
of unknown subclasses which the compiler doesn’t know about.
Why the compiler doesn't know about other subclasses defined in other files? Even IDE knows that. Just press Ctrl+Alt+B in IDEA on, for instance, List<> definition and all implementations will be shown even in other source files. If a subclass can be defined in some third-party framework, which not used in the application, why should we care about that?
Say you have a domain (your pets) where you know there is a definite enumeration (count) of types. For example, you have two and only two pets (which you will model with a class called MyPet). Meowsi is your cat and Fido is your dog.
Compare the following two implementations of that contrived example:
sealed class MyPet
class Meowsi : MyPet()
class Fido : MyPet()
Because you have used sealed classes, when you need to perform an action depending on the type of pet, then the possibilities of MyPet are exhausted in two and you can ascertain that the MyPet instance will be exactly one of the two options:
fun feed(myPet: MyPet): String {
return when(myPet) {
is Meowsi -> "Giving cat food to Meowsi!"
is Fido -> "Giving dog biscuit to Fido!"
}
}
If you don't use sealed classes, the possibilities are not exhausted in two and you need to include an else statement:
open class MyPet
class Meowsi : MyPet()
class Fido : MyPet()
fun feed(myPet: MyPet): String {
return when(myPet) {
is Mewosi -> "Giving cat food to Meowsi!"
is Fido -> "Giving dog biscuit to Fido!"
else -> "Giving food to someone else!" //else statement required or compiler error here
}
}
In other words, without sealed classes there is not exhaustion (complete coverage) of possibility.
Note that you could achieve exhaustion of possiblity with Java enum however these are not fully-fledged classes. For example, enum cannot be subclasses of another class, only implement an interface (thanks EpicPandaForce).
What is the use case for complete exhaustion of possibilities? To give an analogy, imagine you are on a tight budget and your feed is very precious and you want to ensure you don't end up feeding extra pets that are not part of your household.
Without the sealed class, someone else in your home/application could define a new MyPet:
class TweetiePie : MyPet() //a bird
And this unwanted pet would be fed by your feed method as it is included in the else statement:
else -> "Giving food to someone else!" //feeds any other subclass of MyPet including TweetiePie!
Likewise, in your program exhaustion of possibility is desirable because it reduces the number of states your application can be in and reduces the possibility of bugs occurring where you have a possible state where behaviour is poorly defined.
Hence the need for sealed classes.
Mandatory else
Note that you only get the mandatory else statement if when is used as an expression. As per the docs:
If [when] is used as an expression, the value of the satisfied branch becomes the value of the overall expression [... and] the else branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions
This means you won't get the benefit of sealed classes for something like this):
fun feed(myPet: MyPet): Unit {
when(myPet) {
is Meowsi -> println("Giving cat food to Meowsi!") // not an expression so we can forget about Fido
}
}
To get exhaustion for this scenario, you would need to turn the statement into an expression with return type.
Some have suggested an extension function like this would help:
val <T> T.exhaustive: T
get() = this
Then you can do:
fun feed(myPet: MyPet): Unit {
when(myPet) {
is Meowsi -> println("Giving cat food to Meowsi!")
}.exhaustive // compiler error because we forgot about Fido
}
Others have suggested that an extension function pollutes the namespace and other workarounds (like compiler plugins) are required.
See here for more about this problem.
Sealed classes are easier to understand when you understand the kinds of problems they aim to solve. First I'll explain the problems, then I'll introduce the class hierarchies and the restricted class hierarchies step by step.
We'll take a simple example of an online delivery service where we use three possible states Preparing, Dispatched and Delivered to display the current status of an online order.
Problems
Tagged class
Here we use a single class for all the states. Enums are used as type markers. They are used for tagging the states Preparing, Dispatched and Delivered :
class DeliveryStatus(
val type: Type,
val trackingId: String? = null,
val receiversName: String? = null) {
enum class Type { PREPARING, DISPATCHED, DELIVERED }
}
The following function checks the state of the currently passed object with the help of enums and displays the respective status:
fun displayStatus(state: DeliveryStatus) = when (state.type) {
PREPARING -> print("Preparing for dispatch")
DISPATCHED -> print("Dispatched. Tracking ID: ${state.trackingId ?: "unavailable"}")
DELIVERED -> print("Delivered. Receiver's name: ${state.receiversName ?: "unavailable"}")
}
As you can see, we are able to display the different states properly. We also get to use exhaustive when expression, thanks to enums. But there are various problems with this pattern:
Multiple responsibilities
The class DeliveryStatus has multiple responsibilities of representing different states. So it can grow bigger, if we add more functions and properties for different states.
More properties than needed
An object has more properties than it actually needs in a particular state. For example, in the function above, we don't need any property for representing the Preparing state. The trackingId property is used only for the Dispatched state and the receiversName property is concerned only with the Delivered state. The same is true for functions. I haven't shown functions associated with states to keep the example small.
No guarantee of consistency
Since these unused properties can be set from unrelated states, it's hard to guarantee the consistency of a particular state. For example, one can set the receiversName property on the Preparing state. In that case, the Preparing will be an illegal state, because we can't have a receiver's name for the shipment that hasn't been delivered yet.
Need to handle null values
Since not all properties are used for all states, we have to keep the properties nullable. This means we also need to check for the nullability. In the displayStatus() function we check the nullability using the ?:(elvis) operator and show unavailable, if that property is null. This complicates our code and reduces readability. Also, due to the possibility of a nullable value, the guarantee for consistency is reduced further, because the null value of receiversName in Delivered is an illegal state.
Introducing Class Hierarchies
Unrestricted class hierarchy: abstract class
Instead of managing all the states in a single class, we separate the states in different classes. We create a class hierarchy from an abstract class so that we can use polymorphism in our displayStatus() function:
abstract class DeliveryStatus
object Preparing : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val receiversName: String) : DeliveryStatus()
The trackingId is now only associated with the Dispatched state and receiversName is only associated with the Delivered state. This solves the problems of multiple responsibilities, unused properties, lack of state consistency and null values.
Our displayStatus() function now looks like the following:
fun displayStatus(state: DeliveryStatus) = when (state) {
is Preparing -> print("Preparing for dispatch")
is Dispatched -> print("Dispatched. Tracking ID: ${state.trackingId}")
is Delivered -> print("Delivered. Received by ${state.receiversName}")
else -> throw IllegalStateException("Unexpected state passed to the function.")
}
Since we got rid of null values, we can be sure that our properties will always have some values. So now we don't need to check for null values using the ?:(elvis) operator. This improves code readability.
So we solved all the problems mentioned in the tagged class section by introducing a class hierarchy. But the unrestricted class hierarchies have the following shortcomings:
Unrestricted Polymorphism
By unrestricted polymorphism I mean that our function displayStatus() can be passed a value of unlimited number of subclasses of the DeliveryStatus. This means we have to take care of the unexpected states in displayStatus(). For this, we throw an exception.
Need for the else branch
Due to unrestricted polymorphism, we need an else branch to decide what to do when an unexpected state is passed. If we use some default state instead of throwing an exception and then forget to take care of any newly added subclass, then that default state will be displayed instead of the state of the newly created subclass.
No exhaustive when expression
Since the subclasses of an abstract class can exist in different packages and compilation units, the compiler doesn't know all the possible subclasses of the abstract class. So it won't flag an error at compile time, if we forget to take care of any newly created subclasses in the when expression. In that case, only throwing an exception can help us. Unfortunately, we'll know about the newly created state only after the program crashes at runtime.
Sealed Classes to the Rescue
Restricted class hierarchy: sealed class
Using the sealed modifier on a class does two things:
It makes that class an abstract class. Since Kotlin 1.5, you can use a sealed interface too.
It makes it impossible to extend that class outside of that file. Since Kotlin 1.5 the same file restriction has been removed. Now the class can be extended in other files too but they need to be in the same compilation unit and in the same package as the sealed type.
sealed class DeliveryStatus
object Preparing : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val receiversName: String) : DeliveryStatus()
Our displayStatus() function now looks cleaner:
fun displayStatus(state: DeliveryStatus) = when (state) {
is Preparing -> print("Preparing for Dispatch")
is Dispatched -> print("Dispatched. Tracking ID: ${state.trackingId}")
is Delivered -> print("Delivered. Received by ${state.receiversName}")
}
Sealed classes offer the following advantages:
Restricted Polymorphism
By passing an object of a sealed class to a function, you are also sealing that function, in a sense. For example, now our displayStatus() function is sealed to the limited forms of the state object, that is, it will either take Preparing, Dispatched or Delivered. Earlier it was able to take any subclass of DeliveryStatus. The sealed modifier has put a limit on polymorphism. As a result, we don't need to throw an exception from the displayStatus() function.
No need for the else branch
Due to restricted polymorphism, we don't need to worry about other possible subclasses of DeliveryStatus and throw an exception when our function receives an unexpected type. As a result, we don't need an else branch in the when expression.
Exhaustive when expression
Just like all the possible values of an enum class are contained inside the same class, all the possible subtypes of a sealed class are contained inside the same package and the same compilation unit. So, the compiler knows all the possible subclasses of this sealed class. This helps the compiler to make sure that we have covered(exhausted) all the possible subtypes in the when expression. And when we add a new subclass and forget to cover it in the when expression, it flags an error at compile time.
Note that in the latest Kotlin versions, your when is exhaustive for the when expressions as well the when statements.
Why in the same file?
The same file restriction has been removed since Kotlin 1.5. Now you can define the subclasses of the sealed class in different files but the files need to be in the same package and the same compilation unit. Before 1.5, the reason that all the subclasses of a sealed class needed to be in the same file was that it had to be compiled together with all of its subclasses for it to have a closed set of types. If the subclasses were allowed in other files, the build tools like Gradle would have to keep track of the relations of files and this would affect the performance of incremental compilation.
IDE feature: Add remaining branches
When you just type when (status) { } and press Alt + Enter, Enter, the IDE automatically generates all the possible branches for you like the following:
when (state) {
is Preparing -> TODO()
is Dispatched -> TODO()
is Delivered -> TODO()
}
In our small example there are just three branches but in a real project you could have hundreds of branches. So you save the effort of manually looking up which subclasses you have defined in different files and writing them in the when expression one by one in another file. Just use this IDE feature. Only the sealed modifier enables this.
That's it! Hope this helps you understand the essence of sealed classes.
If you've ever used an enum with an abstract method just so that you could do something like this:
public enum ResultTypes implements ResultServiceHolder {
RESULT_TYPE_ONE {
#Override
public ResultOneService getService() {
return serviceInitializer.getResultOneService();
}
},
RESULT_TYPE_TWO {
#Override
public ResultTwoService getService() {
return serviceInitializer.getResultTwoService();
}
},
RESULT_TYPE_THREE {
#Override
public ResultThreeService getService() {
return serviceInitializer.getResultThreeService();
}
};
When in reality what you wanted is this:
val service = when(resultType) {
RESULT_TYPE_ONE -> resultOneService,
RESULT_TYPE_TWO -> resultTwoService,
RESULT_TYPE_THREE -> resultThreeService
}
And you only made it an enum abstract method to receive compile time guarantee that you always handle this assignment in case a new enum type is added; then you'll love sealed classes because sealed classes used in assignments like that when statement receive a "when should be exhaustive" compilation error which forces you to handle all cases instead of accidentally only some of them.
So now you cannot end up with something like:
switch(...) {
case ...:
...
default:
throw new IllegalArgumentException("Unknown type: " + enum.name());
}
Also, enums cannot extend classes, only interfaces; while sealed classes can inherit fields from a base class. You can also create multiple instances of them (and you can technically use object if you need the subclass of the sealed class to be a singleton).
I was going through Kotlin reference document and then I saw this.
The class declaration consists of the class name, the class header
(specifying its type parameters, the primary constructor etc.) and the
class body, surrounded by curly braces. Both the header and the body
are optional; if the class has no body, curly braces can be omitted.
class Empty
Now I'm wondering what is the use of such class declaration without header and body
Empty classes can be useful to represent state along with other classes, especially when part of a sealed class. Eg.
sealed class MyState {
class Empty : MyState()
class Loading : MyState()
data class Content(content: String) : MyState()
data class Error(error: Throwable) : MyState()
}
In this way you can think of them like java enum entries with more flexibility.
tldr: they want to demonstrate it's possible
even an empty class is of type Any and therefore has certain methods automatically. I think in most cases, this does not make sense, but in the documentation case it's used to show the simplest possible definition of a class.
The Java equivalent would be:
public final class Empty {
}
From practical programmer day to day perspective empty class makes no much sense indeed. There are however cases where this behavior is desirable.
There are scenarios where we want to make sure that we want to define a class and at the same time, we want to make sure that instance of this class will never be created (type created from such class is called empty type or uninhabited type).
Perfect example of this is Kotlin Nothing class with do not have class declaration header and body (notice that it also have private constructor)
https://github.com/JetBrains/kotlin/blob/master/core/builtins/native/kotlin/Nothing.kt
There are few usages for Nothing in Kotlin language. One of them would be a function that does not return a value (do not confuse this with Unit where the function returns actually returns a value of type Unit). A typical example is an assertFail method used for testing or method that exits current process. Both methods will never actually return any value yet we need to explicitly say tell it to a compiler using special type (Nothing).
fun assertFail():Nothing {
throw Exception()
}
Nothing can be also used with start projections where type Function<*, String> can be in-projected to Function<in Nothing, String>
Another usage for empty class is type token or placeholder:
class DatabaseColumnName
class DatabaseTableName
addItem(DatabaseColumnName.javaClass, "Age")
addItem(DatabaseTableName.javaClass, "Person")
...
getItemsByType(DatabaseTableName.javaClass)
Some languages are using empty classes for metaprogramming although I haven't explored this part personally:
Advantages of an empty class in C++
An example of empty class usage from Spring Boot framework:
#SpringBootApplication
class FooApplication
fun main(args: Array<String>) {
runApplication<FooApplication>(*args)
}
It doesn't make much sense as a final result. However it can be useful in active development and at a design time as a placeholder of some sort, which may be expanded in the future. Such terse syntax allows you to quickly define such new types as needed. Something like:
class Person (
val FirstName: String,
val LastName: String,
// TODO
val Address: Address
)
class Address
I think main reason this is specifically mentioned in documentation is to demonstrate, that language syntax in general can be terse, not that it is specifically created for common usage.
Sealed classes, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state.
reference