Skip to main content

Working with Kotlin Object-Oriented Programming

1. Creating Classes

Feature Description Example
class declaration Basic class definition using class keyword
class Person {
    var name: String = ""
}
primary constructor Constructor defined in the class header class Person(val name: String, var age: Int)
secondary constructor Additional constructors using constructor
class Person {
    constructor(name: String) {
        this.name = name
    }
}
data class Automatically generates equals(), hashCode(), and toString() data class User(val id: Int, val name: String)
sealed class Restricts subclassing to a specific set of classes
sealed class Result<T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Error(val message: String) : Result<T>()
}

Example: Basic Class Usage

class Student {
    var name: String = ""
    var grade: Int = 0

    fun displayInfo() {
        println("Name: $name, Grade: $grade")
    }
}

val student = Student()
student.name = "Alice"
student.grade = 90
student.displayInfo()

2. Implementing Constructors

Constructor Type Description Example
Primary Constructor Defined in the class header. Can include properties and initialization code.
class Person(val name: String, var age: Int) {
    init {
        println("Creating person: $name")
    }
}
Secondary Constructor Defined using the constructor keyword. Must delegate to the primary constructor.
class Person(val name: String, var age: Int) {
    constructor(name: String) : this(name, 0)
}
No-argument Constructor Primary constructor with no parameters. Can be omitted if properties are initialized directly.
class Person {
    var name: String = ""
    var age: Int = 0
}

Example: Using Primary Constructor with Initialization

class Student(val id: Int, val name: String) {
    init {
        println("Student created with ID: $id")
    }
}

val student = Student(101, "Alice")

Example: Secondary Constructor with Delegation

class Rectangle(val width: Double, val height: Double) {
    constructor(side: Double) : this(side, side)
}

val square = Rectangle(5.0)
val rectangle = Rectangle(4.0, 6.0)

3. Working with Properties

Property Type Description Syntax Notes
Delegated Properties Properties whose behavior is delegated to another object val prop: Type by Delegate() Use by keyword to delegate property access
Mutable Properties Properties that can be changed after initialization var name: String = "default" Use var for mutable properties
Read-Only Properties Properties that can only be read, not modified val name: String = "default" Use val for immutable properties
Backing Fields Hidden fields that store property values private var _value: Int = 0 Use _value to store actual value
Computed Properties Properties whose value is calculated on access val fullName: String get() = "$firstName $lastName" Use get() to define computed logic

Example: Delegated Properties

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("initial") { _, old, new ->
        println("Name changed from $old to $new")
    }
}

fun main() {
    val user = User()
    user.name = "Alice" // Prints: Name changed from initial to Alice
}

Example: Computed Properties

class Rectangle(val width: Double, val height: Double) {
    val area: Double
        get() = width * height

    val perimeter: Double
        get() = 2 * (width + height)
}

fun main() {
    val rect = Rectangle(5.0, 3.0)
    println("Area: ${rect.area}") // Area: 15.0
    println("Perimeter: ${rect.perimeter}") // Perimeter: 16.0
}

Example: Mutable vs Read-Only Properties

class Person {
    var age: Int = 25 // Mutable property
        set(value) {
            if (value >= 0) field = value
        }

    val fullName: String = "John Doe" // Read-only property
}

fun main() {
    val person = Person()
    person.age = 30 // Valid
    // person.fullName = "Jane Doe" // Error: Val cannot be reassigned
}
Note: Kotlin's property system provides powerful features like delegation and computed properties that simplify common patterns in object-oriented programming.

4. Implementing Inheritance

Feature Description Example
Inheritance in Kotlin Allows a class to inherit properties and methods from another class. class Animal { fun speak() { println("Animal speaks") } }
Extending a Class Use open keyword to allow inheritance. open class Animal { ... }
Subclass Declaration Use class SubClass : SuperClass class Dog : Animal() { ... }
Overriding Methods Use override to redefine methods from parent class. override fun speak() { println("Dog barks") }
Calling Superclass Methods Use super.methodName() override fun speak() { super.speak(); println("Dog barks") }

Example: Basic Inheritance

open class Animal {
    open fun speak() {
        println("Animal speaks")
    }
}

class Dog : Animal() {
    override fun speak() {
        super.speak()
        println("Dog barks")
    }
}

fun main() {
    val dog = Dog()
    dog.speak()
}
Note: In Kotlin, classes are final by default. Use open to enable inheritance.
Warning: If a method is not marked open, it cannot be overridden in subclasses.

5. Using Abstract Classes

Feature Description Example
abstract class Defines a class that cannot be instantiated directly and may contain abstract methods.
abstract class Animal {
    abstract fun makeSound()
    fun sleep() {
        println("Sleeping...")
    }
}
abstract method A method declared without implementation in an abstract class. abstract fun makeSound()
concrete method A method with implementation in an abstract class that can be inherited.
fun sleep() {
    println("Sleeping...")
}
inheritance from abstract class Subclasses must implement all abstract methods.
class Dog : Animal() {
    override fun makeSound() {
        println("Woof!")
    }
}

Example: Implementing Abstract Class

abstract class Shape {
    abstract fun area(): Double
    open fun description(): String = "This is a shape"
}

class Circle(radius: Double) : Shape() {
    override fun area(): Double = Math.PI * radius * radius
    override fun description(): String = "This is a circle"
}

val circle = Circle(5.0)
println(circle.area()) // 78.53981633974483
println(circle.description()) // This is a circle
Note: Abstract classes can have constructors, properties, and methods with implementations.

6. Creating Interfaces

In Kotlin, interfaces are similar to Java interfaces but with some additional features like default implementations and extension functions.

Feature Description Example
Basic Interface Defines a contract with methods that implementing classes must provide. interface Drawable {<br> fun draw()<br>}
Default Implementation Allows methods to have a default implementation using fun. interface Logger {<br> fun log(message: String) {<br> println("Log: $message")<br> }<br>}
Abstract Methods Methods without implementation that must be overridden. interface Shape {<br> fun area(): Double<br>}
Properties in Interface Interfaces can declare properties, which must be implemented by classes. interface Vehicle {<br> val brand: String<br>}
Companion Object Interfaces can have companion objects for static-like functionality. interface Constants {<br> companion object {<br> const val MAX_SPEED = 200<br> }<br>}

Example: Implementing an Interface

interface Drawable {<br>    fun draw()<br>}<br><br>class Circle : Drawable {<br>    override fun draw() {<br>        println("Drawing a circle")<br>    }<br>}
Note: A class can implement multiple interfaces using a comma-separated list.

Example: Multiple Interface Implementation

interface Flyable {<br>    fun fly()<br>}<br><br>interface Swimmable {<br>    fun swim()<br>}<br><br>class Duck : Flyable, Swimmable {<br>    override fun fly() {<br>        println("Duck flying")<br>    }<br><br>    override fun swim() {<br>        println("Duck swimming")<br>    }<br>}

Key Points About Interfaces

  • Interfaces define contracts for classes to implement.
  • They support default implementations and abstract methods.
  • Classes can implement multiple interfaces.
  • Properties in interfaces must be implemented by implementing classes.

7. Implementing Visibility Modifiers

Modifier Description Access Scope Usage Example
public Default modifier; accessible from anywhere Any class, package, or module class MyClass { public fun doSomething() { } }
private Accessible only within the same class Same class only class MyClass { private fun doSomething() { } }
protected Accessible within the same class and subclasses Same class and subclasses open class Parent { protected fun doSomething() { } }
internal Accessible within the same module Same module only class MyClass { internal fun doSomething() { } }
Note: In Kotlin, internal visibility is more restrictive than Java's default package-private access.

Example: Using Visibility Modifiers

class Person {
    private val id: String = "123"
    internal var name: String = "John"
    public fun getId(): String {
        return id
    }
}

class Employee : Person() {
    fun printName() {
        println(name) // Valid: internal property accessible in same module
        // println(id) // Invalid: private property not accessible
    }
}

8. Using Data Classes

Feature Description Example
data class declaration Declares a class that primarily holds data data class Person(val name: String, val age: Int)
copy() Creates a copy of the instance with specified fields changed
val person1 = Person("Alice", 30)
val person2 = person1.copy(age = 31)
componentN() Enables destructuring declarations val (name, age) = person
toString() Automatically generated to show object state Person(name=Alice, age=30)
equals() / hashCode() Automatically generated for value-based comparison person1 == person2

Example: Data Class Usage

data class Product(
    val id: Int,
    val name: String,
    val price: Double
)

val product1 = Product(1, "Laptop", 999.99)
val product2 = product1.copy(name = "Gaming Laptop")
val (id, name, price) = product1

println(product1) // Product(id=1, name=Laptop, price=999.99)
println(product2) // Product(id=1, name=Gaming Laptop, price=999.99)
Note: Data classes are immutable by default when using val properties.
Warning: Do not use var properties in data classes unless you intend to mutate state, as it can lead to unexpected behavior.

9. Implementing Enum Classes

Enum classes in Kotlin are a special type of class used to define a fixed set of constants. They are similar to enums in other languages but offer more flexibility and features.

Feature Description Example
Basic Enum Declaration Defines a simple enum with constants. enum class Color { RED, GREEN, BLUE }
Enum with Properties Enums can have properties and methods. enum class Direction(val degree: Int) { NORTH(0), EAST(90), SOUTH(180), WEST(270) }
Enum with Methods Enums can include methods and override functions. enum class Planet(val mass: Double, val radius: Double) { EARTH(5.972e24, 6371e3), MARS(6.39e23, 3389.5e3); fun surfaceGravity(): Double = 6.673e-11 * mass / (radius * radius) }
Using Enum Values Accessing enum constants and their properties. val color = Color.RED<br>val direction = Direction.NORTH<br>println(direction.degree) // Outputs: 0

Example: Using Enum in Switch-like Logic

fun describeColor(color: Color): String = when (color) {
    Color.RED -> "Red is for danger"
    Color.GREEN -> "Green is for go"
    Color.BLUE -> "Blue is for calm"
}

Example: Enum with Constructor Parameters

enum class Size(val width: Int, val height: Int) {
    SMALL(100, 100),
    MEDIUM(200, 200),
    LARGE(300, 300)
}
Note: Enum constants are implicitly public, final, and static. You can't instantiate them directly.
Warning: Unlike Java, Kotlin enums do not support valueOf or values methods directly. Use enumValues<T>() and enumValueOf<T>() instead.

Key Points About Kotlin Enums

  • Enums are classes, not primitive types.
  • They support properties and methods just like regular classes.
  • Each enum constant is an instance of the enum class.
  • Enums can be used in when expressions for exhaustive matching.

10. Working with Sealed Classes

Sealed classes in Kotlin are a special kind of class that restricts the inheritance hierarchy to a specific set of subclasses. They are useful for modeling a restricted set of types, such as states in a state machine or different types of responses from an API.

Feature Description Notes
Sealed Class Declaration sealed class Shape Cannot be instantiated directly. Subclasses must be declared within the same file or in a sealed hierarchy.
Subclass Declaration class Circle(radius: Double) : Shape() Subclasses must be declared in the same file or as nested classes within the sealed class.
Companion Object companion object Can be used to define factory methods or utility functions for the sealed class.

Example: Sealed Class Usage

sealed class Result<T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Error(val message: String) : Result<T>()
}

fun processResult(result: Result<String>) {
    when (result) {
        is Result.Success -> println("Success: ${result.value}")
        is Result.Error -> println("Error: ${result.message}")
    }
}
Note: The when expression must handle all possible subclasses of a sealed class, or use an else clause to handle any unexpected cases.

Key Points About Sealed Classes

  • They are not open by default, meaning they cannot be extended outside their defining scope.
  • They are not instantiable; only their subclasses can be instantiated.
  • They are particularly useful for modeling a closed set of types, such as state machines or API responses.
  • They support when expressions with exhaustive checks, ensuring all cases are handled.

11. Using Nested and Inner Classes

Class Type Description Key Features
Nested Class A class defined inside another class without referencing the outer class instance Declared with inner keyword, can access outer class members
Inner Class A class defined inside another class that has access to the outer class instance Uses inner keyword, can access outer class members directly

Example: Nested and Inner Classes

class OuterClass {
    private val outerProperty = "I'm from outer"

    class NestedClass {
        fun display() {
            println("Nested class accessing: $outerProperty") // ❌ Not possible
        }
    }

    inner class InnerClass {
        fun display() {
            println("Inner class accessing: $outerProperty") // ✅ Possible
        }
    }
}

fun main() {
    val outer = OuterClass()
    val nested = OuterClass.NestedClass()
    val inner = outer.InnerClass()

    nested.display() // Output: Nested class accessing: null
    inner.display()  // Output: Inner class accessing: I'm from outer
}
Note: Nested classes do not have access to the outer class instance, whereas inner classes do.

12. Implementing Delegation

Delegation in Kotlin allows you to delegate the implementation of an interface or a class to another object. This is achieved using the by keyword.

Feature Description Example
Delegation Syntax Uses by keyword to delegate implementation class MyClass<T>(delegate: T) : T by delegate
Interface Delegation Implementing an interface by delegating to another object interface MyInterface { fun doSomething() }<br>class MyClass(delegate: MyInterface) : MyInterface by delegate
Property Delegation Using by with property delegates class MyClass {<br> var prop: String by Delegate()

Example: Interface Delegation

interface Drawable {<br>    fun draw(): Unit<br>}<br><br>class Canvas : Drawable {<br>    override fun draw() {<br>        println("Drawing on canvas")<br>    }<br>}<br><br>class Shape(delegatedDrawable: Drawable) : Drawable by delegatedDrawable<br><br>fun main() {<br>    val shape = Shape(Canvas())<br>    shape.draw() // Output: Drawing on canvas<br>}

Example: Property Delegation

import kotlin.properties.ReadWriteProperty<br>import kotlin.reflect.KProperty<br><br>class Delegate<T>(private var value: T) : ReadWriteProperty<Any?, T> {<br>    override fun getValue(thisRef: Any?, property: KProperty<*>): T = value<br>    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {<br>        this.value = value<br>    }<br>}<br><br>class MyClass {<br>    var prop: String by Delegate("initial")<br>}<br><br>fun main() {<br>    val obj = MyClass()<br>    println(obj.prop) // Output: initial<br>    obj.prop = "updated"<br>    println(obj.prop) // Output: updated<br>}
Note: Delegation in Kotlin is a powerful feature that allows for clean and readable code by offloading responsibilities to other objects.

Was this article helpful?