How do I use object expressions and object declarations in Kotlin?

Object expressions vs. object declarations in Kotlin

Kotlin has two closely related features:

  • Object expressions: create an anonymous object immediately.
  • Object declarations: create a named singleton object.

They both use the object keyword, but they are used for different purposes.


1. Object expressions

Use an object expression when you need a one-off object, often to implement an interface or extend a class without creating a named class.

Basic anonymous object

fun main() {
    val user = object {
        val name = "Ava"
        val age = 30

        fun greet() {
            println("Hello, my name is $name")
        }
    }

    println(user.name)
    user.greet()
}

Here, user is an anonymous object with properties and functions.


2. Implementing an interface with an object expression

Object expressions are commonly used for callbacks, listeners, and small implementations.

interface ClickListener {
    fun onClick()
}

fun setClickListener(listener: ClickListener) {
    listener.onClick()
}

fun main() {
    setClickListener(object : ClickListener {
        override fun onClick() {
            println("Button clicked")
        }
    })
}

The syntax is:

object : SomeInterface {
    override fun someFunction() {
        // implementation
    }
}

3. Extending a class with an object expression

You can also create an anonymous subclass.

open class Animal(val name: String) {
    open fun speak() {
        println("$name makes a sound")
    }
}

fun main() {
    val dog = object : Animal("Buddy") {
        override fun speak() {
            println("$name barks")
        }
    }

    dog.speak()
}

4. Implementing multiple types

An object expression can extend one class and implement one or more interfaces.

open class Logger {
    open fun log(message: String) {
        println("Log: $message")
    }
}

interface Closeable {
    fun close()
}

fun main() {
    val resource = object : Logger(), Closeable {
        override fun log(message: String) {
            println("Custom log: $message")
        }

        override fun close() {
            println("Resource closed")
        }
    }

    resource.log("Started")
    resource.close()
}

If a superclass has a constructor, call it after the class name:

object : Logger()

5. Object declarations

Use an object declaration when you want a named singleton: exactly one instance, created lazily when first used.

object DatabaseConfig {
    val url = "jdbc:postgresql://localhost:5432/app"
    val username = "admin"

    fun connect() {
        println("Connecting to $url as $username")
    }
}

fun main() {
    DatabaseConfig.connect()
}

You do not instantiate it with DatabaseConfig().

Use it directly by name:

DatabaseConfig.connect()

6. Object declarations can implement interfaces

interface Analytics {
    fun track(event: String)
}

object ConsoleAnalytics : Analytics {
    override fun track(event: String) {
        println("Tracking event: $event")
    }
}

fun main() {
    ConsoleAnalytics.track("AppOpened")
}

This is useful for global services, registries, configuration, or strategy objects.


7. Object declarations can extend classes

open class AppLogger {
    open fun info(message: String) {
        println("INFO: $message")
    }
}

object Logger : AppLogger() {
    override fun info(message: String) {
        println("[App] $message")
    }
}

fun main() {
    Logger.info("Application started")
}

8. Companion objects

A companion object is an object declaration inside a class. It is commonly used for factory methods and static-like members.

class User private constructor(val name: String) {
    companion object {
        fun create(name: String): User {
            return User(name.trim())
        }
    }
}

fun main() {
    val user = User.create("  Ava  ")
    println(user.name)
}

You call companion object members through the class name:

User.create("Ava")

9. Named companion objects

A companion object can have a name.

class App {
    companion object Config {
        const val VERSION = "1.0.0"

        fun printVersion() {
            println("Version: $VERSION")
        }
    }
}

fun main() {
    App.printVersion()
    App.Config.printVersion()
}

Both calls are valid:

App.printVersion()
App.Config.printVersion()

10. Object expression visibility detail

If an anonymous object is stored in a local variable, you can access its members:

fun main() {
    val person = object {
        val name = "Mira"
        fun sayHi() = println("Hi, I am $name")
    }

    println(person.name)
    person.sayHi()
}

But if an anonymous object is returned from a public function, its specific members are not visible unless the return type exposes them.

class Factory {
    fun createPublic(): Any = object {
        val name = "Hidden"
    }

    private fun createPrivate() = object {
        val name = "Visible inside class"
    }

    fun demo() {
        val item = createPrivate()
        println(item.name)
    }
}

In this example:

fun createPublic(): Any = object {
    val name = "Hidden"
}

The caller only sees Any, not name.


11. Quick comparison

Feature Object expression Object declaration
Purpose One-off anonymous object Named singleton
Has a name? Usually no Yes
Instantiated with constructor? No No
Created when? Immediately where expression runs Lazily on first access
Common use Callbacks, temporary implementations Singletons, global config, shared services
Can extend classes? Yes Yes
Can implement interfaces? Yes Yes

Simple rule of thumb

Use an object expression when you need an object right here, right now:

val listener = object : ClickListener {
    override fun onClick() {
        println("Clicked")
    }
}

Use an object declaration when you need one shared named instance:

object AppSettings {
    val theme = "dark"
}

How do I create and use companion objects for static-like behavior in Kotlin?

In Kotlin, a companion object is an object declared inside a class that can hold members callable on the class itself, giving you static-like behavior.

Kotlin does not have Java-style static members directly. Instead, you usually use companion object.

Basic example

class User(val name: String) {
    companion object {
        const val DEFAULT_NAME = "Guest"

        fun createDefault(): User {
            return User(DEFAULT_NAME)
        }
    }
}

You can access companion object members using the class name:

fun main() {
    println(User.DEFAULT_NAME)

    val user = User.createDefault()
    println(user.name)
}

Output:

Guest
Guest

Why it feels like static

This:

User.createDefault()

is similar to calling a static method in Java:

User.createDefault();

But internally, Kotlin’s companion object is an actual singleton object associated with the class.

Companion object properties

You can put properties inside a companion object:

class Counter {
    companion object {
        var count = 0

        fun increment() {
            count++
        }
    }
}

Usage:

fun main() {
    Counter.increment()
    Counter.increment()

    println(Counter.count)
}

Output:

2

Named companion objects

A companion object can have a name:

class Database {
    companion object Factory {
        fun connect(): Database {
            return Database()
        }
    }
}

You can still access members through the class name:

val db = Database.connect()

Or through the companion object name:

val db = Database.Factory.connect()

Factory method example

A common use case is creating factory methods:

class Person private constructor(
    val name: String,
    val age: Int
) {
    companion object {
        fun of(name: String, age: Int): Person {
            require(age >= 0) { "Age cannot be negative" }
            return Person(name, age)
        }
    }
}

Usage:

fun main() {
    val person = Person.of("Alice", 30)
    println(person.name)
}

Constants in companion objects

For compile-time constants, use const val:

class ApiConfig {
    companion object {
        const val BASE_URL = "https://api.example.com"
        const val TIMEOUT_SECONDS = 30
    }
}

Usage:

println(ApiConfig.BASE_URL)

const val can only be used with primitive types and String.

Java interoperability

From Java, companion object members are normally accessed through Companion:

class MathUtils {
    companion object {
        fun double(x: Int): Int = x * 2
    }
}

Java usage:

int result = MathUtils.Companion.double(5);

If you want Java callers to use it like a real static method, add @JvmStatic:

class MathUtils {
    companion object {
        @JvmStatic
        fun double(x: Int): Int = x * 2
    }
}

Then Java can call:

int result = MathUtils.double(5);

For constants:

class Constants {
    companion object {
        const val APP_NAME = "MyApp"
    }
}

Java can access this as:

String appName = Constants.APP_NAME;

Companion objects can implement interfaces

Because companion objects are real objects, they can implement interfaces:

interface Parser<T> {
    fun parse(value: String): T
}

class User(val name: String) {
    companion object : Parser<User> {
        override fun parse(value: String): User {
            return User(value)
        }
    }
}

Usage:

fun main() {
    val user = User.parse("Alice")
    println(user.name)
}

You can also pass the companion object where the interface is expected:

fun <T> parseWith(parser: Parser<T>, value: String): T {
    return parser.parse(value)
}

val user = parseWith(User, "Bob")

Here, User refers to the companion object when used as a value.

Key points

  • Use companion object for static-like members.
  • Access members as ClassName.member.
  • Use const val for compile-time constants.
  • Use @JvmStatic if Java callers need static-style access.
  • Companion objects are real singleton objects.
  • Companion objects can have names and implement interfaces.

How do I use inner and nested classes in Kotlin?

In Kotlin, classes can be declared inside other classes in two main ways:

  1. Nested classes — default behavior
  2. Inner classes — declared with the inner keyword

Nested classes

A class declared inside another class is nested by default.

class Outer {
    class Nested {
        fun message(): String {
            return "Hello from Nested"
        }
    }
}

You create an instance of the nested class using the outer class name:

fun main() {
    val nested = Outer.Nested()
    println(nested.message())
}

Important point

A nested class does not have access to members of the outer class.

class Outer {
    private val name = "Outer"

    class Nested {
        fun printName() {
            // println(name) // Error: cannot access outer class member
        }
    }
}

This is similar to a static nested class in Java.


Inner classes

If you want the nested class to access members of the outer class, mark it with inner.

class Outer {
    private val name = "Outer"

    inner class Inner {
        fun message(): String {
            return "Hello from $name"
        }
    }
}

You create an inner class instance from an instance of the outer class:

fun main() {
    val outer = Outer()
    val inner = outer.Inner()

    println(inner.message())
}

Output:

Hello from Outer

Difference between nested and inner classes

Feature Nested class Inner class
Keyword No keyword needed Uses inner
Has reference to outer class No Yes
Can access outer members No Yes
Instantiation Outer.Nested() Outer().Inner()
Similar to Java static nested class non-static inner class

Accessing this from an inner class

Inside an inner class, this refers to the inner class instance.

To refer to the outer class instance, use this@Outer.

class Outer {
    private val value = "Outer value"

    inner class Inner {
        private val value = "Inner value"

        fun printValues() {
            println(value)
            println(this.value)
            println([email protected])
        }
    }
}

Output:

Inner value
Inner value
Outer value

Example with state

class ShoppingCart {
    private val items = mutableListOf<String>()

    fun addItem(item: String) {
        items.add(item)
    }

    inner class Summary {
        fun printSummary() {
            println("Cart has ${items.size} items")
            println(items.joinToString())
        }
    }
}

fun main() {
    val cart = ShoppingCart()
    cart.addItem("Book")
    cart.addItem("Pen")

    val summary = cart.Summary()
    summary.printSummary()
}

Output:

Cart has 2 items
Book, Pen

Here, Summary is an inner class because it needs access to items from ShoppingCart.


When to use each

Use a nested class when:

  • The class is logically grouped inside another class
  • It does not need access to the outer class instance
  • You want a namespace-like structure
class ApiResponse {
    class Error(val code: Int, val message: String)
}

Use an inner class when:

  • The class needs access to the outer class’s properties or functions
  • Each inner class instance is tied to a specific outer class instance
class Form {
    private val fields = mutableListOf<String>()

    inner class Validator {
        fun validate(): Boolean {
            return fields.isNotEmpty()
        }
    }
}

Summary

class Outer {
    class Nested {
        // No access to Outer instance
    }

    inner class Inner {
        // Has access to Outer instance
    }
}

Use nested classes by default, and use inner only when the inner class needs to access the outer class instance.

How do I use abstract classes and methods in Kotlin?

In Kotlin, an abstract class is a class that cannot be instantiated directly. It is meant to be subclassed.

An abstract method is a method declared without an implementation. Subclasses must override it.

Basic example

abstract class Animal {
    abstract fun makeSound()

    fun sleep() {
        println("Sleeping...")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("Woof!")
    }
}

fun main() {
    val dog = Dog()
    dog.makeSound()
    dog.sleep()
}

Output:

Woof!
Sleeping...

Key points

1. Use abstract before the class

abstract class Shape

You cannot create an instance of it:

val shape = Shape() // Error

2. Abstract methods have no body

abstract fun area(): Double

A subclass must implement them:

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

3. Abstract classes can have regular methods

abstract class Shape {
    abstract fun area(): Double

    fun describe() {
        println("This is a shape")
    }
}

4. Abstract properties are allowed

abstract class Vehicle {
    abstract val maxSpeed: Int
}

class Car : Vehicle() {
    override val maxSpeed: Int = 200
}

5. Abstract classes can have constructors

abstract class Person(val name: String) {
    abstract fun work()
}

class Developer(name: String) : Person(name) {
    override fun work() {
        println("$name writes code")
    }
}

Complete example

abstract class Shape(val name: String) {
    abstract fun area(): Double

    fun printInfo() {
        println("$name has area ${area()}")
    }
}

class Rectangle(
    name: String,
    val width: Double,
    val height: Double
) : Shape(name) {
    override fun area(): Double {
        return width * height
    }
}

class Circle(
    name: String,
    val radius: Double
) : Shape(name) {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

fun main() {
    val shapes = listOf(
        Rectangle("Rectangle", 5.0, 3.0),
        Circle("Circle", 2.0)
    )

    for (shape in shapes) {
        shape.printInfo()
    }
}

Abstract class vs interface

Use an abstract class when you want to share state or constructor logic:

abstract class BaseRepository(val tableName: String) {
    abstract fun findAll(): List<String>
}

Use an interface when you mainly want to define behavior:

interface Drawable {
    fun draw()
}

A class can extend only one abstract class, but it can implement multiple interfaces:

abstract class Animal

interface Runnable {
    fun run()
}

interface Swimmable {
    fun swim()
}

class Duck : Animal(), Runnable, Swimmable {
    override fun run() {
        println("Duck runs")
    }

    override fun swim() {
        println("Duck swims")
    }
}

In short: use abstract class for a shared base with common implementation/state, and use abstract fun or abstract val for members subclasses must provide.

How do I use visibility modifiers (private, internal, protected, public) in Kotlin classes?

In Kotlin, visibility modifiers control where classes, properties, functions, and constructors can be accessed from.

The main visibility modifiers are:

  • public
  • private
  • protected
  • internal

If you do not specify a visibility modifier, Kotlin uses public by default.


1. public

public means the declaration can be accessed from anywhere.

class User {
    public val name: String = "Alice"

    public fun sayHello() {
        println("Hello, $name")
    }
}

Because public is the default, this is equivalent:

class User {
    val name: String = "Alice"

    fun sayHello() {
        println("Hello, $name")
    }
}

Use public when you want something to be part of the normal external API of a class.


2. private

Inside a class, private means the member can only be accessed inside that same class.

class BankAccount {
    private var balance: Double = 0.0

    fun deposit(amount: Double) {
        if (amount > 0) {
            balance += amount
        }
    }

    fun getBalance(): Double {
        return balance
    }
}

Usage:

fun main() {
    val account = BankAccount()

    account.deposit(100.0)

    println(account.getBalance())

    // Error:
    // println(account.balance)
}

Here, balance is hidden from outside code. The class controls access through deposit() and getBalance().

Private class members

class UserService {
    private fun validateName(name: String): Boolean {
        return name.isNotBlank()
    }

    fun createUser(name: String) {
        if (validateName(name)) {
            println("User created: $name")
        }
    }
}

validateName() is an implementation detail and cannot be called from outside UserService.


3. protected

protected means the member is visible inside:

  1. The class where it is declared
  2. Its subclasses

It is not visible to general outside code.

open class Animal {
    protected val name: String = "Animal"

    protected fun makeSound() {
        println("Some sound")
    }
}

class Dog : Animal() {
    fun bark() {
        println(name)
        makeSound()
        println("Woof!")
    }
}

Usage:

fun main() {
    val dog = Dog()

    dog.bark()

    // Error:
    // println(dog.name)

    // Error:
    // dog.makeSound()
}

Dog can access name and makeSound() because it inherits from Animal, but outside code cannot.

Important note about protected

In Kotlin, protected is mainly for class inheritance. Unlike Java, Kotlin does not make protected members visible to the whole package.


4. internal

internal means the declaration is visible within the same module.

A module is usually something like:

  • a Gradle source set
  • a Maven project
  • an IntelliJ IDEA module
  • a set of files compiled together
internal class InternalLogger {
    fun log(message: String) {
        println("LOG: $message")
    }
}

Code in the same module can use it:

fun main() {
    val logger = InternalLogger()
    logger.log("Application started")
}

But code from another module cannot access InternalLogger.

Use internal when you want something available across your project/module but not exposed as a public API to other modules.


Visibility in class members

You can apply visibility modifiers to properties and functions:

class Profile {
    public val username: String = "guest"
    private val passwordHash: String = "abc123"
    internal val accountId: Int = 42
    protected val role: String = "user"

    public fun showUsername() {
        println(username)
    }

    private fun checkPassword() {
        println(passwordHash)
    }
}

However, protected only makes sense in classes that may be inherited from:

open class BaseController {
    protected fun authorize() {
        println("Checking permissions")
    }
}

class UserController : BaseController() {
    fun getUser() {
        authorize()
        println("Returning user")
    }
}

Visibility for constructors

You can also control constructor visibility.

Private constructor

Useful when you want to restrict object creation.

class DatabaseConnection private constructor() {
    companion object {
        fun create(): DatabaseConnection {
            return DatabaseConnection()
        }
    }
}

Usage:

fun main() {
    val connection = DatabaseConnection.create()

    // Error:
    // val direct = DatabaseConnection()
}

Visibility for property setters

A common Kotlin pattern is to expose a property for reading but restrict writing.

class Counter {
    var count: Int = 0
        private set

    fun increment() {
        count++
    }
}

Usage:

fun main() {
    val counter = Counter()

    counter.increment()
    println(counter.count)

    // Error:
    // counter.count = 10
}

Here:

  • count can be read from outside the class
  • count can only be changed inside the class

You can also use protected set or internal set:

open class Document {
    var status: String = "draft"
        protected set
}

Subclasses can change status, but outside code cannot.


Top-level visibility

Kotlin also allows functions, properties, and classes outside classes.

private fun helper() {
    println("Only visible in this file")
}

internal fun moduleHelper() {
    println("Visible in the same module")
}

public fun publicApi() {
    println("Visible everywhere")
}

For top-level declarations:

Modifier Meaning
public Visible everywhere
private Visible only in the same file
internal Visible in the same module
protected Not allowed at top level

Quick comparison

Modifier Class member visibility
public Visible everywhere
private Visible only inside the declaring class
protected Visible inside the declaring class and subclasses
internal Visible inside the same module

For top-level declarations:

Modifier Top-level visibility
public Visible everywhere
private Visible only in the same file
internal Visible inside the same module
protected Not allowed

Practical example

open class Account(
    public val owner: String,
    private var balance: Double
) {
    internal val accountNumber: String = "ACC-123"

    protected fun canWithdraw(amount: Double): Boolean {
        return amount <= balance
    }

    fun deposit(amount: Double) {
        if (amount > 0) {
            balance += amount
        }
    }

    fun currentBalance(): Double {
        return balance
    }
}

class SavingsAccount(owner: String, balance: Double) : Account(owner, balance) {
    fun withdraw(amount: Double) {
        if (canWithdraw(amount)) {
            println("Withdrawal allowed")
        } else {
            println("Insufficient funds")
        }
    }
}

Usage:

fun main() {
    val account = SavingsAccount("Alice", 500.0)

    println(account.owner)
    println(account.currentBalance())

    account.deposit(100.0)
    account.withdraw(50.0)

    // Error: private
    // println(account.balance)

    // Error: protected
    // account.canWithdraw(100.0)

    // Accessible only in the same module:
    // println(account.accountNumber)
}

Rule of thumb

Use the most restrictive visibility that still works:

  • Use private for implementation details.
  • Use protected for behavior intended only for subclasses.
  • Use internal for code shared inside a module but hidden from external modules.
  • Use public for APIs that other code is expected to use.

In most Kotlin classes, you will commonly use private for internal state and leave only the necessary functions or properties public.