In Chapter 12, we saw how we could use interfaces to create subtypes, and in the last chapter, we saw how we could use delegation with interfaces in order to share general code among specific classes. In this chapter, we’ll learn how we can extend open and abstract classes to accomplish these same things with a different approach.
Modeling a Car
Let’s start by modeling a simple car that can increase its speed with an accelerate()
function.
Each time that we call the accelerate()
function, the car will increase its speed by 1.0
1 and make an engine sound.
This works great for many cars because they make a “Vrrrrrr…” sound.

But wait… here comes Old Man Keaton in his rundown, puttering clunker of a car. Instead of a smooth “Vrrrrrr…” sound, it sounds like “putt-putt-putt”!

And whizzing by him is Rico in his 1969 muscle car! Go, Rico!

Well, it seems like “Vrrrrrr…” is a fine sound for lots of cars, but we’ll need to allow different kinds of cars to have different engine sounds. How can we accomplish this in Kotlin?
This problem sounds familiar. In Chapter 12, in order to create multiple animal classes that could each have its own sound, we created an interface called FarmAnimal
, and classes for each specific animal, like Chicken
, Pig
, and Cow
.
If that approach worked for animals making sounds, can it also work for cars and their engine sounds? Could we convert our Car
class into an interface, like FarmAnimal
, and create subtypes for the different kinds of cars?
Well, it’s possible to convert the Car
class into an interface, but it brings with it some rather significant and undesirable changes. Here’s how it looks as an interface, compared to the original class. Can you spot the differences?
Here are the changes we had to make when converting Car
into an interface:
- Visibility - An interface cannot have
private
members, so we had to makespeed
andmakeEngineSound()
public. This means that code outside ofCar
would be able to set the speed, without going through theaccelerate()
function. Similarly, it’d be possible to callmakeEngineSound()
without accelerating. - State - Although an interface can declare a property, it can’t contain state. In other words, it can’t itself contain a value for that property. So, we had to remove the
= 0.0
from thespeed
property. The implementing classes will have to initialize it, instead. - Instantiation - An interface cannot be instantiated. To work around this, we would have to introduce a class that implements the
Car
interface, and instantiate that, instead.
Those are some concerning changes, so it’d be great if we could create a subtype without introducing them. Thankfully, in addition to creating subtypes from an interface, Kotlin also allows us to create subtypes from a class. But we can’t create a subtype from just any old class. By default, a class is final, which means that Kotlin will not allow us to create subtypes from it.
Instead, we have to modify the class declaration so that Kotlin knows we want to create subtypes from it. One way to do this is with an abstract class.
Introduction to Abstract Classes
Abstract classes are a lot like interfaces, but they can include private functions, private properties, and state. To create an abstract class, just add the abstract
modifier when declaring it. As you can see below, the only difference between the new abstract class and the original class is the word abstract
at the beginning.
So far, so good! Just like with our original Car
code, the speed
property is private and initialized to 0.0
. The makeEngineSound()
function is also private.
There’s one problem, though. As with the interface version of Car
above, we can’t directly instantiate this abstract class - we can only instantiate its subtypes. Later in this chapter, we’ll remedy this, but for now, let’s see how we can create a subtype from our new abstract class!
Extending Abstract Classes
Now that we’ve got this abstract Car
class, we can create a subtype from it. When we create a subtype class from an abstract class, we usually say that the subtype class extends the abstract class. This differs from when we create a subtype class from an interface, in which case we say that the class implements the interface.
When creating a subtype from a class, the class being extended is called a superclass, and the class that’s extending it is called a subclass.
So, how can we create a subclass from Car
? Thankfully, the syntax for subtyping a class looks a whole lot like the syntax for subtyping an interface!
The only difference between these two is the parentheses. Why is it that we need parentheses when we subtype a class, but not when we subtype an interface? Because classes have constructors, but interfaces don’t. The parentheses here are invoking the constructor of the Car
class.
This is much easier to see if we were to add a constructor parameter, so let’s add one for the rate of acceleration
.
Now, when we create our Clunker
subclass, we can pass it a rate of acceleration that’s slower than the default of 1.0
. Now that we’re passing a constructor argument, it’s easier to see that we’re calling the constructor with those parentheses!
In this case, we hard-coded the acceleration to 0.25
for all clunkers. If we don’t want all clunkers to have the same acceleration, we could also add it as a constructor parameter of the Clunker
class, and just relay the argument to the constructor of the Car
class.
Relaying constructor arguments from a subclass (e.g., Clunker
) to a superclass (e.g., Car
) is a very common thing to do. Note that acceleration
in Clunker
’s constructor does not include the val
or var
keyword - we’re just passing it along to the Car
class, which will store it as a property.
Inheritance
Back in Chapter 12, we learned how one interface can inherit the functions and properties of another interface. As the example that we used in that chapter, a FarmAnimal
has a name and it can speak, so we were able to inherit from both a Named
interface and a Speaker
interface.
In the code above, the FarmAnimal
interface inherits a few things:
- It gains the
name
property from theNamed
interface. - It gains the
speak()
function from theSpeaker
interface.
So even though the FarmAnimal
interface doesn’t explicitly declare those members, it inherited them from the other interfaces.
Similarly, when extending an abstract class, the subclass will inherit the functions and properties from the superclass. So, the Clunker
subclass contains a function called accelerate()
, even though it’s not explicitly declared in its class body, because it inherits the function from Car
.
For what it’s worth, it technically also contains acceleration
, speed
, and makeEngineSound()
, but since those are private, they won’t be visible to the subclass. We’ll see how to work around this in a moment.
Interface and Implementation
To understand inheritance, it can be helpful to distinguish between two parts of a class:
- The visible function and property signatures (that is, their names, parameter types, and return types) of a class make up its interface. This term can be confusing, because languages like Kotlin also include a code element called an
interface
, which we covered in Chapter 12. So, when we talk about the “interface of a class”, it’s not always clear whether we’re talking about the public surface area of the class, or an actualinterface
in the class declaration. - The code in the body of a function or property is called its implementation.
To help visualize this, let’s look at the code for one of the first classes we wrote, way back in Chapter 4 - a Circle
.
Now, let’s take the exact same code, but indent the implementation off to the right, in order to help distinguish between the interface and implementation.
- We can think of an interface as the way a class looks from the “outside” - its name, its properties and their types, its functions and their parameter and return types, and so on.
- The implementation, on the other hand, is what a class looks like from the “inside” - its functions and properties that are not externally visible, the body of its functions and properties, and so on. Essentially, its inner workings.
So, when we say that a class is implementing an interface
(referring to the code element here), what we really mean is that it’s providing an implementation - that is, the code in the body - for each function and property that the interface
declares.
When a class inherits from an interface
or a class, what exactly does it inherit? The interface, or the implementation?
- In some cases, it just inherits the interface - that is, the function and property signatures. For example, when an
interface
does not include a default implementation, the class inherits the interface, but must provide its own implementation, such as in theCow
code above. - In other cases, it also inherits the implementation of those functions and properties. For example, when an interface includes a default implementation, the inheriting class can inherit that implementation, such as in the following code.
As we’ll see in a moment, these two things hold true when extending an abstract class, as well.
When a subclass inherits an implementation from its superclass, it might also have an opportunity to replace or augment the implementation that the superclass provides. This is called overriding,2 and it’s how we can customize the sound of a Clunker
’s engine! Let’s look at overriding next.
Overriding Members
Just like when using delegation, you can override functions and properties from an abstract class in order to specialize the behavior of the subclass - such as to give a Clunker
a special engine sound! We can’t just add the override
keyword, though, or we’ll get a compiler error.
The problem here is that the makeEngineSound()
has a private
visibility modifier in the superclass, as in Listing 14.3 above. When a function or property is private
, it’s so private that even its own subclasses can’t see it! We can fix that with a different visibility modifier.
Protected Visibility
A function marked as private
in the superclass isn’t visible in its subclasses. And if you can’t see it, you can’t override it! Of course, one option is to remove the private
modifier, which would make it a public function, but if we do that, then it would be possible to make the engine sound without calling the accelerate()
function, like this:
We only want the car to make an engine sound when accelerating. It’d be great if we could make it so that the makeEngineSound()
function is visible to subclasses, but not to any other code. For these situations, Kotlin provides another visibility modifier, called protected
. Let’s update makeEngineSound()
so that it’s protected
:
A function or property marked as protected
will be visible to both the current class (e.g., Car
) and its subclasses (e.g., Clunker
), but invisible to code everywhere else. With this, makeEngineSound()
is now visible in the Clunker
subclass. Are we ready to override it now?
We’re still getting a compiler error! Remember how classes are final by default? Well, it’s the same thing with functions… by default, a function is final. In other words, it can’t be overridden in subclasses unless we explicitly state that it’s allowed. There are two ways to do this.
Abstract Functions and Properties
The first way is to add the abstract
modifier to the function or property. When a function is marked with abstract
…
- It cannot be implemented in the abstract class, and…
- It must be implemented in the subclass… unless the subclass itself is also abstract!
So, let’s remove the function body from makeEngineSound()
, and add the abstract
modifier to it.
With this, we can finally override the makeEngineSound()
function:
And now we can instantiate and accelerate a clunker…
…which makes that putt-putt-putt sound that follows Old Man Keaton around everywhere he goes!
Again, marking a function or property as abstract
means that each non-abstract subclass must implement it. But what if you want Car
to have a default implementation for makeEngineSound()
, so that subtypes don’t have to override it? For this, we have to turn to a different modifier, which we’ll explore next.
Open Functions and Properties
The second way to allow subclasses to override a function or property is to mark it as open
. Open members can have a default implementation in the superclass, so that subclasses don’t have to provide their own implementation. But they can if they want to. Let’s change our makeEngineSound()
function so that it’s open
instead of abstract
, and add the body to that function again.
With this change, we can run the code from Listing 14.13 again, and we’ll get the exact same result, because Clunker
still overrides the makeEngineSound()
function.
Let’s introduce another subclass that does not override it.
When we instantiate it, and call accelerate()…
…it’ll use the default engine sound of “Vrrrrrr…”.
So, to summarize, abstract classes can be extended by other classes. Their functions and properties can be:
abstract
, in which case they have no body in the abstract class, but subclasses must implement them.open
, in which case they have a body in the abstract class, but subclasses may override them.- Final (i.e., neither
abstract
noropen
), in which case subclasses cannot override them.
There’s still one problem with our code. As mentioned earlier, like an interface, an abstract class doesn’t let you instantiate it directly.
Instead, you have to instantiate one of its subclasses. To fix this, instead of making Car
an abstract class, we can consider making it an open class.
Introduction to Open Classes
An open class is a class that can be both extended and instantiated directly. We can change our Car
class from an abstract class to an open class by simply replacing the keyword abstract
with the keyword open
:
With this simple change, we can now instantiate a Car
directly.
There’s a catch, though - while an open class can have functions and properties that are either open
or final, it cannot contain any that are abstract
. That makes sense, though - imagine if this open Car
class had an abstract function called honk()
, which naturally could have no body. Now, if we were to instantiate and call honk()
on the car, what could we possibly expect to happen?
So again, open classes cannot contain abstract
members. Next, let’s look at how we can use a visibility modifier to give subclasses special access to the functions and properties of a superclass.
Getter and Setter Visibility Modifiers
Let’s create another subclass of Car
. This one’s a muscle car, and the sound of its engine depends on how fast it’s going. Unfortunately, when we try to reference the speed
variable, we get a compiler error:
The problem is that, in the Car
class, the speed
property has a private
visibility modifier on it.
As we saw earlier, we can use the protected
modifier so that subclasses can see the speed
property.
With this change, our MuscleCar
code from Listing 14.20 now compiles just fine!
Let’s not celebrate just yet though. With this change, subclasses can now bypass the accelerate()
function, and directly set the speed to anything they want!
What we really want here is to let the subclasses get the speed
value but prevent them from setting it. Thankfully, in Kotlin, a property’s getter can have a different visibility modifier from its setter. The syntax can seem a little unnatural at first, but here’s how it looks:
This code indicates that:
- The
speed
property isprotected
, so subclasses ofCar
can get its value. - The
speed
property’s setter visibility isprivate
, which means only theCar
class itself can set the value.
By the way, if you prefer to keep everything on one line, you can just use a semicolon to separate them, like this:
For what it’s worth, this is one of two occasions when I might use a semicolon in Kotlin. The other is when adding functions to an enum class.
Combining Interfaces and Abstract/Open Classes
As we saw in Chapter 12, a class can implement multiple interfaces. It’s also possible to implement interfaces and extend a class. To do this, just separate the names of the interfaces and/or superclass with a comma, like this:
The biggest critical difference between interfaces and abstract/open classes is that a subclass can only extend one class. Implement as many interfaces as you want, but you’re stuck with no more than one superclass.3 This is why interfaces can be much more flexible than abstract and open classes.
So, when should we use interfaces, and when should we use abstract or open classes?
Comparing Interfaces, Abstract Classes, and Open Classes
Between interfaces, abstract classes, and open classes, there are a lot of options for creating subtypes, and it can be hard to know which option is the most appropriate for different circumstances. Software design decisions like this are the subject of many books (and many debates!).
Although software analysis and design aren’t in scope for this book, it’s still worth summarizing the significant characteristics of each option, so I’ve included the following handy-dandy chart to help get you pointed in the right direction!
Characteristic | Interface | Abstract Class | Open Class |
---|---|---|---|
Can inherit from it? | Yes | Yes | Yes |
Can inherit from multiple? | Yes | No | No |
Can be instantiated directly? | No | No | Yes |
Can include non-implemented members? | Yes | Yes | No |
Can include default implementation? | Yes | Yes | Yes |
Subclasses and Substitution
As mentioned back in Chapter 12, we can use a subtype anywhere that the Kotlin code expects a supertype. This is true not only for interfaces, but also for abstract and open classes. So, we can explicitly specify the type of a variable as a superclass (e.g., Car
), while actually assigning an instance of a subclass (e.g., MuscleCar
).
The same holds true for calling a function.
If a function has a parameter of type Car
, it will happily receive a MuscleCar
, because - by definition - the subclass has at least all the same functions and properties as its superclass. It could have more than its superclass, but it will never have fewer.
This ability to use a subtype where a supertype is expected, along with the ability of the subtypes to override functions and properties, is called *polymorphism4. It’s a big word that, apart from software development, probably doesn’t mean anything to you (unless you happen to be a biologist), but it’s still important to know, because it’s considered one of the pillars of object-oriented programming.
Class Hierarchies
So far, every subclass we’ve created has been a final class, but it’s entirely possible for a subclass itself to also be an abstract or open class. For example, a clunker that doesn’t drive at all might be classified as a “junker”. To accommodate this, we could make Clunker
an open class, and extend it with a new class called Junker
.
Now, Clunker
is both a subclass of Car
and a superclass of Junker
. Once you’ve got more than a few classes, it can be helpful to visualize the relationships of the different classes with a UML class diagram, like this:
This visualization makes it easy to see how these classes are related in a class hierarchy, where the general classes are toward the top, and as you go down the diagram, the classes become more specific. The depth of a class hierarchy is determined by how many layers of classes there are in the hierarchy. In the diagram above, we see three layers of depth.
Generally, it’s a good idea to limit the depth of a class hierarchy to only a few layers. Otherwise, it gets hard to keep track of which superclasses are providing the different functions and properties, which subclasses are depending on them, and in what ways they’re depending on them.
The Any
Type
Supertypes and subtypes are not limited to our own classes and interfaces. Many of the classes in Kotlin’s standard library implement interfaces and extend abstract or open classes. For example, the basic number types like Int
, Double
, and Float
are all subclasses of an abstract class called Number
.
In fact, every Kotlin class that you write will have at least one superclass. For example, way back in Listing 4.1, we created the simplest class possible:
Even though this class doesn’t explicitly extend a class, it still implicitly extends an open class called Any
. This class is at the very top of the class hierarchy in Kotlin, so even classes that are otherwise unrelated have the Any
class in common with each other.
The Any
class provides a few essential functions that are inherited by all other classes - equals()
, hashCode()
, and toString()
. These three functions can be overridden, but it’s not very often that we need to do so, because Kotlin has a special kind of class that will override those functions for us, with the implementations that we usually need. We’ll learn all about that in the next chapter, as we explore data classes!
Summary
Congratulations for completing this large chapter! Here’s what you learned:
- What abstract classes are.
- What it means to extend a class.
- How functions and properties are inherited from superclasses.
- The difference between the concepts of interface and implementation.
- How to override functions and properties from a superclass.
- How protected visibility gives subclasses access to the members of a superclass.
- The difference between abstract members and open members.
- How open classes can be instantiated and include default implementations.
- How getters and setters can have different visibility modifiers.
- How one class can both extend a class and implement interfaces.
- How classes form a class hierarchy.
- How the Any type is at the top of the class hierarchy in Kotlin.
In the next chapter, we’ll look at data classes. See you then!
Thanks to James Lorenzen and @gbagd24 for reviewing this chapter!
-
To keep things simple, I’m not including a unit of speed. If it helps, feel free to imagine that the
speed
is in kilometers per hour, miles per hour, meters per second, or any other unit you like! ↩︎ -
As you might recall, we did the same kind of thing when we used class delegation in the last chapter, and the term “override” is the same as we used then. ↩︎
-
Like many other programming languages, Kotlin does not allow multiple class inheritance because of the ambiguity created when two superclasses have different implementations of the same function. For more information about this, see The Diamond Problem in Wikipedia’s article on Multiple Inheritance. ↩︎
-
More precisely, this is called subtype polymorphism. There’s another kind called “parametric polymorphism”, which we typically just refer to as “generics”. ↩︎