Instruction

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Interfaces

Inheritance is a powerful tool for code reuse, but it focuses on the “is-a” relationship between classes. A subclass is a specialized version of its superclass. Interfaces, on the other hand, introduce a different concept: the “is” relationship.

interface Edible {
  val isItReallyEdible: Boolean
  
  fun taste(): String
}

Default Implementation

Remember how interfaces are all about defining behavior contracts? While interfaces primarily focus on what a class should do, they can sometimes help with how to do it. This is where default methods come in.

  fun consumer(): String {
    return "Human"
  }

Implementation

Now you’ll create two classes implementing the Edible interface:

class Fruit : Edible {
  override val isItReallyEdible = true

  override fun taste(): String {
    return "Sweet"
  }

}

class Mushroom : Edible {
  override val isItReallyEdible = false
  
  override fun taste(): String {
    return "Delicious"
  }

  override fun consumer(): String {
    return "Pig"
  }
}
fun main() {
  val items = listOf<Edible>(Fruit(), Mushroom())
  items.forEach { item ->
    println("${item.javaClass.simpleName} really edible: ${item.isItReallyEdible}, taste: ${item.taste()}, consumed by ${item.consumer()}")
  }
}
// Fruit really edible: true, taste: Sweet, consumed by Human
// Mushroom really edible: false, taste: Delicious, consumed by Pig

Multiple Interfaces

Each class can implement multiple interfaces. It’s a valuable tool for building flexible, reusable, and well-designed code in Kotlin. It promotes loose coupling and clear separation of concerns, making your code easier to understand, maintain, and evolve. Here are just a few benefits:


interface Edible {
  fun taste(): String
}

interface Sweet {
  fun andSour(): String
}

class Fruit(val name: String, val sour: Boolean = false) : Edible, Sweet {
  override fun taste(): String {
    return "$name is very good"
  }

  override fun andSour(): String {
    return if(sour)"Maybe" else "Definitely not!"
  }
}

class Garlic : Edible {
  override fun taste(): String {
    return "delicious"
  }
}

fun main() {
  // Error: Type mismatch: inferred type is Garlic but Sweet was expected
  val sweetItems = listOf<Sweet>(Fruit("Peach"), Garlic())
  sweetItems.forEach { item ->
    println("${item.javaClass.simpleName} sour: ${item.andSour()}")
  }
}
fun main() {
  val items = listOf<Edible>(Fruit("Peach"), Garlic())
  items.forEach { item ->
    println("${item.javaClass.simpleName} taste: ${item.taste()}")
  }
}
//Fruit taste: Peach is very good
//Garlic taste: delicious
fun main() {
  //   Same, but for Sweet
  val sweetItems = listOf<Sweet>(Fruit("Peach"), Fruit("Tomato", false))
  sweetItems.forEach { item ->
    // Error:
    // Unresolved reference: name
    // Unresolved reference: taste
    println("${item.name} taste: ${item.taste()}. Sour? ${item.andSour()}")
  }
}
fun printList(val sweets: List<Sweet>)
fun main() {
  // Same, but name is not available
  val sweetItems = listOf<Sweet>(Fruit("Peach"), Fruit("Tomato", true))
  sweetItems.forEach { item ->
    println("${item.javaClass.simpleName} is Sweet. Sour? ${item.andSour()}")
  }
}
//Fruit is Sweet. Sour? Definitely not!
//Fruit is Sweet. Sour? Maybe
fun main() {
  // Same, but for Fruit
  val fruitItems = listOf<Fruit>(Fruit("Peach"), Fruit("Tomato", true))
  fruitItems.forEach { item ->
    println( "${item.name} taste: ${item.taste()}. Sour? ${item.andSour()}")
  }
}
//Peach taste: Peach is very good. Sour? Definitely not!
//Tomato taste: Tomato is very good. Sour? Maybe
See forum comments
Download course materials from Github
Previous: Introduction Next: Demo