Lesson 16 - Interfaces in Kotlin
In the previous lesson, Diary with a database in Kotlin (finishing), we practiced working with the
List
collection and created an electronic diary in . In today's
tutorial, we'll be getting back into some more theoretical matters. We're going
to unveil yet another utility of the object-oriented programming world!
Interface
When one refers to an object's interface, they are referring to how an object
is visible from the outside. We already know that an object contains methods
that can be set as private or public. The object's interface consists of its
public methods, which is the way we communicate with certain types of objects.
We have already dealt with public methods in previous lessons, e.g. the ones we
created for our arena warrior. The Warrior
class we made had the
following public methods:
attack(enemy: Warrior)
defend(hit: Int)
alive(): Boolean
setMessage(message: String)
getLastMessage(): String
healthBar(): String
If we store a Warrior
class instance into a variable, we are
able to call the attack()
or defend()
methods on it.
Nothing new, right?
As a matter of fact, we are able to declare an interface separately, sort of like we do with classes, and we would then be able to use it as a data type.
We'll give it a go, but on a simpler class than the Warrior
class we made. We'll start by creating a new project, naming it
InterfaceSample
, and adding a simple class. In my opinion,
theoretical concepts should be explained using light examples, meaning not as
serious. With that all being said, let's make a bird class! Our bird will be
able to chirp, breathe and peck. Our Bird
class
will look something like this:
class Bird { fun chirp() { println("♫ ♫ ♫") } fun breathe() { println("Breathing...") } fun peck() { println("Peck, peck!") } }
Done! Now, let's move to Main.kt
and create a bird instance:
val bird = Bird()
Once you've created an instance of the Bird
class, write
whatever you named the instance, in my case, it's bird
, and type a
dot right after it. IntelliJ will then display all of its class methods (you can
also invoke this menu by pressing Ctrl + Space):
We now see everything that we can call on the bird
instance. The
3 methods we've just implemented are there as well as some others that all
objects have from its base.
Now let's create an interface for our bird. We'll use the
interface
keyword to do just that. Naming
interfaces in Kotlin is a rocket science, as well as in Java.
We'll get by with BirdInterface
though. Right-click on the project,
and choose "New" -> "Kotlin File/Class". In the dropdown menu, choose
"Interface".
An empty interface will be added to our project. We'll add the headers of methods which this interface will require. The implementation itself, method content, is added later by the class that implements the interface (see further).
Kotlin, as well as Java 8, allows us to implement method
bodies in interfaces as well. This goes against what we're going to talk about
here today. This functionality was made to support traits. However, working with
traits is a bit different than with interfaces and many languages such as PHP or Scala even have a separate trait
keyword for
it.
Let's add method headers to BirdInterface
, we'll purposely omit
one of them and only add chirping and breathing:
interface BirdInterface {
fun chirp()
fun breathe()
}
An interface contains public methods only. It wouldn't make sense otherwise since it specifies how to work with an object from the outside.
Let's go back to Main.kt
and change the line with the
bird
variable so it won't be longer of the Bird
type,
but of the BirdInterface
type:
val bird: BirdInterface = Bird()
What the code above means is that in the bird
variable, we
expect an object that contains the methods specified in the
BirdInterface
interface. Kotlin reports an error since the
Bird
class doesn't implement BirdInterface
yet.
Although it does have the needed methods, it must first be informed that it
implements this interface. Let's move to the Bird
class and let it
implement the BirdInterface
interface. We'll assign the
override
keyword (as we did with inheritance) to the implemented
methods. We implement interfaces using the :
operator:
class Bird: BirdInterface { override fun chirp() { println("♫ ♫ ♫") } override fun breathe() { println("Breathing...") } fun peck() { println("Peck, peck!") }
When we go back to Main.kt
, the line with the variable of the
BirdInterface
type no longer causes an error. The Bird
class correctly implements the BirdInterface
interface. Meaning
that Bird
instances can now be stored in variables of this
type.
Now just for completeness' sake, let's see what happens when we remove a
method from the class, which is required by the interface, like the
chirp()
method. IntelliJ will warn us that the implementation is
not complete. Once you have visual confirmation of the interface's dependency on
the class, put the method back where it belongs.
Let's write bird
with a dot after it. Again, IntelliJ will offer
the following methods:
We can see that now we can call only the methods provided by the interface on
the instance. That's because the bird is now a variable of the
BirdInterface
type, not the Bird
type. Meaning that we
cannot call the peck()
method because we did not add it to the
interface.
Why did we leave it out in the first place, you may ask? Lots of potential reasons, we've already encountered one of them. By using an interface, we simplify a complex object and expose only the parts needed in a certain part of the program.
Also, I must add that interfaces cannot be instantiated. In other words, this code will not work:
// this code won't work
val bird: BirdInterface = BirdInterface()
Multiple inheritance
Kotlin, like most programming languages, doesn't support multiple inheritance. Meaning that we can't inherit one class from more than one other class. Mainly, because method naming collisions could very well occur when multiple classes containing methods with the same name inherit their methods into another class (the diamond problem). Multiple inheritance is often worked around using interfaces because we are allowed to implement as many interfaces in a class as we want. A class of the sort only allows us to work with its instances in ways that we want to. We wouldn't have to worry about of what object type it actually is, or what it provides beyond the interfaces.
Now let's add a LizardInterface
to our project.
Lizards will be able to breathe and crawl:
interface LizardInterface {
fun crawl()
fun breathe()
}
Next, we'll try "multiple inheritance", more accurately, implement multiple
interfaces by a single class. We'll add a Pterodactyl
class to the
project. It'll implement both the BirdInterface
and
LizardInterface
interfaces:
class Pterodactyl: LizardInterface, BirdInterface {
}
If we click on the "light-bulb" icon, we can choose to "Implement members". IntelliJ will then automatically generate the class methods we choose.
After having both interfaces implemented, the code will look like this:
class Pterodactyl: LizardInterface, BirdInterface { override fun crawl() { TODO("not implemented") // To change body of created functions use File | Settings | File Templates } override fun breathe() { TODO("not implemented") // To change body of created functions use File | Settings | File Templates } override fun chirp() { TODO("not implemented") // To change body of created functions use File | Settings | File Templates } }
Now all we have to do is specify what we want each method to do:
override fun crawl() { println("I'm crawling...") } override fun breathe() { println("I'm breathing...") } override fun chirp() { println("♫ ♫♫ ♫ ♫ ♫♫") }
That's pretty much it! Now, let's add an instance of the
Pterodactyl
class in Main.kt
:
val pterodactyl = Pterodactyl()
Make sure, that it has both the bird and lizard methods:
We'll stick to interfaces for a little while since there's much more to them that we haven't covered. In the next lesson, Type casting and object hierarchy in Kotlin, you'll learn more advanced techniques of the object-oriented programming.