1. Overview

In this tutorial, we’ll discuss how to define and implement interfaces in Kotlin.

We’ll also take a look at how multiple interfaces can be implemented by a class. This can certainly cause conflicts, and we’ll learn the mechanism that Kotlin has to resolve them.

2. Interfaces in Kotlin

An interface is a way to provide a description or contract for classes in object-oriented programming. They may contain properties and functions in abstract or concrete ways depending upon the programming language. We’ll go through the details of interfaces in Kotlin.

Interfaces in Kotlin are similar to interfaces in many other languages like Java. But they have specific syntax, let’s review them in the next few sub-sections.

2.1. Defining Interfaces

Let’s begin by defining our first interface in Kotlin:

interface SimpleInterface

This is the simplest interface which is completely empty. These are also known as marker interfaces.

Let’s now add some functions to our interface:

interface SimpleInterface {
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, World!")
    }
}

We have added two methods to our previously defined interface:

  • One of them called firstMethod is an abstract method
  • While the other one called secondMethod has a default implementation.

Let’s go ahead and add some properties to our interface now:

interface SimpleInterface {	
    val firstProp: String
    val secondProp: String
        get() = "Second Property"
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, from: " + secondProp)
    }
}

Here we have added two properties to our interface:

  • One of them called firstProp is of type String and is abstract
  • The second one called secondProp is also of the type string but it defines an implementation for its accessor.

Note that properties in an interface cannot maintain state. So the following is an illegal expression in Kotlin:

interface SimpleInterface {
    val firstProp: String = "First Property" // Illegal declaration
}

2.2. Implementing Interfaces

Now that we have defined a basic interface, let’s see how we can implement that in a class in Kotlin:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }	
}

Note that when we define SimpleClass as an implementation of SimpleInterface, we only have to provide the implementation for abstract properties and functions. However, we can override any previously defined property or function, too.

Let’s now override all previously defined properties and functions in our class:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override val secondProp: String
        get() = "Second Property, Overridden!"
    
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
    override fun secondMethod(): String {
        return("Hello, from: " + secondProp + firstProp)
    }
}

Here, we’ve overridden the property secondProp and the function secondFunction which were previously defined in the interface SimpleInterface.

2.3. Implementing Interfaces Through Delegation

Delegation is a design pattern in object-oriented programming to achieve code reusability through composition instead of inheritance. While this is possible to implement in many languages, like Java, Kotlin has native support for implementation through delegation.

If we begin with a basic interface and class:

interface MyInterface {
    fun someMethod(): String
}

class MyClass() : MyInterface {
    override fun someMethod(): String {
        return("Hello, World!")
    }
}

So far, nothing new. But now, we can define another class that implements MyInterface through delegation:

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass expects a delegate as an argument that actually implements the interface MyInterface.

Let’s see how we can call a function of the interface through delegate:

val myClass = MyClass()
MyDerivedClass(myClass).someMethod()

Here we have instantiated MyClass and used that as the delegate to call functions of the interface on MyDerivedClass, which actually never implemented these functions directly.

2.4. Omitting Methods in Interface Implementations

There may be cases where we only want to implement some, but not all, methods of an interface. This is useful when there are optional functions with sensible defaults for the interface. We’ll cover two ways to achieve this.

If we own the interface and we expect this to be a standard use case, we can provide default implementations in the interface itself:

interface MyAdapter {
    fun onFocus() { /* default does nothing */ }
    fun onClick()
    fun onDrag() { /* default does nothing */ }
}

class MyAdapterImpl : MyAdapter {
    override fun onClick() {
        println("Clicked!")
    }
}

As we can see, implementation sub-classes don’t have to provide implementations for interface functions that have default implementations, but they can if it’s necessary.

Alternatively, if the interface does not have default implementations, we can create our own intermediate interface that does so:

interface MyAdapter { 
    fun onFocus() 
    fun onClick() 
    fun onDrag()
}

interface MyOnClickAdapter : MyAdapter {
    override fun onFocus() { /* default does nothing */ }
    override fun onDrag() { /* default does nothing */ }
}

class MyOnClickAdapterImpl : MyOnClickAdapter {
    override fun onClick() {
        println("Clicked!")
    }
}

As we can see, in this case, we provide MyOnClickAdapter that our concrete classes implement to provide default implementations. The end result is the same, despite not having control over the interface itself.

3. Multiple Inheritance

Multiple inheritance is a key concept in the object-oriented programming paradigm. This allows for a class to inherit characteristics from more than one parent object, like an interface, for example.

While this provides more flexibility in object modeling, it comes with its own set of complexities. One such is the “diamond problem”.

Java 8 has its own mechanisms for addressing the diamond problem, as does any other language that allows for multiple inheritances.

Let’s see how Kotlin addresses it through interfaces.

3.1. Inheriting Multiple Interfaces

We’ll begin by defining two simple interfaces:

interface FirstInterface {
    fun someMethod(): String
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in FirstInterface")
    }
}

interface SecondInterface {
    fun someMethod(): String {
        return("Hello, from someMethod in SecondInterface")
    }
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in SecondInterface")
    }
}

Note that both interfaces have methods with the same contract.

Now let’s define a class that inherits from both these interfaces:

class SomeClass: FirstInterface, SecondInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SomeClass")
    }
    override fun anotherMethod(): String {
        return("Hello, from anotherMethod in SomeClass")
    }
}

As we can see, SomeClass implements both FirstInterface and SecondInterface. While syntactically this is quite simple, there is a bit of semantics that requires attention here. We will go over this in the next sub-section.

3.2. Resolving Conflicts

When implementing multiple interfaces, a class may inherit a function that has a default implementation for the same contract in multiple interfaces. This raises the problem of invocation for this function from an instance of the implementing class.

To resolve this conflict, Kotlin requires the subclass to provide an overridden implementation for such functions to make the resolution explicit.

For example, SomeClass above implements anotherMethod. But, if it didn’t, Kotlin wouldn’t know whether to invoke First or SecondInterface’s default implementation of anotherMethodSomeClass must implement anotherMethod for this reason.

However, someMethod is a bit different since there is actually no conflict. FirstInterface doesn’t provide a default implementation for someMethod. That said, SomeClass still must implement it because Kotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.

3.3. Resolving the Diamond Problem

A “diamond problem” occurs when two child objects of a base object describe a particular behavior defined by the base object. Now an object inheriting from both these child objects has to resolve which inherited behavior it subscribes to.

Kotlin’s solution to this problem is through the rules defined for multiple inheritances in the previous sub-section. Let’s define a few interfaces and an implementing class to present the diamond problem:

interface BaseInterface {
    fun someMethod(): String
}

interface FirstChildInterface: BaseInterface {	
    override fun someMethod(): String {
        return("Hello, from someMethod in FirstChildInterface")
    }
}

interface SecondChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SecondChildInterface")
    }
}

class ChildClass: FirstChildInterface, SecondChildInterface {
    override fun someMethod(): String {
        return super<SecondChildInterface>.someMethod()
    }
}

Here we have defined BaseInterface which declared an abstract function called someMethod. Both the interfaces FirstChildInterface and SecondChildInterface inherits from BaseInterface and implement the function someMethod.

Now as we implement ChildClass inheriting from FirstChildInterface and SecondChildInterface, it’s necessary for us to override the function someMethod. However, even though we must override the method, we can still simply call super as we do here with SecondChildInterface.

4. Interfaces Compared to Abstract Classes in Kotlin

Abstract classes in Kotlin are classes that cannot be instantiated. This may contain one or more properties and functions. These properties and functions can be abstract or concrete. Any class inheriting from an abstract class must implement all inherited abstract properties and functions unless that class itself is also declared as abstract.

4.1. Differences Between Interface and Abstract Class

Wait! Doesn’t that sound exactly like what an interface does?

Actually, at the outset, an abstract class is not very different from the interface. But, there are subtle differences that govern the choice we make:

  • A class in Kotlin can implement as many interfaces as they like but it can only extend from one abstract class
  • Properties in the interface cannot maintain state, while they can in an abstract class

4.2. When Should We Use What?

An interface is just a blueprint for defining classes, they can optionally have some default implementations as well. On the other hand, an abstract class is an incomplete implementation which is completed by the extending classes.

Typically interfaces should be used to define the contract, which elicits the capabilities it promises to deliver. An implementing class holds the responsibility of delivering those promises. An abstract class, however, should be used to share partial characteristics with extending classes. An extending class can take it further to complete it.

5. Comparison With Java Interfaces

With the changes to Java interface in Java 8, they have come very close to Kotlin interfaces. One of our previous articles captures the new features introduced in Java 8 including changes to the interface.

There are mostly syntactic differences between Java and Kotlin interfaces now. One difference that stands out is related to the keyword “override”. In Kotlin, while implementing abstract properties or functions inherited from an interface, it is mandatory to qualify them with the keyword “override“. There is no such explicit requirement in Java.

6. Conclusion

In this tutorial, we discussed Kotlin interfaces, how to define and implement them. Then we talked about inheriting from multiple interfaces and the conflict they may create. We took a look at how Kotlin handles such conflicts.

Finally, we discussed interfaces compared to abstract classes in Kotlin. We also briefly talked about how does Kotlin interface compares with the Java interface.

As always, the code for the examples is available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.