Skip to content
Educora
Advanced24 min7 / 14

Classes, data classes, inheritance and interfaces

Bundle data and behaviour together: classes, constructors, data classes, `object` and companion objects, inheritance with `open`, and interfaces.

Check yourself
In this lesson you will learn
  • Write a class with a primary constructor, properties and methods
  • Use what a data class gives you: toString, == and copy
  • Explain what object and companion objects are for
  • Build inheritance with open/override and implement an interface

You are writing a class register: every student has a name, a grade level and scores, and you need to add new scores and calculate averages. With 30 students, separate variables quickly turn into a mess. Object-oriented programming offers another way: bundle the data and the functions that work with it into one place — an object. Kotlin does this with very short syntax.

Class, constructor and properties

A class is a blueprint for objects, and an object is a concrete instance built from it. In Kotlin the primary constructor is written in parentheses right after the class name: parameters declared there with val or var immediately become the class's properties. Functions inside a class are called methods. An object is created without the word new, like a function call: Student("Aysel", 8).

Kotlin
class Student(val name: String, var grade: Int) {
    val scores = mutableListOf<Int>()

    fun addScore(score: Int) {
        scores.add(score)
    }

    fun average() = scores.average()

    fun introduce() = "I am $name, grade $grade"
}

fun main() {
    val aysel = Student("Aysel", 8)
    aysel.addScore(5)
    aysel.addScore(4)
    println(aysel.introduce())
    println(aysel.scores)
    println(aysel.average())
    aysel.grade = 9
    println(aysel.introduce())
}
Expected output
I am Aysel, grade 8
[5, 4]
4.5
I am Aysel, grade 9
grade is a var, so it can change; name is a val and cannot.

Data classes

If you print an object of an ordinary class, you see cryptic text like Student@1b6d3586, and two objects with the same data are not equal with ==. Very often a class exists just to hold data — a user, a product, an order. In such cases, write data in front of the class, and Kotlin automatically generates a readable toString, an == that compares contents, copy, and destructuring.

Kotlin
data class User(val name: String, val age: Int)

fun main() {
    val u1 = User("Aysel", 15)
    val u2 = User("Aysel", 15)
    println(u1)
    println(u1 == u2)
    val older = u1.copy(age = 16)
    println(older)
    val (name, age) = older
    println("$name is $age")
}
Expected output
User(name=Aysel, age=15)
true
User(name=Aysel, age=16)
Aysel is 16

object and companion objects

Sometimes a program needs exactly one instance of something: the app settings or a database connection. For this, write object instead of class — it both defines the class and creates its only object. A companion object inside a class holds functions and constants that are called on the class name itself, without creating an object, for example “factory” functions that build objects.

Kotlin
object Settings {
    var language = "az"
    fun describe() = "Language: $language"
}

class Temperature(val celsius: Double) {
    companion object {
        const val WATER_BOILS = 100.0
        fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
    }
}

fun main() {
    println(Settings.describe())
    Settings.language = "en"
    println(Settings.describe())
    val t = Temperature.fromFahrenheit(212.0)
    println(t.celsius)
    println(t.celsius == Temperature.WATER_BOILS)
}
Expected output
Language: az
Language: en
100.0
true

Inheritance: open and override

Inheritance lets you build a new class on top of an existing one: the child class gets the parent's properties and methods. In Kotlin classes are closed by default — to inherit from one, the parent class must be marked open. A method that a child may change must also be open, and the child writes its new version with override. The parent is given after a colon: class Dog(name: String) : Animal(name).

Kotlin
open class Animal(val name: String) {
    open fun sound() = "..."
    fun introduce() = "$name says ${sound()}"
}

class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof!"
}

class Cat(name: String) : Animal(name) {
    override fun sound() = "Meow!"
}

fun main() {
    val pets = listOf(Dog("Rex"), Cat("Mestan"), Animal("Nemo"))
    for (pet in pets) {
        println(pet.introduce())
    }
}
Expected output
Rex says Woof!
Mestan says Meow!
Nemo says ...
The same introduce() calls each object's own version of sound() — this is called polymorphism.

Interfaces

An interface is a contract that describes what classes must be able to do: every class that implements its properties and methods belongs to the interface's type. An interface can also contain a ready-made version of a method. A class can inherit from only one class, but it can implement any number of interfaces.

Kotlin
interface Shape {
    val name: String
    fun area(): Double
    fun describe() = "$name: area ${area()}"
}

class Rectangle(val width: Double, val height: Double) : Shape {
    override val name = "Rectangle"
    override fun area() = width * height
}

class Circle(val radius: Double) : Shape {
    override val name = "Circle"
    override fun area() = 3.14 * radius * radius
}

fun main() {
    val shapes: List<Shape> = listOf(Rectangle(3.0, 4.0), Circle(2.0))
    shapes.forEach { println(it.describe()) }
    println(shapes.maxBy { it.area() }.name)
}
Expected output
Rectangle: area 12.0
Circle: area 12.56
Circle
For simplicity π ≈ 3.14 is used; the exact value is kotlin.math.PI.
DeclarationWhen to use it
classordinary objects with data and behaviour
data classobjects that mainly hold data
objectsomething that has exactly one instance
open classa base for child classes
interfacea shared ability of several classes (a contract)

Key points

  • class Student(val name: String) — the primary constructor declares the property directly; objects are created without new.
  • A data class automatically gets toString, content-based ==, copy and destructuring.
  • object creates a single instance, and a companion object holds members called on the class name.
  • Classes and methods are closed by default: inheritance needs open, and replacing a method needs override.
  • A class inherits from one parent class but can implement several interfaces.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
For data class Point(val x: Int, val y: Int), what does println(Point(1, 2)) print?