RizTech Academy logo
RizTech Academy
Classes and ObjectsLesson 1 of 525 min

Classes, constructors and properties

A class models a thing in your program — a customer, an order, a book — bundling the data that describes it with the behaviour that acts on it. Kotlin's class syntax is dramatically shorter than Java's, and this lesson covers the shape of a class, its constructor, and its properties.

The shortest useful class

class Customer(val name: String, val email: String)

That one line is a complete, usable class. It declares a Customer with two read-only properties, name and email, and a constructor that takes them. Compare with the equivalent Java — a class declaration, two private fields, a constructor that assigns them, and two getters, easily fifteen lines. Kotlin's class Customer(val name: String, val email: String) says the same thing with the noise removed.

Create an instance with no new keyword — you just call the class like a function:

val customer = Customer("Kavita", "kavita@example.com")
println(customer.name)         // Kavita
println(customer.email)        // kavita@example.com

The primary constructor and properties in one

That header — (val name: String, val email: String) — is the primary constructor, and the val keyword on each parameter is doing something powerful: it declares a property and the constructor parameter that initialises it, together. Break it down:

  • val name: String in the constructor: a read-only property name, set from the argument.
  • var age: Int: a mutable property — the object's age can change after creation.
  • A plain parameter with no val/var (class C(name: String)): a constructor argument that is not kept as a property — usable only during construction.

So the choice of val, var, or nothing on a constructor parameter decides whether it becomes a read-only property, a mutable one, or just a construction-time value. As everywhere, prefer val — an object whose properties do not change is easier to reason about.

Properties with defaults, and named arguments

Constructor parameters can have defaults, and you construct with named arguments — the functions module, applied to objects:

class Customer(
    val name: String,
    val email: String,
    val city: String = "Pune",
    val isPremium: Boolean = false
)

val a = Customer("Kavita", "kavita@example.com")
val b = Customer(
    name = "Ravi",
    email = "ravi@example.com",
    isPremium = true            // skip city (defaulted), set only what differs
)

This is why the named-arguments lesson mattered: constructing an object with several fields reads like filling in a form, each field labelled, impossible to get the order wrong. This is the everyday way you will build objects in Kotlin.

init blocks — running code at construction

When you need to do something when an object is created — validate, compute, log — use an init block:

class Customer(val name: String, val email: String) {
    init {
        require(name.isNotBlank()) { "name must not be blank" }
        require("@" in email) { "email must contain @" }
    }
}

val ok = Customer("Kavita", "kavita@example.com")     // fine
val bad = Customer("", "kavita@example.com")          // throws: name must not be blank

init runs as part of construction, after the properties are set. require(condition) { message } throws an IllegalArgumentException with your message if the condition is false — the idiomatic way to validate constructor arguments so an invalid object can never exist. An object that validates itself at birth is one you never have to check again.

Computed properties and behaviour

Properties can be computed rather than stored, using a custom getter, and classes hold methods (functions that act on the object):

class Order(val itemPaise: Int, val qty: Int) {
    // a computed property — no stored value, recalculated on each access
    val totalPaise: Int
        get() = itemPaise * qty

    // a method
    fun formatTotal(): String = "₹${totalPaise / 100}.${totalPaise % 100}"
}

val order = Order(itemPaise = 15000, qty = 3)
println(order.totalPaise)        // 45000
println(order.formatTotal())     // ₹450.0

totalPaise has a get() and no stored value — every time you read it, it recomputes from itemPaise and qty. This is the right way to expose something derived from other properties: it can never fall out of sync, because there is nothing to keep in sync (the same idea as a generated column in the database course). formatTotal() is a method — behaviour that belongs to the order.

Secondary constructors, briefly

Occasionally you want an alternative way to construct an object; a secondary constructor provides it:

class Customer(val name: String, val email: String) {
    constructor(name: String) : this(name, "$name@example.com")   // delegates to the primary
}

val c = Customer("kavita")     // email defaults to kavita@example.com

In practice you rarely need these — default arguments on the primary constructor usually do the job more cleanly, and idiomatic Kotlin prefers them. Reach for a secondary constructor only when you need genuinely different construction logic that defaults cannot express.

Check your work

What a one-line class with val properties gives you. A full class with read-only properties and a constructor — no fields, getters or boilerplate.

How you create an instance. By calling the class like a function — no new.

What val/var/nothing on a constructor parameter means. Read-only property, mutable property, or a construction-only argument that is not kept.

Why constructing with named arguments is idiomatic. Many-field objects read like a labelled form and cannot be built in the wrong order.

What an init block is for. Running code at construction — typically validation with require.

What a computed property is. A property with a get() and no stored value, recalculated on each access — right for derived values.

What a method is. A function belonging to the class, acting on the object.

Why default arguments usually beat a secondary constructor. They express alternative construction more cleanly; reach for a secondary constructor only for genuinely different logic.

Practice

  1. Write class Customer(val name: String, val email: String) and create one. Print both properties.
  2. Add a defaulted city and isPremium, and construct an instance setting only isPremium by name.
  3. Change one property to var, reassign it after creation, and confirm it changed. Change it back to val and watch the reassignment fail.
  4. Add an init block that validates the email contains @. Construct a valid and an invalid one; read the exception for the invalid.
  5. Write an Order class with a computed totalPaise property. Change qty (make it a var) and confirm totalPaise updates without you setting it.
  6. Add a formatTotal() method to the order.
  7. Add a secondary constructor, then rewrite the same convenience using a default argument and decide which you prefer.

Official documentation

Next: data classes — equality, copying, and printing for free.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship