Kotlin Abstract Keyword
last modified April 19, 2025
Kotlin's abstract keyword is used to create abstract classes and members that cannot be instantiated directly. Abstract members must be implemented by concrete subclasses. This tutorial explores abstract classes and members in depth with practical examples.
Basic Definitions
The abstract keyword in Kotlin declares abstract classes or
members. Abstract classes cannot be instantiated directly. They serve as base
classes for concrete implementations. Abstract members have no implementation
and must be overridden in subclasses.
Basic Abstract Class
An abstract class is declared with the abstract keyword. It can contain both abstract and concrete members. Abstract classes cannot be instantiated directly.
package com.zetcode
abstract class Shape {
abstract fun area(): Double
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double {
return Math.PI * radius * radius
}
}
fun main() {
val circle = Circle(5.0)
println("Area: ${circle.area()}") // Output: Area: 78.53981633974483
}
Here we define an abstract Shape class with an abstract area() function. The Circle class inherits from Shape and provides a concrete implementation of area(). We then create a Circle instance and calculate its area.
Abstract Properties
Abstract classes can have abstract properties in addition to methods. These properties must be implemented by concrete subclasses. They can be either val or var properties.
package com.zetcode
abstract class Animal {
abstract val sound: String
abstract var age: Int
}
class Dog : Animal() {
override val sound = "Woof"
override var age = 0
}
fun main() {
val dog = Dog()
println("Sound: ${dog.sound}, Age: ${dog.age}") // Output: Sound: Woof, Age: 0
dog.age = 3
println("New age: ${dog.age}") // Output: New age: 3
}
The Animal class declares abstract sound (val) and age (var) properties. The Dog class provides concrete implementations. We can read both properties and modify the mutable age property.
Abstract Class with Concrete Members
Abstract classes can contain both abstract and concrete members. Concrete members provide default implementations that subclasses can use or override.
package com.zetcode
abstract class Vehicle {
abstract val maxSpeed: Double
fun start() {
println("Vehicle started")
}
abstract fun stop()
}
class Car : Vehicle() {
override val maxSpeed = 200.0
override fun stop() {
println("Car stopped")
}
}
fun main() {
val car = Car()
car.start() // Output: Vehicle started
println("Max speed: ${car.maxSpeed}") // Output: Max speed: 200.0
car.stop() // Output: Car stopped
}
The Vehicle class has both abstract (maxSpeed, stop()) and concrete (start()) members. The Car class inherits from Vehicle and implements the abstract members while using the concrete start() method as-is.
Multiple Abstract Members
An abstract class can have multiple abstract members. Subclasses must implement all of them. This enforces a consistent interface across all implementations.
package com.zetcode
abstract class Employee {
abstract val name: String
abstract val salary: Double
abstract fun work()
abstract fun takeBreak()
}
class Developer : Employee() {
override val name = "John Doe"
override val salary = 75000.0
override fun work() {
println("Writing code...")
}
override fun takeBreak() {
println("Taking coffee break")
}
}
fun main() {
val dev = Developer()
println("${dev.name} earns ${dev.salary}") // Output: John Doe earns 75000.0
dev.work() // Output: Writing code...
dev.takeBreak() // Output: Taking coffee break
}
The Employee abstract class defines four abstract members. The Developer class provides implementations for all of them. This ensures all Employee subclasses have consistent behavior while allowing implementation details to vary.
Abstract Class Inheritance
Abstract classes can inherit from other abstract classes. The child abstract class doesn't need to implement all parent abstract members. It can add new abstract members.
package com.zetcode
abstract class Person {
abstract val name: String
abstract fun greet()
}
abstract class Student : Person() {
abstract val studentId: Int
abstract fun study()
}
class CollegeStudent : Student() {
override val name = "Alice"
override val studentId = 12345
override fun greet() {
println("Hi, I'm $name")
}
override fun study() {
println("Studying hard...")
}
}
fun main() {
val student = CollegeStudent()
student.greet() // Output: Hi, I'm Alice
println("ID: ${student.studentId}") // Output: ID: 12345
student.study() // Output: Study hard...
}
Person is an abstract class with abstract members. Student inherits from Person and adds new abstract members. CollegeStudent implements all abstract members from both classes. This shows how abstract classes can build on each other.
Abstract Class with Interface
Abstract classes can implement interfaces. They can provide implementations for some interface members while leaving others abstract. This offers flexibility in design.
package com.zetcode
interface Drawable {
fun draw()
fun resize(scale: Double)
}
abstract class Shape : Drawable {
abstract val color: String
override fun resize(scale: Double) {
println("Resizing by scale $scale")
}
}
class Circle : Shape() {
override val color = "Red"
override fun draw() {
println("Drawing a $color circle")
}
}
fun main() {
val circle = Circle()
circle.draw() // Output: Drawing a Red circle
circle.resize(1.5) // Output: Resizing by scale 1.5
}
Shape is an abstract class implementing Drawable interface. It provides concrete implementation for resize() but leaves draw() abstract. Circle implements the remaining abstract members. This shows partial implementation of interfaces.
Abstract Class as Parameter Type
Abstract classes can be used as parameter types in functions. This allows polymorphic behavior where any concrete subclass can be passed as an argument.
package com.zetcode
abstract class Logger {
abstract fun log(message: String)
}
class ConsoleLogger : Logger() {
override fun log(message: String) {
println("CONSOLE: $message")
}
}
class FileLogger : Logger() {
override fun log(message: String) {
println("FILE: $message (writing to file)")
}
}
fun process(logger: Logger, message: String) {
logger.log(message)
}
fun main() {
val consoleLogger = ConsoleLogger()
val fileLogger = FileLogger()
process(consoleLogger, "Test message") // Output: CONSOLE: Test message
process(fileLogger, "Important data") // Output: FILE: Important data (writing to file)
}
The process function accepts any Logger subclass. We pass different
logger implementations (ConsoleLogger and FileLogger) that each handle logging
differently. This demonstrates polymorphism with abstract classes.
Best Practices for Abstract Classes
- Use for common behavior: Abstract classes are ideal when multiple classes share common behavior that can be implemented once.
- Favor interfaces for simple contracts: For simple method contracts without shared implementation, prefer interfaces.
- Document abstract members: Clearly document the purpose and expected behavior of abstract members.
- Keep abstract classes focused: Abstract classes should have a single, well-defined responsibility.
- Consider sealed classes: For restricted class hierarchies, consider sealed classes instead of abstract classes.
Source
Kotlin Abstract Classes Documentation
This tutorial covered Kotlin's abstract keyword in depth, showing
how to create abstract classes and members. We explored various scenarios
including properties, inheritance, and interface implementation. Proper use of
abstract classes can create flexible and maintainable class hierarchies.
Author
List all Kotlin tutorials.