Skip to main content

Kotlin Interface Properties

In object-oriented programming, interfaces traditionally define method signatures that implementing classes must provide. Kotlin takes this concept further by allowing interfaces to contain not only method declarations but also properties. This feature makes Kotlin interfaces more powerful and flexible compared to interfaces in languages like Java.

What are Interface Properties?

Interface properties are property declarations inside an interface that can be:

  • Abstract properties (without initialization)
  • Properties with default implementations

Let's explore how these work in Kotlin and why they're useful.

Abstract Properties in Interfaces

The most basic way to define properties in interfaces is to declare them without initialization. These act as abstract properties that implementing classes must provide.

kotlin
interface User {
val id: String // Abstract property (must be implemented)
val name: String // Abstract property (must be implemented)
}

class Student(override val id: String, override val name: String) : User {
// Implementation of User interface with concrete property values
}

fun main() {
val student = Student("S12345", "Alex Johnson")
println("ID: ${student.id}")
println("Name: ${student.name}")
}

Output:

ID: S12345
Name: Alex Johnson

In this example:

  • The User interface declares two properties: id and name
  • The Student class implements these properties by providing concrete values
  • We use the override keyword to indicate that we are implementing the interface properties

Properties with Default Implementations

Kotlin interfaces can also include properties with custom getters, allowing you to provide default implementations.

kotlin
interface User {
val id: String // Abstract property
val name: String // Abstract property
val email: String // Abstract property

// Property with default implementation (custom getter)
val formattedInfo: String
get() = "User(id=$id, name=$name, email=$email)"
}

class Employee(
override val id: String,
override val name: String,
override val email: String
) : User {
// No need to override formattedInfo as it has a default implementation
}

fun main() {
val employee = Employee("E7890", "Sarah Smith", "[email protected]")
println(employee.formattedInfo)
}

Output:

User(id=E7890, name=Sarah Smith, [email protected])

In this example:

  • The formattedInfo property has a default implementation using a custom getter
  • The Employee class doesn't need to override formattedInfo since it already has an implementation in the interface
  • The getter can use other properties from the interface

Backing Fields in Interface Properties

It's important to note that interface properties cannot have backing fields. This means you cannot use the field identifier in the property accessors.

kotlin
interface Incorrect {
var counter: Int = 0 // Error: Property in an interface cannot be initialized

var message: String
get() = field // Error: Interfaces cannot have backing fields
set(value) { field = value } // Error: Interfaces cannot have backing fields
}

Instead, you can use accessor methods without backing fields:

kotlin
interface Correct {
// Property with default getter implementation (no backing field)
val readOnlyProp: String
get() = "Default value"

// Abstract properties (no implementation)
var mutableProp: String
}

Different Ways to Implement Interface Properties

There are multiple ways to implement interface properties in classes:

1. Primary Constructor Parameters

kotlin
interface Vehicle {
val maxSpeed: Int
val manufacturer: String
}

class Car(
override val maxSpeed: Int,
override val manufacturer: String
) : Vehicle

2. Property Declaration in Class Body

kotlin
class Motorcycle : Vehicle {
override val maxSpeed = 200
override val manufacturer = "Honda"
}

3. Custom Getters

kotlin
class Truck(val model: String) : Vehicle {
override val maxSpeed: Int
get() = when(model) {
"Heavy" -> 80
"Medium" -> 100
else -> 120
}

override val manufacturer: String
get() = "Volvo"
}

Real-World Example: Component Architecture

Interface properties are particularly useful when designing component-based architectures. Here's a practical example of a UI component system:

kotlin
interface UIComponent {
val id: String
val isVisible: Boolean

// Default implementation
val cssClasses: List<String>
get() = listOf("ui-component")
}

interface Clickable {
// Event handler property
val onClick: () -> Unit
}

class Button(
override val id: String,
override val isVisible: Boolean = true,
override val onClick: () -> Unit,
val text: String
) : UIComponent, Clickable {
// Additional custom property
override val cssClasses: List<String>
get() = super<UIComponent>.cssClasses + listOf("button", "clickable")
}

fun main() {
val loginButton = Button(
id = "login-btn",
text = "Login",
onClick = { println("Button clicked! Performing login...") }
)

println("Button ID: ${loginButton.id}")
println("Button Text: ${loginButton.text}")
println("CSS Classes: ${loginButton.cssClasses.joinToString(", ")}")

// Simulate button click
loginButton.onClick()
}

Output:

Button ID: login-btn
Button Text: Login
CSS Classes: ui-component, button, clickable
Button clicked! Performing login...

In this example:

  1. We define two interfaces: UIComponent (with an abstract property and a property with default implementation) and Clickable (with an event handler property)
  2. The Button class implements both interfaces
  3. It overrides the default implementation of cssClasses and extends it with additional values
  4. We use super<UIComponent>.cssClasses to call the default implementation from the interface

Best Practices for Interface Properties

When working with properties in interfaces, consider these best practices:

  1. Use interface properties for contract definition: Define properties that all implementing classes must have
  2. Provide default implementations when possible: This reduces boilerplate in implementing classes
  3. Avoid complex logic in property getters: Keep default implementations simple and focused
  4. Remember the lack of backing fields: Use abstract properties when state storage is needed
  5. Prefer read-only properties: When possible, use val properties for better immutability

Summary

Kotlin interface properties provide a powerful way to define contracts that include both behavior (methods) and state (properties). They can be either abstract properties that must be implemented by classes or properties with default implementations.

Key points to remember:

  • Interfaces can declare abstract properties
  • Interfaces can provide default implementations for properties using custom getters
  • Interface properties cannot have backing fields
  • Properties can be implemented in multiple ways: via constructors, property declarations, or custom getters

Interface properties are particularly useful in component-based architectures, framework design, and when defining clear contracts between system components.

Exercises

  1. Create an interface Product with properties name, price, and a computed property formattedPrice that formats the price with a currency symbol.
  2. Design a Logger interface with a property logLevel and a method log() that uses this property.
  3. Implement a Shape interface with abstract properties for dimensions and a default property for calculating area.
  4. Create a class hierarchy for a media player application using interfaces with properties for media metadata.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)