Classes Workbook (Part 1)

Eleven short exercises on Kotlin classes — declarations, primary constructors, properties, init blocks, custom accessors, encapsulation, and inheritance.

Practice problems for A Kotlin Class Is Mostly Its Header. Each takes a minute or two. A few exercises auto-check: write the class so its method returns the right value, press Run, and hidden tests go green when you’re right and red (with a hint) when you’re not, all on JetBrains’ Kotlin server. Most are declaration exercises that stay attempt-then-reveal: fill in the TODO, press Run to compile it, then click Show answer to check your work. Nothing here is a trick question, just direct practice of the syntax from the lesson.

declarations and constructors

1. The smallest class

Declare an empty class Person and create an instance.

Try it — edit, then press Run fun main() { // TODO: declare a class Person, then create an instance and print it val p = Any() println(p) }
Show answer Hide answer
class Person

val p = Person()

No new keyword — you call the class like a function.

2. Properties in the header

Declare Point with a read-only x and y of type Int in its primary constructor.

Try it — edit, then press Run // TODO: give Point a read-only x and y of type Int in its primary constructor class Point fun main() { val p = Point() // then construct Point(3, 4) and print p.x and p.y println(p) }
Show answer Hide answer
class Point(val x: Int, val y: Int)

3. A default property value

Declare User(name: String) with a var active property that defaults to true.

Try it — edit, then press Run // TODO: add a var 'active' property that defaults to true class User(val name: String) fun main() { val u = User("Ada") println(u.name) // then also print u.active }
Show answer Hide answer
class User(val name: String, var active: Boolean = true)

4. An init block

Declare Account(balance: Int) that throws IllegalArgumentException at construction if balance is negative.

Try it — edit, then press Run // TODO: throw in an init block when balance is negative (use require) class Account(val balance: Int) fun main() { val a = Account(100) println(a.balance) // then try Account(-5) }
Show answer Hide answer
class Account(val balance: Int) {
    init {
        require(balance >= 0) { "balance must be non-negative" }
    }
}

5. Construct with named arguments

Given class Server(val host: String, val port: Int = 80, val secure: Boolean = false), construct one for "example.com" that is secure, keeping the default port.

Try it — edit, then press Run class Server(val host: String, val port: Int = 80, val secure: Boolean = false) fun main() { // TODO: construct a secure Server for "example.com", keeping the default port val s = Server("example.com") println(s.host + " " + s.port + " " + s.secure) }
Show answer Hide answer
Server(host = "example.com", secure = true)

6. A secondary constructor

Add a secondary constructor to Point that takes a single Int and uses it for both x and y.

Try it — edit, then press Run // TODO: add a secondary constructor taking one Int, used for both x and y class Point(val x: Int, val y: Int) fun main() { val p = Point(3, 4) println(p.x.toString() + ", " + p.y) // then try Point(5) }
Show answer Hide answer
class Point(val x: Int, val y: Int) {
    constructor(both: Int) : this(both, both)
}

properties and encapsulation

7. A computed property

Add a read-only area property to Rectangle(val w: Int, val h: Int), computed from its sides, so Rectangle(3, 4).area is 12.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun computesArea() { Assert.assertEquals("area is w * h", 12, Rectangle(3, 4).area) Assert.assertEquals("area of a unit square", 1, Rectangle(1, 1).area) } } //sampleStart class Rectangle(val w: Int, val h: Int) { val area: Int get() = 0 // TODO: compute the area from w and h } //sampleEnd
Show answer Hide answer
class Rectangle(val w: Int, val h: Int) {
    val area: Int
        get() = w * h
}

8. Read-public, write-private

Declare Counter with an Int property value that anyone can read but only the class can change, plus an increment() method.

Try it — edit, then press Run // TODO: make 'value' read-public but write-private, and add increment() class Counter { var value: Int = 0 } fun main() { val c = Counter() println(c.value) // then call increment() a few times and print again }
Show answer Hide answer
class Counter {
    var value: Int = 0
        private set

    fun increment() { value++ }
}

inheritance

9. Classes are final by default

Animal has an open speak() returning "...". Override speak() in Dog so it returns "woof".

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun speaks() { Assert.assertEquals("Animal speaks the default", "...", Animal().speak()) Assert.assertEquals("Dog overrides to woof", "woof", Dog().speak()) } } //sampleStart open class Animal { open fun speak() = "..." } // TODO: override speak() so a Dog returns "woof" class Dog : Animal() //sampleEnd
Show answer Hide answer
open class Animal {
    open fun speak() = "..."
}

class Dog : Animal() {
    override fun speak() = "woof"
}

A class and its members must be marked open before they can be extended or overridden.

10. An abstract class

Shape is abstract with an abstract area(): Double. Implement area() in Circle as Math.PI * r * r.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun circleArea() { Assert.assertEquals("area is pi times r squared", Math.PI * 2.0 * 2.0, Circle(2.0).area(), 0.0001) } } //sampleStart abstract class Shape { abstract fun area(): Double } class Circle(val r: Double) : Shape() { override fun area(): Double = 0.0 // TODO: Math.PI * r * r } //sampleEnd
Show answer Hide answer
abstract class Shape {
    abstract fun area(): Double
}

class Circle(val r: Double) : Shape() {
    override fun area() = Math.PI * r * r
}

11. Make it extendable

This doesn’t compile. Fix the declaration so Derived can extend Base:

class Base
class Derived : Base()
Try it — edit, then press Run // TODO: make Base extendable so Derived can subclass it class Base class Derived // then: class Derived : Base() fun main() { val d = Derived() println(d) }
Show answer Hide answer
open class Base
class Derived : Base()

A class is final by default; open is what lets it be extended.


Going deeper: backing fields and factories

12. A validating setter

Give celsius a custom setter that rejects anything below absolute zero (-273.15) and otherwise stores the value. Use the backing field.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun setter() { val t = Temperature(20.0) t.celsius = 25.0 Assert.assertEquals(25.0, t.celsius, 0.001) try { t.celsius = -300.0; Assert.fail("should reject") } catch (e: IllegalArgumentException) { /* expected */ } } } //sampleStart class Temperature(c: Double) { var celsius: Double = c // TODO: reject values below -273.15, otherwise store via the backing field } //sampleEnd
Show answer Hide answer
class Temperature(c: Double) {
    var celsius: Double = c
        set(value) {
            require(value >= -273.15) { "below absolute zero" }
            field = value
        }
}

Assign field, not celsius — writing celsius = value would call the setter again, forever.

13. Force creation through a factory

Make Email’s constructor private and add a companion factory of that returns an Email?, non-null only when the input contains @.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun factory() { Assert.assertEquals("[email protected]", Email.of("[email protected]")?.address) Assert.assertNull(Email.of("nonsense")) } } //sampleStart class Email(val address: String) { // TODO: make the constructor private; add companion fun of(raw): Email? } //sampleEnd
Show answer Hide answer
class Email private constructor(val address: String) {
    companion object {
        fun of(raw: String): Email? = if ("@" in raw) Email(raw) else null
    }
}

A private constructor plus a companion factory is Kotlin’s validated-construction pattern.

14. Construction order

Property initializers and init blocks run in source order, interleaved. Given a property a, then an init, then property b, then another init, what order do they run in? Reveal to check.

Show answer Hide answer

Top to bottom as written: a’s initializer, then the first init, then b’s initializer, then the second init. That’s why an init block can only reference properties declared above it — anything below is still uninitialized.


Back to the lesson, A Kotlin Class Is Mostly Its Header, or on to the next one: class types.

Comments