Question

Kotlin Anonymous Classes with Object Expressions

kotlinjavaabstract-class

Question

Assume that KeyAdapter is an abstract Java class with several methods that may be overridden. In Java, an anonymous subclass can be created and assigned to a listener variable:

KeyAdapter keyListener = new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent keyEvent) {
        // ...
    }
};

How can I create the equivalent anonymous instance of an abstract class in Kotlin and override keyPressed?

Short Answer

You will learn how Kotlin's object expressions create anonymous objects, including anonymous subclasses of abstract Java or Kotlin classes. You will also see how this differs from Kotlin lambdas and named classes.

Concept

Kotlin uses an object expression to create an anonymous object at the point where it is needed.

For an abstract class, an object expression creates an unnamed subclass and provides implementations for the required abstract members. This is Kotlin's equivalent of Java syntax such as:

new KeyAdapter() { ... }

The general Kotlin form is:

object : ParentClass(arguments) {
    // overrides and additional members
}

object starts the anonymous object, : means “inherits from,” and ParentClass() invokes the parent constructor. The body contains the functions or properties you want to override.

This is useful when a behavior is small and local, such as handling one UI event, defining a callback for one operation, or supplying a temporary strategy. It avoids creating a separate named class solely for one use.

Mental Model

Think of an abstract class as a partially completed form: it defines the available fields and asks you to fill in required sections.

A Kotlin object expression is like filling out one copy of that form immediately, without giving the completed copy a class name.

  • KeyAdapter is the form or template.
  • object : KeyAdapter() creates one completed object from it.
  • override fun keyPressed(...) fills in the behavior for that event.

The object can then be stored in a variable or passed directly to a function.

Take Quiz

Syntax and Examples

The core syntax for an anonymous subclass is:

val variableName: ParentType = object : ParentType(constructorArguments) {
    override fun requiredMethod() {
        // implementation
    }
}

For Java AWT's KeyAdapter, the Kotlin equivalent is:

import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent

val keyListener: KeyAdapter = object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        println("Pressed key code: ${event.keyCode}")
    }
}

KeyAdapter already provides empty implementations of the methods in KeyListener. Therefore, you only need to override the event method you care about.

You can also pass the anonymous object directly instead of assigning it first:

textField.addKeyListener(object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        if (event.keyCode == KeyEvent.VK_ENTER) {
            println("Enter was pressed")
        }
    }
})

If the parent class has constructor parameters, pass them after the parent class name:

abstract class MessageHandler(val prefix: String) {
    abstract fun handle(message: String)
}

val handler = object : MessageHandler("INFO") {
    override fun handle(message: String) {
        println("$prefix: $message")
    }
}

Step by Step Execution

Consider this example:

import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent

val listener = object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        println("Key: ${event.keyChar}")
    }
}

Execution happens as follows:

  1. Kotlin evaluates object : KeyAdapter().
  2. It creates one new anonymous object that inherits from KeyAdapter.
  3. The KeyAdapter constructor runs.
  4. Kotlin records the custom implementation of keyPressed on that object.
  5. The object is stored in listener.
  6. Later, when the UI toolkit detects a key press, it calls listener.keyPressed(event).
  7. The overridden method runs and prints the character from the received KeyEvent.

The anonymous object's class has no source-level name, but the object itself is a normal runtime object and can be passed anywhere a KeyAdapter or compatible listener type is expected.

Real World Use Cases

Object expressions are commonly used for small, one-off implementations.

  • Desktop UI events: Handle keyboard, mouse, focus, or window events with Java Swing or AWT adapters.
  • Android callbacks: Implement Java-style listener interfaces or abstract callback classes when working with older APIs.
  • Testing: Create a tiny fake implementation of an abstract dependency for a single test.
  • Data processing: Define a short comparator, visitor, or handler near the code that uses it.
  • Networking and SDK callbacks: Supply a response handler for a request without creating a reusable class.

For example, a one-off test double:

abstract class Clock {
    abstract fun currentYear(): Int
}

val fixedClock = object : Clock() {
    override fun currentYear() = 2030
}

A test can use fixedClock to behave predictably without relying on the actual system clock.

Take Quiz

Real Codebase Usage

In real Kotlin projects, object expressions work best when the implementation is short, specific to one location, and unlikely to be reused.

Pass directly to an API

button.addActionListener {
    println("Saved")
}

When an API expects a Java interface with one abstract method, Kotlin can often use a lambda. But an abstract class such as KeyAdapter requires an object expression:

component.addKeyListener(object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        if (event.keyCode != KeyEvent.VK_ESCAPE) return
        closeDialog()
    }
})

The early return acts as a guard clause: it keeps the relevant behavior clear and avoids nesting.

Promote repeated behavior to a named class

If the listener is long, used in multiple places, or needs its own state and tests, use a named class instead:

class EscapeKeyListener(
    private val close: () -> Unit
) : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        if (event.keyCode == KeyEvent.VK_ESCAPE) {
            close()
        }
    }
}

This is easier to reuse and test than duplicating multiple anonymous objects.

Common Mistakes

Forgetting parentheses after a superclass constructor

Most classes must be constructed with () when used as an anonymous superclass.

// Incorrect
val listener = object : KeyAdapter {
    override fun keyPressed(event: KeyEvent) {}
}
// Correct
val listener = object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {}
}

Omitting override

Kotlin requires the override keyword when replacing an inherited open or abstract member.

// Incorrect: this attempts to declare a new function
object : KeyAdapter() {
    fun keyPressed(event: KeyEvent) {}
}
// Correct
object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {}
}

Using a lambda for an abstract class

A lambda can implement a functional interface, but it cannot generally extend an abstract class.

// Not valid for KeyAdapter
val listener: KeyAdapter = { event -> println(event) }

Use an object expression for KeyAdapter.

Using the wrong parameter type or method name

Overridden methods must match the parent declaration exactly. Let the IDE generate the override when possible:

override fun keyPressed(event: KeyEvent) {
    // Correct signature for java.awt.event.KeyAdapter
}

Expecting the anonymous type to be reusable

An object expression creates a fresh object each time it executes:

fun newListener() = object : KeyAdapter() {}

Each call to newListener() returns a different listener instance. Use a named object declaration only when you want one shared singleton instance.

Comparisons

TechniqueBest forCan extend an abstract class?Creates an instance
Object expression: object : KeyAdapter() {}One-off local implementationYesEach time the expression runs
Named class: class MyListener : KeyAdapter()Reusable, testable behaviorYesWhen constructed with MyListener()
Object declaration: object MyListener : KeyAdapter()One shared singletonYesOnce, lazily on first access
Lambda: { value -> ... }A functional interface with one abstract methodNo, not for classesWhen the lambda is created

A named object declaration differs from an object expression:

object GlobalKeyListener : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        println(event.keyCode)
    }
}

This declares a named singleton. In contrast, object : KeyAdapter() { ... } is anonymous and creates an object at the expression location.

Take Quiz

Cheat Sheet

// Anonymous subclass of an abstract or open class
val value: Parent = object : Parent() {
    override fun method() {
        // implementation
    }
}
// Java AWT KeyAdapter example
val listener = object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {
        // handle event
    }
}
// Pass it directly
component.addKeyListener(object : KeyAdapter() {
    override fun keyPressed(event: KeyEvent) {}
})

Rules:

  • Use object : Superclass() for an anonymous subclass.
  • Include constructor arguments: object : Parent(argument) { ... }.
  • Use override for inherited methods.
  • Implement every required abstract member unless the parent adapter already supplies it.
  • Use a lambda only for a suitable functional interface, not an abstract class.
  • A new object expression creates a new instance whenever it runs.

FAQ

What is the Kotlin equivalent of Java's new SomeClass() { ... }?

Use an object expression:

object : SomeClass() {
    // overrides
}

Can an object expression extend an abstract class in Kotlin?

Yes. Provide the abstract members inside the object body, and call the superclass constructor if it has one.

Why does Kotlin require KeyAdapter() instead of KeyAdapter?

KeyAdapter() invokes the superclass constructor. Classes normally require constructor parentheses when they are instantiated or used as a superclass in an object expression.

Can I use a lambda instead of object : KeyAdapter()?

No. KeyAdapter is an abstract class. Lambdas are intended for functional interfaces, not classes. Use an object expression.

Do I need to override every key event method in KeyAdapter?

No. KeyAdapter is an adapter class that provides default empty methods. Override only the methods you need, such as keyPressed.

When should I create a named listener class instead?

Create a named class when the behavior is reused, large, stateful, or worth testing independently. Use an object expression for small, local behavior.

Does an anonymous object expression create a singleton?

No. It creates a new instance every time the expression executes. A named object declaration is Kotlin's singleton feature.

Take Quiz

Mini Project

Description

Build a small keyboard shortcut handler for a Java AWT or Swing component. The handler uses an anonymous KeyAdapter object to react to specific key presses while ignoring unrelated events.

Goal

Create and attach a KeyAdapter that prints a message for Enter and Escape keys.

Requirements

Attach a key listener using a Kotlin object expression. Override keyPressed with the correct KeyEvent parameter. Print Submitted when Enter is pressed. Print Cancelled when Escape is pressed. Ignore all other keys.

Take Quiz

Keep learning

Related questions

View more