0
0
Kotlinprogramming~15 mins

Object expressions (anonymous objects) in Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Object expressions (anonymous objects)
What is it?
Object expressions in Kotlin let you create objects without naming a class. They are called anonymous objects because they don't have a class name. You can use them to quickly make a one-time object with specific behavior. This helps when you want a simple object without writing a full class.
Why it matters
Without object expressions, you would need to create a full class every time you want a small custom object. This would make your code longer and harder to read. Object expressions save time and keep code clean by letting you create objects on the spot. They are especially useful for quick tasks like event handling or simple interfaces.
Where it fits
Before learning object expressions, you should understand classes, objects, and interfaces in Kotlin. After this, you can learn about object declarations (named singletons) and higher-order functions that use anonymous objects. This topic fits in the middle of Kotlin's object-oriented and functional programming features.
Mental Model
Core Idea
An object expression is a quick way to create a one-off object with custom behavior without naming a class.
Think of it like...
It's like writing a quick note on a sticky pad instead of printing a whole book when you just need a small reminder.
Object Expression Structure:

┌─────────────────────────────┐
│ object : SuperType1, ... {   │
│   // override or add members │
│ }                           │
└─────────────────────────────┘

Usage:
val obj = object : InterfaceOrClass {
  override fun method() { ... }
}

obj.method()
Build-Up - 7 Steps
1
FoundationUnderstanding classes and objects
🤔
Concept: Learn what classes and objects are in Kotlin as the base for object expressions.
In Kotlin, a class is like a blueprint for creating objects. An object is an instance of a class with its own data and behavior. For example: class Person(val name: String) { fun greet() = "Hello, $name!" } val p = Person("Anna") println(p.greet()) // Output: Hello, Anna!
Result
You can create named objects from classes and call their functions.
Understanding classes and objects is essential because object expressions create objects without naming a class explicitly.
2
FoundationInterfaces and inheritance basics
🤔
Concept: Know how interfaces and inheritance work to understand what object expressions can extend or implement.
Interfaces define functions without implementation. Classes or objects can implement interfaces by providing those functions. For example: interface Greeter { fun greet(): String } class Friendly : Greeter { override fun greet() = "Hi!" } val f = Friendly() println(f.greet()) // Output: Hi!
Result
You can create objects that follow a contract defined by interfaces.
Object expressions often implement interfaces or extend classes, so knowing this helps you understand their purpose.
3
IntermediateCreating anonymous objects with object expressions
🤔Before reading on: do you think you can create an object without a class name in Kotlin? Commit to yes or no.
Concept: Learn how to create an object expression that defines an anonymous object implementing an interface or extending a class.
You can write: val greeter = object : Greeter { override fun greet() = "Hello from anonymous!" } println(greeter.greet()) // Output: Hello from anonymous! This creates an object without a class name, implementing Greeter.
Result
You get a one-off object with custom behavior without declaring a class.
Knowing that Kotlin lets you create objects on the spot saves you from boilerplate and speeds up coding.
4
IntermediateAdding properties and functions in object expressions
🤔Before reading on: can you add new properties or functions inside an anonymous object? Commit to yes or no.
Concept: Object expressions can have their own properties and functions beyond what they inherit or implement.
Example: val counter = object { var count = 0 fun increment() { count++ } } counter.increment() println(counter.count) // Output: 1 This object has a property and a function unique to it.
Result
You can create rich anonymous objects with state and behavior.
Understanding this lets you use object expressions as mini-classes for quick tasks.
5
IntermediateUsing object expressions for event listeners
🤔
Concept: Object expressions are often used to implement interfaces for callbacks or listeners in one place.
For example, in Android: button.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { println("Button clicked") } }) This creates an anonymous listener object inline.
Result
You can handle events without creating a separate named class.
This pattern keeps code concise and focused where the event happens.
6
AdvancedType and visibility of anonymous objects
🤔Before reading on: do you think anonymous objects always keep their full type outside their scope? Commit to yes or no.
Concept: Anonymous objects have special type behavior: if declared locally, their full type is known; if declared as a public property, only their supertype is visible.
Example: fun foo() { val obj = object { val x = 10 } println(obj.x) // Works: x is visible } val obj2 = object { val x = 10 } // Outside, obj2.x is NOT accessible because obj2's type is Any This means anonymous objects lose their unique members when exposed publicly.
Result
You learn when you can use anonymous object members and when you cannot.
Knowing this prevents confusion about why some properties are inaccessible outside their scope.
7
ExpertAnonymous objects and smart casts limitations
🤔Before reading on: do you think Kotlin's smart casts work with anonymous objects? Commit to yes or no.
Concept: Kotlin's smart casts do not work well with anonymous objects because their types are anonymous and cannot be checked at runtime easily.
For example: val obj: Any = object { val x = 42 } if (obj is SomeType) { // smart cast to SomeType } But with anonymous objects, you cannot check their type with 'is' because they have no name. This limits some uses of anonymous objects in type-safe code.
Result
You understand a key limitation of anonymous objects in Kotlin's type system.
Knowing this helps you avoid bugs and choose alternatives like data classes or named classes when type checks are needed.
Under the Hood
At runtime, Kotlin creates a hidden class for each anonymous object. This class extends the specified superclass or implements interfaces. The compiler generates a unique name for this class. The object expression creates an instance of this hidden class. If the anonymous object is local, the compiler keeps its full type information; if it's exposed publicly, only the declared supertype is visible to other code.
Why designed this way?
This design balances flexibility and type safety. It allows quick object creation without cluttering code with many small classes. The limitation on type visibility prevents exposing implementation details and keeps the public API clean. It also avoids runtime overhead of many named classes for simple uses.
Anonymous Object Creation Flow:

Source Code:
val obj = object : Interface {
  override fun method() {}
}

Compiler:
┌─────────────────────────────┐
│ Generates hidden class: Obj$1 │
│ Implements Interface         │
└─────────────┬───────────────┘
              │
Runtime:
┌─────────────▼───────────────┐
│ Creates instance of Obj$1    │
│ Assigns to 'obj' variable    │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do anonymous objects have a class name you can use elsewhere? Commit to yes or no.
Common Belief:Anonymous objects have a class name you can refer to anywhere in your code.
Tap to reveal reality
Reality:Anonymous objects do not have a usable class name; the compiler generates a hidden name that you cannot use directly.
Why it matters:Trying to refer to an anonymous object's class by name causes errors and confusion, blocking reuse or type checks.
Quick: Can you always access all properties of an anonymous object outside its declaration scope? Commit to yes or no.
Common Belief:All properties of an anonymous object are accessible everywhere you use the object.
Tap to reveal reality
Reality:Only properties declared in the object's supertype are accessible outside its local scope; unique properties are hidden if the object is exposed publicly.
Why it matters:Assuming full access leads to compilation errors and bugs when properties are unexpectedly invisible.
Quick: Do anonymous objects support smart casts like regular classes? Commit to yes or no.
Common Belief:Kotlin's smart casts work perfectly with anonymous objects.
Tap to reveal reality
Reality:Smart casts do not work with anonymous objects because their types are anonymous and cannot be checked at runtime.
Why it matters:Expecting smart casts can cause runtime errors or force awkward workarounds.
Quick: Are object expressions only for implementing interfaces? Commit to yes or no.
Common Belief:Object expressions can only implement interfaces, not extend classes.
Tap to reveal reality
Reality:Object expressions can both implement interfaces and extend classes, allowing flexible use.
Why it matters:Limiting their use to interfaces restricts design options and misses their full power.
Expert Zone
1
Anonymous objects created inside functions keep their full type, allowing access to all members locally, but lose this outside the function.
2
When multiple anonymous objects implement the same interface but have different extra members, their types are incompatible, which can cause subtle bugs.
3
Using object expressions in public APIs can unintentionally hide members, so named classes or interfaces are preferred for public contracts.
When NOT to use
Avoid anonymous objects when you need to expose rich APIs publicly or require type checks and smart casts. Instead, use named classes or data classes. Also, for complex logic or reuse, named classes improve readability and maintainability.
Production Patterns
In production, object expressions are commonly used for quick callbacks, listeners, or small helper objects inside functions. They keep code concise and localized. For public APIs, named classes or interfaces define clear contracts. Sometimes, object expressions are combined with higher-order functions for flexible behavior injection.
Connections
Lambda expressions
Both are ways to create anonymous behavior blocks in Kotlin.
Understanding object expressions helps grasp how Kotlin supports anonymous code units, complementing lambdas for different use cases.
Java anonymous inner classes
Object expressions in Kotlin are a modern, cleaner alternative to Java's anonymous inner classes.
Knowing this connection helps Java developers transition smoothly to Kotlin and appreciate its concise syntax.
Ad-hoc polymorphism in biology
Both involve creating unique instances with specific traits on demand without formal classification.
Seeing anonymous objects like unique organisms helps understand flexible, one-off behavior creation without formal naming.
Common Pitfalls
#1Trying to access unique properties of an anonymous object outside its local scope.
Wrong approach:val obj = object { val secret = 42 } fun printSecret(o: Any) { println((o as dynamic).secret) // Error: 'secret' not accessible } printSecret(obj)
Correct approach:val obj = object { val secret = 42 } fun printSecret() { println(obj.secret) // Access inside scope where 'secret' is visible } printSecret()
Root cause:Misunderstanding that anonymous object's unique members are only visible in their declaration scope.
#2Using anonymous objects as public properties expecting full member access.
Wrong approach:class Example { val obj = object { val x = 10 } } fun main() { val e = Example() println(e.obj.x) // Error: 'x' is not accessible }
Correct approach:interface HasX { val x: Int } class Example { val obj: HasX = object : HasX { override val x = 10 } } fun main() { val e = Example() println(e.obj.x) // Works }
Root cause:Not declaring the anonymous object with a supertype limits member visibility outside.
#3Expecting smart casts to work with anonymous objects in type checks.
Wrong approach:val obj: Any = object { val x = 5 } if (obj is SomeType) { println(obj.x) // Error: smart cast not possible }
Correct approach:Use named classes or interfaces when you need smart casts: interface HasX { val x: Int } val obj: HasX = object : HasX { override val x = 5 } if (obj is HasX) { println(obj.x) // Works }
Root cause:Anonymous objects lack a stable type for smart casts.
Key Takeaways
Object expressions let you create quick, one-off objects without naming a class, saving time and code.
They can implement interfaces or extend classes and have their own properties and functions.
Anonymous objects have limited visibility of their unique members outside their local scope.
Smart casts do not work with anonymous objects, so use named types when type checks are needed.
Object expressions are great for local, simple tasks like event listeners but not ideal for public APIs.