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.

4.5 (4) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Implementing the Animal Abstract Class

Open Animal.kt from the model/animals package. Make the class abstract:

 abstract class Animal

Remove the existing getHungerAmount() method and move the properties from the constructor into the body of the class as abstract properties.

  abstract fun getHungerAmount() : Int

  abstract val name: String

  @get:DrawableRes
  abstract val image: Int

  abstract val food: Food

  abstract val habitat: Habitat

Do the same for Mammal and this time add furColor as an immutable abstract property:

abstract class Mammal : Animal() {

  abstract val furColor: List<Int>
}

For Bird add a feathersColor abstract property instead:

abstract class Bird: Animal() {

  abstract val feathersColor: List<Int>
}

For Bat, this is how the code will look after the update:

class Bat : Mammal() {

  override val furColor: List<Int>
    get() = listOf(
        Color.parseColor("#463D4A")
    )

  override fun getHungerAmount() : Int {
    return (0..100).random()
  }

  override val name: String
    get() = "Bat"

  override val image: Int
    get() = R.drawable.bat

  override val food: Food
    get() = Food.INSECTS

  override val habitat: Habitat
    get() = Habitat.CAVE
}

Follow the same instruction for Elephant, Giraffe, Lion, Mammal and Penguin. Feel free to refer to the final project in the materials you downloaded at the start of the tutorial.

In the updates above, you:

  • There’s less boilerplate. Mammal isn’t implementing all the members that Animal defines because they’re both abstract classes. An abstract class doesn’t need to implement an abstract member.
  • Lion implements and initializes all the members implemented in both Animal and Mammal.
  1. Set Animal and Mammal as abstract. You can’t create an instance of these classes anymore, so you solved the first problem.
  2. Moved the variables from the constructor into the class’s body. As you can see, the code is clearer now, but there are some other benefits to using this new approach:
  3. Defined getHungerAmount() as abstract. Now, the compiler forces you to implement the method inside Lion. Try commenting it out and the compiler will scream at you. For now, you’ve solved the second problem, too.

The third issue is still present, and you need to solve it. Hint: You’ll need to use interfaces to overcome it. :]

Interfaces

Interfaces establish an is, rather than an is-a, relationship between the class and the interface. Unlike abstract classes, interfaces define a specific behavior rather than a template. From now on, I’ll often refer to an interface as a behavior because interfaces can connect objects that otherwise have nothing in common.

Characteristics of Interfaces

Every time your class implements an interface, it’s signing a contract. The contract says that once a class implements an interface, it must also implement all the members defined inside the interface. However, there’s an exception you’ll explore in the next section.

Since Kotlin doesn’t support multiple inheritances, the compiler won’t let a subclass have multiple parent classes. It can only extend one class. But there’s good news: You can implement as many interfaces as you want.

Think about the WordPress template example. You can’t use more than one template for a single site. However, you can add features.

So, if you’re building an e-commerce website and the template doesn’t have a map indicating your store’s position, you can add one with an external plugin. In fact, you can add as many plugins as you want. Kotlin works in the same way with interfaces.

Interfaces can only define open members. So every class that implements an interface can access every non-private member. Remember that abstract methods can’t be private.

Example of Interfaces Application

Imagine you have two classes, Microwave and WashingMachine, that only have one thing in common: They both need electricity to work. What would you do to connect them? Take a look at the code below for an example of how this could be done:

// 1
interface Pluggable {
  
    val neededWattToWork: Int
  
    //Measured in Watt
    fun electricityConsumed(wattLimit: Int) : Int

    fun turnOff()
    
    fun turnOn()
}

// 2
class Microwave : Pluggable {
    
    override val neededWattToWork = 15

    override fun electricityConsumed(wattLimit: Int): Int {
        return if (neededWattToWork > wattLimit) {
            turnOff()
            0
        } else {
            turnOn()
            neededWattToWork
        }
    }

    override fun turnOff() {
        println("Turning off..")
    }

    override fun turnOn() {
        println("Turning on..")
    }
}

// 3
class WashingMachine : Pluggable {

    override val neededWattToWork = 60

    override fun electricityConsumed(wattLimit: Int): Int {
        return if (neededWattToWork > wattLimit) {
            turnOff()
            0
        } else {
            turnOn()
            neededWattToWork
        }
    }

    override fun turnOff() {
        println("Turning off..")
    }

    override fun turnOn() {
        println("Turning on..")
    }
}

The best way to solve this problem is to implement an interface that defines a common behavior. That’s exactly what Pluggable does. Now every class that extends this interface needs to implement all the members. In doing so, they can connect to the power.

In Java, you can only define constant properties inside interfaces. So, every time you try to add a property in Kotlin like this:

String password = "Password1234";

The compiler adds public static final under the hood. In Java, you have to initialize properties so you can’t do something like this:

String password; //This code won't run

Kotlin, on the other hand, lets you define non-static properties inside interfaces that can only be val. This becomes useful when you handle only one value, and a method would be too much, as in the neededWattToWork property above. It would be inconvenient to define a method only to return a value, even though, as the decompiled Java code shows, under the hood, the compiler creates a method for the property anyway.

public interface Pluggable {
   int getNeededWattToWork();

   int electricityConsumed(int var1);

   void turnOff();

   void turnOn();
}

However, Kotlin doesn’t let you create var properties because a var property includes three components:

  • A private field to store its value.
  • A getter to take its value.
  • A setter to set its value.

The problem here is that an interface can’t store a value because it can’t have a property.

Default Methods of an Interface

Default methods are a great way to fight the boilerplate. With Kotlin, a method can have a body, and a property can hold a value inside it. Does it make sense to have methods with a body inside an interface?

The answer is yes. As you know, when a class implements an interface, it also has to implement all its members. Sometimes, however, this isn’t useful because the logic is independent of the classes that implement the interface. So instead of having each class provide the same implementation, it’s better to have the definition within the interface.

Considering the home appliances example above, it’d be cool to know the voltage each needs to work. To do that, you can add a new method, voltage(), to Pluggable like this:

    fun voltage() : Int {
        return 230
    }

Then override it in Microwave:

    // There is no need to implement this method
    override fun voltage(): Int {
        return super.voltage()
    }

And also in WashingMachine:

    override fun voltage(): Int {
        return 300
    }

Since 99% of the home appliances use a classic voltage, implementing the method and returning the value every time wouldn’t make sense. By implementing a method with a body and default value, you don’t have to implement this method and return a value. The interface already does that for you.

You can implement the method and change the voltage whenever you need to redefine the voltage, as you did inside WashingMachine.

Now, it’s time to implement interfaces in Zoo Guide.