Operator Overloading Workbook
Ten short exercises on Kotlin operator overloading — plus, get and set, invoke, contains, compareTo, compound assignment, unaryMinus, and making a type iterable.
Practice problems for Operator Overloading, Kept on a Leash. Each takes a minute or two, and every one auto-checks: implement the operator in the editor and press Run — hidden tests exercise it through the real operator (a + b, p in rect, for (x in …)) and go green when you’re right, red (with a hint) when you’re not, all on JetBrains’ Kotlin server. Click Show answer to compare. Nothing here is a trick question, just direct practice of the syntax from the lesson.
arithmetic and indexing
1. Add two vectors
Give data class Vec(val x: Int, val y: Int) a + operator that adds component-wise, so Vec(1, 2) + Vec(3, 4) is Vec(4, 6).
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun plus() {
Assert.assertEquals("add vectors component-wise",
Vec(4, 6), Vec(1, 2) + Vec(3, 4))
}
}
//sampleStart
data class Vec(val x: Int, val y: Int) {
operator fun plus(other: Vec) =
Vec(0, 0) // TODO: add component-wise
}
//sampleEnd Show answer Hide answer
data class Vec(val x: Int, val y: Int) {
operator fun plus(other: Vec) = Vec(x + other.x, y + other.y)
}
Vec(1, 2) + Vec(3, 4) // Vec(4, 6)a + b compiles to a.plus(b).
2. Index into a grid
Give Grid a get(x, y) so grid[x, y] reads a cell.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun get() {
val grid = Grid(intArrayOf(1, 2, 3, 4), width = 2)
Assert.assertEquals("read cells[y * width + x]",
2, grid[1, 0])
}
}
//sampleStart
class Grid(private val cells: IntArray, val width: Int) {
operator fun get(x: Int, y: Int) =
0 // TODO: read cells[y * width + x]
}
//sampleEnd Show answer Hide answer
class Grid(private val cells: IntArray, val width: Int) {
operator fun get(x: Int, y: Int) = cells[y * width + x]
} 3. Index assignment
Add a set(x, y, value) so grid[x, y] = 9 writes a cell.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun set() {
val grid = Grid(intArrayOf(0, 0, 0, 0), width = 2)
grid[1, 0] = 9
Assert.assertEquals("write value into cells[y * width + x]",
9, grid[1, 0])
}
}
//sampleStart
class Grid(private val cells: IntArray, val width: Int) {
operator fun get(x: Int, y: Int) = cells[y * width + x]
operator fun set(x: Int, y: Int, value: Int) {
// TODO: write value into cells[y * width + x]
}
}
//sampleEnd Show answer Hide answer
operator fun set(x: Int, y: Int, value: Int) {
cells[y * width + x] = value
} 4. Call an object
Give Adder(val by: Int) an invoke(x) so add5(10) works like a function call.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun invoke() {
val add5 = Adder(5)
Assert.assertEquals("calling the object works like x + by",
15, add5(10))
}
}
//sampleStart
class Adder(val by: Int) {
operator fun invoke(x: Int) =
x // TODO: return x + by
}
//sampleEnd Show answer Hide answer
class Adder(val by: Int) {
operator fun invoke(x: Int) = x + by
}
val add5 = Adder(5)
add5(10) // 15 membership, comparison, assignment
5. Support the in keyword
Give Rectangle a contains(p: Point) so p in rect works.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun contains() {
val rect = Rectangle(0, 0, 10, 10)
Assert.assertTrue("a point inside is in the rectangle",
Point(5, 5) in rect)
Assert.assertFalse("a point outside is not",
Point(20, 20) in rect)
}
}
//sampleStart
data class Point(val x: Int, val y: Int)
data class Rectangle(val x0: Int, val y0: Int, val x1: Int, val y1: Int)
operator fun Rectangle.contains(p: Point): Boolean =
false // TODO: true when p is inside
//sampleEnd Show answer Hide answer
operator fun Rectangle.contains(p: Point): Boolean =
p.x in x0..x1 && p.y in y0..y1p in rect compiles to rect.contains(p).
6. Comparison operators
Make Money(val cents: Int) support < and > by implementing Comparable.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun compareTo() {
Assert.assertTrue("less-than compares by cents",
Money(100) < Money(200))
Assert.assertFalse("greater is not less",
Money(200) < Money(100))
}
}
//sampleStart
data class Money(val cents: Int) : Comparable<Money> {
override fun compareTo(other: Money) =
0 // TODO: compare by cents
}
//sampleEnd Show answer Hide answer
data class Money(val cents: Int) : Comparable<Money> {
override fun compareTo(other: Money) = cents.compareTo(other.cents)
}compareTo backs <, >, <=, >=.
7. Mutate in place with +=
Give IntBag a plusAssign operator so bag += 3 adds an element to it in place.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun plusAssign() {
val bag = IntBag()
bag += 3
bag += 5
Assert.assertEquals("+= adds an element in place",
listOf(3, 5), bag.items)
}
}
//sampleStart
class IntBag(val items: MutableList<Int> = mutableListOf()) {
operator fun plusAssign(x: Int) {
// TODO: add x to items
}
}
//sampleEnd Show answer Hide answer
class IntBag(val items: MutableList<Int> = mutableListOf()) {
operator fun plusAssign(x: Int) { items.add(x) }
}+= calls plusAssign when it’s defined; otherwise it falls back to a = a + b via plus.
unary and iteration
8. Negate
Give Cents(val n: Int) a unary minus so -cents works.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun unaryMinus() {
Assert.assertEquals("negate n",
Cents(-5), -Cents(5))
}
}
//sampleStart
data class Cents(val n: Int) {
operator fun unaryMinus() =
Cents(n) // TODO: negate n
}
//sampleEnd Show answer Hide answer
data class Cents(val n: Int) {
operator fun unaryMinus() = Cents(-n)
} 9. Make it for-loopable
Give Tree(private val nodes: List<String>) an iterator() so it works in a for loop.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun iterator() {
val seen = mutableListOf<String>()
for (node in Tree(listOf("a", "b"))) seen.add(node)
Assert.assertEquals("iterate over the nodes in order",
listOf("a", "b"), seen)
}
}
//sampleStart
class Tree(private val nodes: List<String>) {
operator fun iterator(): Iterator<String> =
emptyList<String>().iterator() // TODO: return nodes.iterator()
}
//sampleEnd Show answer Hide answer
class Tree(private val nodes: List<String>) {
operator fun iterator() = nodes.iterator()
}
for (node in Tree(listOf("a", "b"))) println(node) 10. Scale by an Int
Give Money(val cents: Int) a times operator so Money(100) * 3 returns Money(300).
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun times() {
Assert.assertEquals("scale cents by n",
Money(300), Money(100) * 3)
}
}
//sampleStart
data class Money(val cents: Int) {
operator fun times(n: Int) =
Money(cents) // TODO: scale cents by n
}
//sampleEnd Show answer Hide answer
data class Money(val cents: Int) {
operator fun times(n: Int) = Money(cents * n)
}
Money(100) * 3 // Money(300)* maps to times — a good fit only because scaling money by a count has an obvious meaning.
Going deeper: get/set, contains, and plusAssign
12. Two-argument indexing
Give Grid get and set operators so grid[x, y] reads and grid[x, y] = v writes into a flat 3×3 IntArray.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun grid() {
val g = Grid()
g[1, 2] = 9
Assert.assertEquals(9, g[1, 2])
Assert.assertEquals(0, g[0, 0])
}
}
//sampleStart
class Grid {
private val cells = IntArray(9)
// TODO: operator get(x, y) and operator set(x, y, value), index = y*3 + x
}
//sampleEnd Show answer Hide answer
class Grid {
private val cells = IntArray(9)
operator fun get(x: Int, y: Int) = cells[y * 3 + x]
operator fun set(x: Int, y: Int, value: Int) { cells[y * 3 + x] = value }
}get/set back the [] syntax, and they take any number of indices.
13. Which way does in read?
You write 2 in vec, and Vec defines operator fun contains(n: Int). Which object is the receiver of contains, and with what argument? Reveal to check.
Show answer Hide answer
2 in vec compiles to vec.contains(2) — the operands reverse, because in asks “is 2 inside vec,” so the collection (vec) is the receiver and 2 is the argument. It’s the same reason key in map calls map.contains(key).
14. Mutate or reassign?
list += 3 mutates a MutableList in place, but x += 3 on a read-only val list reassigns. What two different functions are behind +=, and what happens if a type defines both? Reveal to check.
Show answer Hide answer
plusAssign (mutate in place) and plus (produce a new value, so a += b becomes a = a + b). A MutableList has plusAssign; a value type has plus. If a type defines both, a += b is ambiguous and the compiler rejects it — pick one.
Back to the lesson, Operator Overloading, Kept on a Leash, or on to the next one: coroutines.
Comments