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
Car
class that inherit from theVehicle
class:car1
andcar2
.Overrides the
displayInfo()
function in theCar
class to also print the number of doors.Calls the overridden
displayInfo()
function oncar1
andcar2
instances.
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
Mammal
that has thename
parameter in the constructor.The
Cat
class inherits from theMammal
sealed class and uses thename
parameter from theMammal
class as thecatName
parameter in its own constructor.The
Human
class inherits from theMammal
sealed class and uses thename
parameter from theMammal
class as thehumanName
parameter in its own constructor. It also has thejob
parameter in its constructor.The
greetMammal()
function accepts an argument ofMammal
type and returns a string.Within the
greetMammal()
function body, there's awhen
expression that uses theis
operator to check the type ofmammal
and decide which action to perform.The
main()
function calls thegreetMammal()
function with an instance of theCat
class andname
parameter 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:
Email
is an inline value class that has one property in the class header:address
.The
sendEmail()
function accepts objects with typeEmail
and prints a string to the standard output.The
main()
function:Creates an instance of the
Email
class calledemail
.Calls the
sendEmail()
function on theemail
object.
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
.