Kotlin Interfaces and Abstract Classes
Interfaces and abstract classes are two tools Kotlin gives you to define contracts — rules that a class must follow. Both enforce structure in your code, but they work differently and suit different situations.
Interfaces — Defining a Contract
An interface defines what a class can do without specifying how it does it. Think of an interface as a job description — it lists the required skills, but each person hired fulfills those skills in their own way.
interface Printable {
fun print() // No body — must be implemented
fun preview() {
println("Previewing...") // Has a default body — optional to override
}
}
Implementing an Interface
class Document(val title: String) : Printable {
override fun print() {
println("Printing document: $title")
}
}
class Photo(val filename: String) : Printable {
override fun print() {
println("Printing photo: $filename")
}
override fun preview() {
println("Showing photo preview of $filename")
}
}
val doc = Document("Report.pdf")
doc.print() // Printing document: Report.pdf
doc.preview() // Previewing... (default used)
val photo = Photo("sunset.jpg")
photo.print() // Printing photo: sunset.jpg
photo.preview() // Showing photo preview of sunset.jpg
Multiple Interfaces
A class can implement multiple interfaces. This is how Kotlin achieves multiple inheritance of behavior — unlike Java's class-based limitation.
interface Flyable {
fun fly() { println("Flying...") }
}
interface Swimmable {
fun swim() { println("Swimming...") }
}
class Duck : Flyable, Swimmable {
override fun fly() { println("Duck flying low.") }
// swim() uses default implementation
}
val duck = Duck()
duck.fly() // Duck flying low.
duck.swim() // Swimming...
Diagram — Multiple Interfaces
[Flyable] [Swimmable]
fly() swim()
\ /
\ /
[Duck]
fly() ← overridden
swim() ← default from Swimmable
Interface with Properties
interface Identifiable {
val id: String // Abstract property — no backing field
val label: String
get() = "ID: $id" // Property with default getter
}
class User(override val id: String, val name: String) : Identifiable
val user = User("U001", "Rahul")
println(user.id) // U001
println(user.label) // ID: U001
Abstract Classes — Partial Implementation
An abstract class can have both abstract members (no body, must be overridden) and concrete members (full body, ready to use). It bridges the gap between a regular class and an interface.
abstract class Report(val title: String) {
abstract fun generateContent(): String // subclass must implement
fun save() { // shared implementation
val content = generateContent()
println("Saving report: $title")
println(content)
}
}
class SalesReport(title: String, val totalSales: Double) : Report(title) {
override fun generateContent() = "Total Sales: ₹$totalSales"
}
class InventoryReport(title: String, val itemCount: Int) : Report(title) {
override fun generateContent() = "Items in stock: $itemCount"
}
SalesReport("Q3 Sales", 450000.0).save()
// Saving report: Q3 Sales
// Total Sales: ₹450000.0
Interface vs Abstract Class — When to Use Which
Feature | Interface | Abstract Class
---------------------------------|------------|----------------
Extend multiple | Yes | No (single only)
Constructor parameters | No | Yes
Store state (fields) | No | Yes
Default method bodies | Yes | Yes
Can be instantiated directly | No | No
Best for | Shared | Shared base
| behavior | with state
Practical Rule
Question: "Does this represent what a class IS or what it CAN DO?" IS → Abstract Class (e.g., Employee is a type of person with a name) CAN DO → Interface (e.g., Printable, Clickable, Shareable)
Resolving Diamond Conflict
When two interfaces share the same method name, the implementing class must override the method and explicitly call the desired interface version.
interface A {
fun greet() = println("Hello from A")
}
interface B {
fun greet() = println("Hello from B")
}
class C : A, B {
override fun greet() {
super.greet() // call A's version
super.greet() // call B's version
}
}
C().greet()
// Hello from A
// Hello from B
