Intermediate: Open and special classes
In this chapter, you'll learn about open classes, how they work with interfaces, and other special types of classes available in Kotlin.
Open classes
If you can't use interfaces or abstract classes, you can explicitly make a class inheritable by declaring it as open. To do this, use the open keyword before your class declaration:
To create a class that inherits from another, add a colon after your class header followed by a call to the constructor of the parent class that you want to inherit from. In this example, the Car class inherits from the Vehicle class:
Just like when creating a normal class instance, if your class inherits from a parent class, then it must initialize all the parameters declared in the parent class header. So in the example, the car instance of the Car class initializes the parent class parameters: make and model.
Overriding inherited behavior
If you want to inherit from a class but change some of the behavior, you can override the inherited behavior.
By default, it's not possible to override a member function or property of a parent class. Just like with abstract classes, you need to add special keywords.
Member functions
To allow a function in the parent class to be overridden, use the open keyword before its declaration in the parent class:
To override an inherited member function, use the override keyword before the function declaration in the child class:
For example:
This example:
Creates two instances of the
Carclass that inherit from theVehicleclass:car1andcar2.Overrides the
displayInfo()function in theCarclass to also print the number of doors.Calls the overridden
displayInfo()function oncar1andcar2instances.
Properties
In Kotlin, it's not common practice to make a property inheritable by using the open keyword and overriding it later. Most of the time, you use an abstract class or an interface where properties are inheritable by default.
Properties inside open classes are accessible by their child class. In general, it's better to access them directly rather than override them with a new property.
For example, let's say that you have a property called transmissionType that you want to override later. The syntax for overriding properties is exactly the same as for overriding member functions. You can do this:
However, this is not good practice. Instead, you can add the property to the constructor of your inheritable class and declare its value when you create the Car child class:
Accessing properties directly, instead of overriding them, leads to simpler and more readable code. By declaring properties once in the parent class and passing their values through the constructor, you eliminate the need for unnecessary overrides in child classes.
For more information about class inheritance and overriding class behavior, see Inheritance.
Open classes and interfaces
You can create a class that inherits a class and implements multiple interfaces. In this case, you must declare the parent class first, after the colon, before listing the interfaces:
Special classes
In addition to abstract, open, and data classes, Kotlin has special types of classes designed for various purposes, such as restricting specific behavior or reducing the performance impact of creating small objects.
Sealed classes
There may be times when you want to restrict inheritance. You can do this with sealed classes. Sealed classes are a special type of abstract class. Once you declare that a class is sealed, you can only create child classes from it within the same package. It's not possible to inherit from the sealed class outside of this scope.
To create a sealed class, use the sealed keyword:
Sealed classes are particularly useful when combined with a when expression. By using a when expression, you can define the behavior for all possible child classes. For example:
In the example:
There is a sealed class called
Mammalthat has thenameparameter in the constructor.The
Catclass inherits from theMammalsealed class and uses thecatNameparameter in its own constructor as thenameparameter from theMammalclass.The
Humanclass inherits from theMammalsealed class and uses thehumanNameparameter in its own constructor as thenameparameter from theMammalclass. It also has thejobparameter in its constructor.The
greetMammal()function accepts an argument ofMammaltype and returns a string.Within the
greetMammal()function body, there's awhenexpression that uses theisoperator to check the type ofmammaland decide which action to perform.The
main()function calls thegreetMammal()function with an instance of theCatclass andnameparameter calledSnowy.
For more information about sealed classes and their recommended use cases, see Sealed classes and interfaces.
Enum classes
Enum classes are useful when you want to represent a finite set of distinct values in a class. An enum class contains enum constants, which are themselves instances of the enum class.
To create an enum class, use the enum keyword:
Let's say that you want to create an enum class that contains the different states of a process. Each enum constant must be separated by a comma ,:
The State enum class has enum constants: IDLE, RUNNING, and FINISHED. To access an enum constant, use the class name followed by a . and the name of the enum constant:
You can use this enum class with a when expression to define the action to take depending on the value of the enum constant:
Enum classes can have properties and member functions just like normal classes.
For example, let's say you're working with HTML and you want to create an enum class containing some colors. You want each color to have a property, let's call it rgb, that contains their RGB value as a hexadecimal. When creating the enum constants, you must initialize it with this property:
To add a member function to this class, separate it from the enum constants with a semicolon ;:
In this example, the containsRed() member function accesses the value of the enum constant's rgb property using the this keyword and checks if the hexadecimal value contains FF as its first bits to return a boolean value.
For more information, see Enum classes.
Inline value classes
Sometimes in your code, you may want to create small objects from classes and use them only briefly. This approach can have a performance impact. Inline value classes are a special type of class that avoids this performance impact. However, they can only contain values.
To create an inline value class, use the value keyword and the @JvmInline annotation:
An inline value class must have a single property initialized in the class header.
Let's say that you want to create a class that collects an email address:
In the example:
Emailis an inline value class that has one property in the class header:address.The
sendEmail()function accepts objects with typeEmailand prints a string to the standard output.The
main()function:Creates an instance of the
Emailclass calledmyEmail.Calls the
sendEmail()function on themyEmailobject.
By using an inline value class, you make the class inlined and can use it directly in your code without creating an object. This can significantly reduce memory footprint and improve your code's runtime performance.
For more information about inline value classes, see Inline value classes.
Practice
Exercise 1
You manage a delivery service and need a way to track the status of packages. Create a sealed class called DeliveryStatus, containing data classes to represent the following statuses: Pending, InTransit, Delivered, Canceled. Complete the DeliveryStatus class declaration so that the code in the main() function runs successfully:
Exercise 2
In your program, you want to be able to handle different statuses and types of errors. You have a sealed class to capture the different statuses which are declared in data classes or objects. Complete the code below by creating an enum class called Problem that represents the different problem types: NETWORK, TIMEOUT, and UNKNOWN.