Interfaces and Abstract Classes in Kotlin: Getting Started
Learn how to best use interfaces and abstract classes to create class hierarchies in your Kotlin Android apps. By Mattia Ferigutti.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Interfaces and Abstract Classes in Kotlin: Getting Started
25 mins
- Getting Started
- Inheritance
- Abstract Classes
- Characteristics of Abstract Classes
- Implementing the Animal Abstract Class
- Interfaces
- Characteristics of Interfaces
- Example of Interfaces Application
- Default Methods of an Interface
- Implementing Interfaces in the Project
- The Practical Side of Interfaces
- Holding State Inside an Interface
- Abstract Classes vs Interfaces
- Where to Go From Here?
Implementing Interfaces in the Project
Animals have different abilities. For example, kangaroos can jump very high, cheetahs can run extremely fast and some fish can survive in the ocean’s greatest depths. However, animals often have similar abilities.
In this project, you’ll implement three main abilities animals share: Walking, flying and swimming. These abilities are represented by the following interfaces: Walkable
, Flyable
and Swimmable
.
While it may seem straightforward, animal categories are complicated. For example, not every mammal can walk and not every bird can fly. So Mammal
can’t define member runningSpeed()
if not all mammals can walk or run. You must implement these specific behaviors separately.
At the root of the project, create a package and name it interfaces. In this package, create the following interfaces in their respective files:
Walkable.kt
interface Walkable {
val runningSpeed: Int
}
Walkable.kt
interface Flyable {
val flyingSpeed: Int
}
Walkable.kt
interface Swimmable {
val swimmingSpeed: Int
}
Every interface implements a speed member representing how fast the animal can go. In this example, the code shows a single property for every interface, but feel free to play around with the code and add some abstract
methods and methods with bodies.
Finally, every animal has to implement the right interface. Let Bat
and Parrot
implement Flyable
. Then set their flyingSpeed
s to 95 and 30 respectively. For instance, update Bat
with:
class Bat : Mammal(), Flyable
and …
override val flyingSpeed: Int
get() = 95
Let Elephant
, Giraffe
and Lion
implement Walkable
. Then set their runningSpeed
s to 40, 60 and 80 respectively. For instance, update Elephant
with:
class Elephant : Mammal(), Walkable
override val runningSpeed: Int
get() = 40
Lastly, let Penguin
implement both Walkable
and Swimmable
. Then set the runningSpeed
and swimmingSpeed
to 7 and 40 respectively. Here are the required changes:
class Penguin : Bird(), Walkable, Swimmable
and …
override val runningSpeed: Int
get() = 7
override val swimmingSpeed: Int
get() = 40
Here’s a code breakdown:
-
Bat
,Parrot
,Elephant
,Giraffe
andLion
implement a single interface and redefine a single method. -
Penguin
has two skills. It can swim and run, even though he’s pretty slow. That’s why it’s crucial to have the ability to implement multiple interfaces.
The Practical Side of Interfaces
While you now understand why your future projects should implement interfaces, it’s often hard to grasp this argument in a practical way. In this section, you’ll explore the practical side of interfaces.
As you may have noticed, Zoo Guide is still missing an important feature, the filter.
The Spinner lets you choose between the three different behaviors defined above, Walkable, Flyable and Swimmable, and an All option that includes all the animals in the list. When you select one of these options, the list updates.
Navigate to ui/fragments package. Then open ListFragment.kt. Inside onItemSelected()
, replace the TODO
with:
val updatedList: List<Animal> = when(position) {
0 -> AnimalStore.getAnimalList()
1 -> AnimalStore.getAnimalList().filter { it is Flyable }
2 -> AnimalStore.getAnimalList().filter { it is Walkable }
3 -> AnimalStore.getAnimalList().filter { it is Swimmable }
else -> throw Exception("Unknown animal")
}
viewModel.updateItem(updatedList)
Here’s a code breakdown:
- Anytime a user clicks an item inside the Spinner,
onItemSelected()
is called. - The
when
statement checks the position and filters the list based on the item the user selects. -
ListViewModel
definesupdateItem()
. You need to call it every time you update the list to notify the adapter that something has changed.
filter
will check every Animal
object in the list and choose only the one that respects the behavior the user selected.
Now you can see how an interface isn’t only a contract to implement methods and properties but also creates a sub-category of objects. You can instantiate a group of objects, such as arrays or lists, that share the same behavior. They may be completely different objects, but they share the same behavior.
Holding State Inside an Interface
If you’ve searched for interfaces on the internet, you’ve likely encountered some blogs that say interfaces can’t hold states. That was true until Java 8 introduced methods with a body inside interfaces. But, what’s the state?
The state is what an object has. For example, the name
property inside Bird defines a state of a Bird
object. If an object doesn’t have any instance properties or has some properties with unknown values that don’t change, it’s stateless.
Where can you store the state? You store it in a companion object property.
Consider the code below:
// 1
interface Animal {
var name: String
get() = names[this]?.toUpperCase() ?: "Not in the list"
set(value) { names[this] = value }
var speed: Int
get() = speeds[this] ?: -1
set(value) { speeds[this] = value }
val details: String
get() = "The $name runs at $speed km/h"
companion object {
private val names = mutableMapOf<Animal, String>()
private val speeds = mutableMapOf<Animal, Int>()
}
}
// 2
class Lion: Animal
// 3
class Penguin: Animal
// 4
fun main() {
val lion = Lion()
lion.name = "Lion"
lion.speed = 80
val penguin = Penguin()
penguin.name = "Penguin"
penguin.speed = 7
println(lion.details) // The LION runs at 80 km/h
println(penguin.details) // The PENGUIN runs at 7 km/h
}
Similar to Zoo Guide, this code will save a list of animals.
Here’s what the code does:
- Every animal has three properties: a
name
,speed
anddetails
. The first two are mutable while the last one is immutable. - Both
name
andspeed
can get and store a value using aMutableMap
. Note that thekey
of theMap
is a reference to the Animal interface itself that changes every time a class implements it. - The Animal interface has two private static mutableMaps that hold the state.
- Both
Lion
andPenguin
implementAnimal
. They don’t have to define any members ofAnimal
since all of them have been initialized.
Change the names
‘s visibility from private
to public
and iterate the list:
// 1
interface Animal {
companion object {
val names = mutableMapOf<Animal, String>()
}
}
// 2
Animal.names.forEach { name ->
println(name.value)
}
//Output:
//Lion
//Penguin
As you can see, Animal.names
holds all the values.
The scope of this example was only to show you that it’s possible to store the state inside an interface. However, you shouldn’t do that in real-world projects because it’s poorly managed and not cleaned properly by the garbage collector.