A Kotlin interface is a blueprint that defines a set of methods and properties that implementing classes must provide. Unlike abstract classes, interfaces in Kotlin can contain both abstract declarations and concrete implementations with default behavior. The interface
keyword is used to declare interfaces in Kotlin, making them a cornerstone of object-oriented design patterns.
interface VehicleInterface {
val maxSpeed: Int
fun start()
fun stop() {
println("Vehicle stopped safely")
}
}
Creating Kotlin interface declarations follows a straightforward syntax pattern. You begin with the interface
keyword, followed by the interface name, and then define the contract within curly braces.
interface MobileDevice {
// Abstract property - must be implemented
val screenSize: Double
// Abstract method - must be implemented
fun powerOn()
// Method with default implementation
fun displayInfo() {
println("Mobile device with $screenSize inch screen")
}
}
Kotlin interface properties can be abstract or have custom getter implementations. Unlike Java interfaces, Kotlin interfaces can declare properties, but they cannot store state directly.
interface StorageDevice {
// Abstract property
val capacity: Int
// Property with custom getter
val formattedCapacity: String
get() = "${capacity}GB"
// Property with backing field - NOT ALLOWED
// val serialNumber: String = "12345" // Compilation error
}
Implementing Kotlin interfaces requires using the colon (:
) operator followed by the interface name. All abstract members must be overridden using the override
keyword.
interface AudioPlayer {
val supportedFormats: List<String>
fun play(file: String)
fun pause()
fun getVolume(): Int = 50 // Default implementation
}
class MusicPlayer : AudioPlayer {
override val supportedFormats = listOf("MP3", "WAV", "FLAC")
override fun play(file: String) {
println("Playing $file")
}
override fun pause() {
println("Playback paused")
}
// Optional: override default implementation
override fun getVolume(): Int = 75
}
One of the most powerful features is Kotlin multiple interface implementation, allowing a single class to implement several interfaces simultaneously.
interface Drawable {
fun draw()
fun getColor(): String = "black"
}
interface Clickable {
fun onClick()
fun isEnabled(): Boolean = true
}
class Button : Drawable, Clickable {
override fun draw() {
println("Drawing button with color: ${getColor()}")
}
override fun onClick() {
if (isEnabled()) {
println("Button clicked!")
}
}
}
When implementing multiple Kotlin interfaces with conflicting method signatures, you must explicitly resolve the conflicts:
interface NetworkInterface {
fun connect() {
println("Network connection established")
}
}
interface BluetoothInterface {
fun connect() {
println("Bluetooth paired successfully")
}
}
class SmartPhone : NetworkInterface, BluetoothInterface {
override fun connect() {
// Call specific interface methods
super<NetworkInterface>.connect()
super<BluetoothInterface>.connect()
println("All connections ready")
}
}
Understanding the difference between Kotlin interfaces vs abstract classes helps choose the right abstraction level:
Feature | Interface | Abstract Class |
---|---|---|
Multiple inheritance | ✅ Supported | ❌ Single inheritance only |
State storage | ❌ No backing fields | ✅ Can store state |
Constructor | ❌ No constructors | ✅ Can have constructors |
Property initialization | ❌ Cannot initialize | ✅ Can initialize properties |
// Interface - contract definition
interface PaymentProcessor {
val processorName: String
fun processPayment(amount: Double): Boolean
}
// Abstract class - partial implementation
abstract class BasePaymentProcessor {
protected var transactionCount = 0 // State storage
abstract fun validatePayment(amount: Double): Boolean
fun logTransaction() { // Concrete implementation
transactionCount++
println("Transaction #$transactionCount processed")
}
}
Kotlin interface default methods provide implementations that can be optionally overridden by implementing classes:
interface NotificationSender {
fun sendNotification(message: String, recipient: String)
// Default method with implementation
fun formatMessage(message: String): String {
return "[${getCurrentTime()}] $message"
}
// Default method calling abstract method
fun sendFormattedNotification(message: String, recipient: String) {
sendNotification(formatMessage(message), recipient)
}
private fun getCurrentTime(): String = "2025-01-20 10:30:00"
}
class EmailSender : NotificationSender {
override fun sendNotification(message: String, recipient: String) {
println("Email sent to $recipient: $message")
}
// Optionally override default implementation
override fun formatMessage(message: String): String {
return "📧 EMAIL: $message"
}
}
Kotlin functional interfaces contain exactly one abstract method and can be used with lambda expressions:
fun interface ClickListener {
fun onClick(view: String)
}
class ButtonWidget {
private var listener: ClickListener? = null
fun setOnClickListener(listener: ClickListener) {
this.listener = listener
}
fun performClick() {
listener?.onClick("Button")
}
}
// Usage with lambda
val button = ButtonWidget()
button.setOnClickListener { view ->
println("$view was clicked!")
}
Kotlin interface delegation allows delegating interface implementation to another object:
interface Logger {
fun log(message: String)
}
class FileLogger : Logger {
override fun log(message: String) {
println("Writing to file: $message")
}
}
class DatabaseManager(logger: Logger) : Logger by logger {
fun saveUser(name: String) {
log("Saving user: $name")
// Database save logic here
}
}
// Usage
val fileLogger = FileLogger()
val dbManager = DatabaseManager(fileLogger)
dbManager.saveUser("John Doe") // Uses delegated logger
interface UserRepository {
suspend fun getUserById(id: String): User?
suspend fun saveUser(user: User): Boolean
suspend fun deleteUser(id: String): Boolean
// Default caching behavior
fun getCacheKey(id: String): String = "user_$id"
}
class RemoteUserRepository : UserRepository {
override suspend fun getUserById(id: String): User? {
// API call implementation
println("Fetching user $id from remote server")
return User(id, "John Doe", "john@example.com")
}
override suspend fun saveUser(user: User): Boolean {
println("Saving user ${user.name} to remote server")
return true
}
override suspend fun deleteUser(id: String): Boolean {
println("Deleting user $id from remote server")
return true
}
}
class LocalUserRepository : UserRepository {
private val users = mutableMapOf<String, User>()
override suspend fun getUserById(id: String): User? {
return users[id]
}
override suspend fun saveUser(user: User): Boolean {
users[user.id] = user
println("User ${user.name} saved locally")
return true
}
override suspend fun deleteUser(id: String): Boolean {
return users.remove(id) != null
}
}
data class User(val id: String, val name: String, val email: String)
interface EventListener<T> {
fun onEvent(event: T)
fun canHandle(event: T): Boolean = true
}
interface EventDispatcher<T> {
fun addEventListener(listener: EventListener<T>)
fun removeEventListener(listener: EventListener<T>)
fun dispatch(event: T)
}
class SimpleEventDispatcher<T> : EventDispatcher<T> {
private val listeners = mutableListOf<EventListener<T>>()
override fun addEventListener(listener: EventListener<T>) {
listeners.add(listener)
}
override fun removeEventListener(listener: EventListener<T>) {
listeners.remove(listener)
}
override fun dispatch(event: T) {
listeners.filter { it.canHandle(event) }
.forEach { it.onEvent(event) }
}
}
// Usage example
sealed class UserEvent {
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val userId: String) : UserEvent()
}
class AnalyticsListener : EventListener<UserEvent> {
override fun onEvent(event: UserEvent) {
when (event) {
is UserEvent.UserLoggedIn ->
println("Analytics: User ${event.userId} logged in")
is UserEvent.UserLoggedOut ->
println("Analytics: User ${event.userId} logged out")
}
}
}
Here’s a comprehensive example demonstrating Kotlin interfaces in a mobile app context:
import kotlinx.coroutines.*
// Data models
data class Product(val id: String, val name: String, val price: Double)
data class CartItem(val product: Product, var quantity: Int)
// Core interfaces
interface ProductService {
suspend fun getProducts(): List<Product>
suspend fun getProductById(id: String): Product?
}
interface CartService {
fun addItem(product: Product, quantity: Int = 1)
fun removeItem(productId: String)
fun getItems(): List<CartItem>
fun getTotalPrice(): Double
fun clear()
}
interface PaymentProcessor {
suspend fun processPayment(amount: Double): PaymentResult
fun getSupportedMethods(): List<String>
}
// Payment result
sealed class PaymentResult {
object Success : PaymentResult()
data class Failed(val reason: String) : PaymentResult()
}
// Implementations
class MockProductService : ProductService {
private val products = listOf(
Product("1", "Smartphone", 699.99),
Product("2", "Laptop", 1299.99),
Product("3", "Headphones", 199.99)
)
override suspend fun getProducts(): List<Product> {
delay(500) // Simulate network delay
return products
}
override suspend fun getProductById(id: String): Product? {
delay(200)
return products.find { it.id == id }
}
}
class InMemoryCartService : CartService {
private val items = mutableListOf<CartItem>()
override fun addItem(product: Product, quantity: Int) {
val existingItem = items.find { it.product.id == product.id }
if (existingItem != null) {
existingItem.quantity += quantity
} else {
items.add(CartItem(product, quantity))
}
println("Added ${product.name} x$quantity to cart")
}
override fun removeItem(productId: String) {
items.removeAll { it.product.id == productId }
println("Removed product $productId from cart")
}
override fun getItems(): List<CartItem> = items.toList()
override fun getTotalPrice(): Double {
return items.sumOf { it.product.price * it.quantity }
}
override fun clear() {
items.clear()
println("Cart cleared")
}
}
class CreditCardProcessor : PaymentProcessor {
override suspend fun processPayment(amount: Double): PaymentResult {
delay(1000) // Simulate payment processing
return if (amount > 0) {
println("Payment of $${"%.2f".format(amount)} processed successfully")
PaymentResult.Success
} else {
PaymentResult.Failed("Invalid amount")
}
}
override fun getSupportedMethods(): List<String> {
return listOf("Visa", "MasterCard", "American Express")
}
}
// Shopping application using interfaces
class ShoppingApp(
private val productService: ProductService,
private val cartService: CartService,
private val paymentProcessor: PaymentProcessor
) {
suspend fun displayProducts() {
println("\n=== Available Products ===")
val products = productService.getProducts()
products.forEach { product ->
println("${product.id}. ${product.name} - $${product.price}")
}
}
suspend fun addToCart(productId: String, quantity: Int = 1) {
val product = productService.getProductById(productId)
if (product != null) {
cartService.addItem(product, quantity)
} else {
println("Product not found")
}
}
fun displayCart() {
println("\n=== Shopping Cart ===")
val items = cartService.getItems()
if (items.isEmpty()) {
println("Cart is empty")
} else {
items.forEach { item ->
val total = item.product.price * item.quantity
println("${item.product.name} x${item.quantity} = $${"%.2f".format(total)}")
}
println("Total: $${"%.2f".format(cartService.getTotalPrice())}")
}
}
suspend fun checkout() {
val total = cartService.getTotalPrice()
if (total > 0) {
println("\n=== Processing Checkout ===")
println("Payment methods: ${paymentProcessor.getSupportedMethods().joinToString(", ")}")
when (val result = paymentProcessor.processPayment(total)) {
is PaymentResult.Success -> {
println("Order completed successfully!")
cartService.clear()
}
is PaymentResult.Failed -> {
println("Payment failed: ${result.reason}")
}
}
} else {
println("Cart is empty, nothing to checkout")
}
}
}
// Main function demonstrating the application
suspend fun main() {
// Initialize services
val productService = MockProductService()
val cartService = InMemoryCartService()
val paymentProcessor = CreditCardProcessor()
// Create shopping app
val app = ShoppingApp(productService, cartService, paymentProcessor)
// Demo workflow
app.displayProducts()
// Add items to cart
app.addToCart("1", 2) // 2 smartphones
app.addToCart("3", 1) // 1 headphones
// Display cart
app.displayCart()
// Process checkout
app.checkout()
// Display final cart state
app.displayCart()
}
Expected Output:
=== Available Products ===
1. Smartphone - $699.99
2. Laptop - $1299.99
3. Headphones - $199.99
Added Smartphone x2 to cart
Added Headphones x1 to cart
=== Shopping Cart ===
Smartphone x2 = $1399.98
Headphones x1 = $199.99
Total: $1599.97
=== Processing Checkout ===
Payment methods: Visa, MasterCard, American Express
Payment of $1599.97 processed successfully
Order completed successfully!
Cart cleared
=== Shopping Cart ===
Cart is empty
This comprehensive example demonstrates how Kotlin interfaces enable clean architecture, dependency injection, and testable code. The interfaces define clear contracts while implementations provide specific behavior, making the system flexible and maintainable for real-world applications.