Extension Functions & Object Declarations

Kotlin Fundamentals — Extension Functions & Object Declarations
Kotlin Fundamentals
Course 1 · Chapter 8 · Extension Functions & Object Declarations

🧰 Extension Functions & Object Declarations

The final chapter of Kotlin Fundamentals covers two features that shape how idiomatic Kotlin code is actually organized: extension functions, which add new behavior to existing classes without touching or subclassing them, and object declarations, Kotlin's built-in, thread-safe way of writing a singleton.

➕ Extension Functions

An extension function adds a new method to an existing class — including classes you don't own, like String or Int — without modifying its source or subclassing it:

fun String.shout(): String = uppercase() + "!" println("hello".shout()) // HELLO!

The syntax is fun ReceiverType.functionName() — the type before the dot is called the receiver type. Inside the function, this refers to the specific string it was called on, exactly as if shout() had been a real member of String all along. Calling it ("hello".shout()) looks completely indistinguishable from calling a built-in method.

Why Not Just Subclass?

String is a final, built-in class — it can't be subclassed at all. Even for classes you do own, extension functions let you add a helper without bloating the original class definition or creating an inheritance relationship that doesn't really exist.

Resolved at Compile Time

Unlike a real override, which method actually runs is decided statically, based on the declared type — extension functions aren't part of the class itself, and don't participate in polymorphism the way a real method does.

// A practical example: an extension on your own data class data class Product(val name: String, val price: Double) fun Product.priceWithTax(rate: Double = 0.2): Double = price * (1 + rate) val item = Product("Mouse", 20.0) println(item.priceWithTax()) // 24.0

Adding Behavior to an Existing Type: Java vs Kotlin

JavaKotlin
static String shout(String s) { return s.toUpperCase() + "!"; }
shout("hello")
fun String.shout() = uppercase() + "!"
"hello".shout()

Java's usual workaround is a static utility method (StringUtils.shout(s)) — functionally similar, but called with the value as an argument rather than as the receiver, so it doesn't read as naturally at the call site and doesn't show up in IDE autocomplete when typing someString..

🏢 object — Kotlin's Built-In Singleton

Chapter 4 introduced companion object for per-class "static" members. A plain object declaration is the more general form — an entire class with exactly one instance, guaranteed:

object AppConfig { val appName = "Learning Blog" var debugMode = false fun printInfo() = println("$appName (debug=$debugMode)") } AppConfig.debugMode = true AppConfig.printInfo() // Learning Blog (debug=true)

There's no constructor, and no way to create a second instance — Kotlin creates the single instance lazily on first access and guarantees it's thread-safe, with no extra code required. This directly replaces the classic Java singleton pattern (a private constructor, a static instance field, a static getInstance() method, and manual thread-safety handling) with one keyword.

⚠ object vs class — Choosing Correctly

Use object only when there's genuinely one, shared instance for the entire app's lifetime (configuration, a shared cache, a stateless utility holder). If you find yourself wanting more than one instance later, or passing constructor arguments in, that's a sign it should have been a regular class all along.

🎭 Object Expressions

An anonymous, one-off object can also be created inline, without a named declaration — most often to implement an interface on the spot:

interface ClickListener { fun onClick() } fun setListener(listener: ClickListener) { listener.onClick() } setListener(object : ClickListener { override fun onClick() { println("Clicked!") } })

This is Kotlin's equivalent of Java's anonymous inner class (new ClickListener() { ... }) — a throwaway implementation created exactly where it's used, with no separate named class needed. It shows up constantly in UI callback code, including Android's older View-based click listeners.

💻 Coding Challenges

Challenge 1: Extension Function on Int

Write an extension function fun Int.isPrime(): Boolean that returns whether the receiver is a prime number (handle 2 and up; anything less than 2 is not prime). Test it against several numbers, calling it as 7.isPrime().

Goal: Add a genuinely new capability to a built-in type via an extension function.

→ Solution

Challenge 2: A Singleton with object

Write an object Counter with a var count: Int = 0 property and a function increment() that adds 1. Call increment() three times from main() and print the final count, then explain in a comment why this couldn't be done the same way with a regular class without extra setup.

Goal: Use object for genuine shared, single-instance state.

→ Solution

Challenge 3: Object Expression Implementing an Interface

Write an interface Validator with a function isValid(input: String): Boolean. Write a function checkInput(text: String, validator: Validator) that prints whether the input is valid. Call it by passing an inline object expression that checks the input isn't blank and is at least 3 characters long.

Goal: Implement an interface with an anonymous object expression instead of a named class.

→ Solution

💡 Course 1 Complete — What This Sets Up

That's every chapter of Kotlin Fundamentals — types, functions, null safety, classes, inheritance, collections, control flow, and now extension functions and singletons. Kotlin Intermediate (Course 2) builds directly on all of this, starting with coroutines — Kotlin's approach to asynchronous code, and one of its most distinctive features next to null safety itself.

🎯 What's Next

Course 1 is complete. Kotlin Intermediate (Course 2) begins with Coroutines Basics — suspend functions, CoroutineScope, launch/async, and Dispatchers.