Extension Functions & Object Declarations
🧰 Extension Functions & Object Declarations
➕ 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:
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.
Adding Behavior to an Existing Type: Java vs Kotlin
| Java | Kotlin |
|---|---|
| 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:
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.
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:
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.
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.
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.
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.