Intermediate: Classes and interfaces
In the beginner tour, you learned how to use classes and data classes to store data and maintain a collection of characteristics that can be shared in your code. Eventually, you will want to create a hierarchy to efficiently share code within your projects. This chapter explains the options Kotlin provides for sharing code and how they can make your code safer and easier to maintain.
Class inheritance
In a previous chapter, we covered how you can use extension functions to extend classes without modifying the original source code. But what if you are working on something complex where sharing code between classes would be useful? In such cases, you can use class inheritance.
By default, classes in Kotlin can't be inherited. Kotlin is designed this way to prevent unintended inheritance and make your classes easier to maintain.
Kotlin classes only support single inheritance, meaning it is only possible to inherit from one class at a time. This class is called the parent.
The parent of a class inherits from another class (the grandparent), forming a hierarchy. At the top of Kotlin's class hierarchy is the common parent class: Any
. All classes ultimately inherit from the Any
class:

The Any
class provides the toString()
function as a member function automatically. Therefore, you can use this inherited function in any of your classes. For example:
If you want to use inheritance to share some code between classes, first consider using abstract classes.
Abstract classes
Abstract classes can be inherited by default. The purpose of abstract classes is to provide members that other classes inherit or implement. As a result, they have a constructor, but you can't create instances from them. Within the child class, you define the behavior of the parent's properties and functions with the override
keyword. In this way, you can say that the child class "overrides" the members of the parent class.
Abstract classes can contain both functions and properties with implementation as well as functions and properties without implementation, known as abstract functions and properties.
To create an abstract class, use the abstract
keyword:
To declare a function or a property without an implementation, you also use the abstract
keyword:
For example, let's say that you want to create an abstract class called Product
that you can create child classes from to define different product categories:
In the abstract class:
The constructor has two parameters for the product's
name
andprice
.There is an abstract property that contains the product category as a string.
There is a function that prints information about the product.
Let's create a child class for electronics. Before you define an implementation for the category
property in the child class, you must use the override
keyword:
The Electronic
class:
Inherits from the
Product
abstract class.Has an additional parameter in the constructor:
warranty
, which is specific to electronics.Overrides the
category
property to contain the string"Electronic"
.
Now, you can use these classes like this:
Although abstract classes are great for sharing code in this way, they are restricted because classes in Kotlin only support single inheritance. If you need to inherit from multiple sources, consider using interfaces.
Interfaces
Interfaces are similar to classes, but they have some differences:
You can't create an instance of an interface. They don't have a constructor or header.
Their functions and properties are implicitly inheritable by default. In Kotlin, we say that they are "open".
You don't need to mark their functions as
abstract
if you don't give them an implementation.
Similar to abstract classes, you use interfaces to define a set of functions and properties that classes can inherit and implement later. This approach helps you focus on the abstraction described by the interface, rather than the specific implementation details. Using interfaces makes your code:
More modular, as it isolates different parts, allowing them to evolve independently.
Easier to understand by grouping related functions into a cohesive set.
Easier to test, as you can quickly swap an implementation with a mock for testing.
To declare an interface, use the interface
keyword:
Interface implementation
Interfaces support multiple inheritance so a class can implement multiple interfaces at once. First, let's consider the scenario where a class implements one interface.
To create a class that implements an interface, add a colon after your class header, followed by the interface name that you want to implement. You don't use parentheses ()
after the interface name because interfaces don't have a constructor:
For example:
In the example:
PaymentMethod
is an interface that has aninitiatePayment()
function without an implementation.CreditCardPayment
is a class that implements thePaymentMethod
interface.The
CreditCardPayment
class overrides the inheritedinitiatePayment()
function.paymentMethod
is an instance of theCreditCardPayment
class.The overridden
initiatePayment()
function is called on thepaymentMethod
instance with a parameter of100.0
.
To create a class that implements multiple interfaces, add a colon after your class header followed by the name of the interfaces that you want to implement separated by a comma:
For example:
In the example:
PaymentMethod
is an interface that has theinitiatePayment()
function without an implementation.PaymentType
is an interface that has thepaymentType
property that isn't initialized.CreditCardPayment
is a class that implements thePaymentMethod
andPaymentType
interfaces.The
CreditCardPayment
class overrides the inheritedinitiatePayment()
function and thepaymentType
property.paymentMethod
is an instance of theCreditCardPayment
class.The overridden
initiatePayment()
function is called on thepaymentMethod
instance with a parameter of100.0
.The overridden
paymentType
property is accessed on thepaymentMethod
instance.
For more information about interfaces and interface inheritance, see Interfaces.
Delegation
Interfaces are useful, but if your interface contains many functions, child classes may end up with a lot of boilerplate code. When you only want to override a small part of your parent's behavior, you need to repeat yourself a lot.
For example, let's say that you have an interface called Drawable
that contains a number of functions and one property called color
:
You create a class called Circle
which implements the Drawable
interface and provides implementations for all of its member functions:
If you wanted to create a child class of the Circle
class which had the same behavior except for the value of the color
property, you still need to add implementations for each member function of the Circle
class:
You can see that if you have a large number of member functions in the Drawable
interface, the amount of boilerplate code in the RedCircle
class can be very large. However, there is an alternative.
In Kotlin, you can use delegation to delegate the interface implementation to an instance of a class. For example, you can create an instance of the Circle
class and delegate the implementations of the member functions of the Circle
class to this instance. To do this, use the by
keyword. For example:
Here, param
is the name of the instance of the Circle
class that the implementations of member functions are delegated to.
Now you don't have to add implementations for the member functions in the RedCircle
class. The compiler does this for you automatically from the Circle
class. This saves you from having to write a lot of boilerplate code. Instead, you add code only for the behavior you want to change for your child class.
For example, if you want to change the value of the color
property:
If you want to, you can also override the behavior of an inherited member function in the RedCircle
class, but now you don't have to add new lines of code for every inherited member function.
For more information, see Delegation.
Practice
Exercise 1
Imagine you're working on a smart home system. A smart home typically has different types of devices that all have some basic features but also unique behaviors. In the code sample below, complete the abstract
class called SmartDevice
so that the child class SmartLight
can compile successfully.
Then, create another child class called SmartThermostat
that inherits from the SmartDevice
class and implements turnOn()
and turnOff()
functions that return print statements describing which thermostat is heating or turned off. Finally, add another function called adjustTemperature()
that accepts a temperature measurement as an input and prints: $name thermostat set to $temperature°C.
- Hint
In the
SmartDevice
class, add theturnOn()
andturnOff()
functions so that you can override their behavior later in theSmartThermostat
class.
Exercise 2
Create an interface called Media
that you can use to implement specific media classes like Audio
, Video
, or Podcast
. Your interface must include:
A property called
title
to represent the title of the media.A function called
play()
to play the media.
Then, create a class called Audio
that implements the Media
interface. The Audio
class must use the title
property in its constructor as well as have an additional property called composer
that has String
type. In the class, implement the play()
function to print the following: "Playing audio: $title, composed by $composer"
.
- Hint
You can use the
override
keyword in class headers to implement a property from an interface in the constructor.
Exercise 3
You're building a payment processing system for an e-commerce application. Each payment method needs to be able to authorize a payment and process a transaction. Some payments also need to be able to process refunds.
In the
Refundable
interface, add a function calledrefund()
to process refunds.In the
PaymentMethod
abstract class:Add a function called
authorize()
that takes an amount and prints a message containing the amount.Add an abstract function called
processPayment()
that also takes an amount.
Create a class called
CreditCard
that implements theRefundable
interface andPaymentMethod
abstract class. In this class, add implementations for therefund()
andprocessPayment()
functions so that they print the following statements:"Refunding $amount to the credit card."
"Processing credit card payment of $amount."
Exercise 4
You have a simple messaging app that has some basic functionality, but you want to add some functionality for smart messages without significantly duplicating your code.
In the code below, define a class called SmartMessenger
that inherits from the BasicMessenger
class but delegates the implementation to an instance of the BasicMessenger
class.
In the SmartMessenger
class, override the sendMessage()
function to send smart messages. The function must accept a message
as an input and return a printed statement: "Sending a smart message: $message"
. In addition, call the sendMessage()
function from the BasicMessenger
class and prefix the message with [smart]
.