Skip to main content

Kotlin SAM Conversions

Introduction

When working with both Kotlin and Java code in the same project, you'll often encounter Java interfaces with a single abstract method (SAM). These are particularly common in Java libraries, especially with event handling or callback mechanisms. Kotlin offers a powerful feature called SAM conversions that makes working with such interfaces much more concise and readable.

In this tutorial, we'll explore what SAM conversions are, how they work, and how they make your code more elegant when interoperating with Java.

What are SAM Conversions?

SAM stands for "Single Abstract Method." A SAM type (also known as a functional interface in Java 8+) is an interface that contains exactly one abstract method. Examples include:

  • Runnable (with the run() method)
  • Comparator (with the compare() method)
  • ActionListener (with the actionPerformed() method)

Kotlin's SAM conversion allows you to replace a full anonymous class implementation with a more concise lambda expression when working with Java SAM interfaces. This makes your code shorter and more focused on what matters - the implementation logic.

Basic SAM Conversion Example

Let's look at a simple example using the Java Runnable interface:

kotlin
// Without SAM conversion (verbose)
val runnable = object : Runnable {
override fun run() {
println("Running without SAM conversion")
}
}

// With SAM conversion (concise)
val runnableSam = Runnable {
println("Running with SAM conversion")
}

// Using both
fun main() {
Thread(runnable).start()
Thread(runnableSam).start()
}

Output:

Running without SAM conversion
Running with SAM conversion

In the first example, we create an anonymous class implementing Runnable. In the second, we use SAM conversion to provide just the implementation for the run() method as a lambda.

How SAM Conversions Work

When the Kotlin compiler sees that you're passing a lambda expression where a Java SAM interface is expected, it automatically converts your lambda into an instance of that interface with the lambda body becoming the implementation of the single abstract method.

This happens:

  1. At compile time (not runtime)
  2. Only for Java interfaces (not Kotlin interfaces by default)
  3. Only when the target type is explicitly known

SAM Conversions with Parameters

Many functional interfaces take parameters. Let's see how SAM conversions work with the Comparator interface:

kotlin
fun sortStrings() {
val names = arrayListOf("Alice", "Bob", "Charlie", "David")

// Without SAM conversion
names.sortWith(object : Comparator<String> {
override fun compare(a: String, b: String): Int {
return a.length - b.length
}
})
println("Sorted by length (without SAM): $names")

// Reset list
names.clear()
names.addAll(listOf("Alice", "Bob", "Charlie", "David"))

// With SAM conversion
names.sortWith(Comparator { a, b ->
a.length - b.length
})
println("Sorted by length (with SAM): $names")

// Even shorter with implied type
names.sortWith { a, b -> a.length - b.length }
println("Sorted by length (shortest form): $names")
}

Output:

Sorted by length (without SAM): [Bob, Alice, David, Charlie]
Sorted by length (with SAM): [Bob, Alice, David, Charlie]
Sorted by length (shortest form): [Bob, Alice, David, Charlie]

The example shows three ways to sort a list using a Comparator, from the most verbose to the most concise.

Real-World Application: UI Event Handling

SAM conversions are particularly useful when working with Java UI libraries like Swing or JavaFX, where event listeners are typically SAM interfaces:

kotlin
import javax.swing.*
import java.awt.event.ActionEvent
import java.awt.event.ActionListener

fun createSimpleUI() {
val frame = JFrame("SAM Conversion Demo")
val button = JButton("Click Me")

// Without SAM conversion
button.addActionListener(object : ActionListener {
override fun actionPerformed(e: ActionEvent) {
JOptionPane.showMessageDialog(frame, "Button clicked the traditional way!")
}
})

// Create a second button
val button2 = JButton("Click Me (SAM)")

// With SAM conversion
button2.addActionListener {
JOptionPane.showMessageDialog(frame, "Button clicked with SAM conversion!")
}

// Set up and display the UI
val panel = JPanel()
panel.add(button)
panel.add(button2)
frame.contentPane = panel
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
frame.pack()
frame.isVisible = true
}

This example shows how SAM conversions significantly reduce boilerplate when setting up UI event handlers.

SAM Constructors

You can also use SAM conversions when creating a new instance of a Java interface, as we did with Runnable earlier. This is called a SAM constructor:

kotlin
fun samConstructors() {
// SAM constructor for Runnable
val task = Runnable {
println("Task is running")
}

// SAM constructor for Comparator
val lengthComparator = Comparator<String> { a, b ->
a.length - b.length
}

// Using them
Thread(task).start()

val names = listOf("Alice", "Bob", "Charlie")
println(names.sortedWith(lengthComparator))
}

Output:

Task is running
[Bob, Alice, Charlie]

Limitations of SAM Conversions

There are a few important limitations to be aware of:

  1. Java interfaces only: By default, SAM conversions work only with Java interfaces, not Kotlin interfaces.

  2. Type ambiguity: In some cases, you might need to specify the target type explicitly if it can't be inferred:

kotlin
// This might be ambiguous if multiple SAM interfaces could apply
val clickHandler = ActionListener { println("Clicked!") }
  1. Multiple method interfaces: If an interface has more than one abstract method, SAM conversion won't work:
kotlin
interface MultipleMethodInterface {
fun method1()
fun method2()
}

// Won't work: not a SAM interface
// val instance = MultipleMethodInterface { println("Can't do this!") }

SAM Conversions for Kotlin Interfaces

Since Kotlin 1.4, you can enable SAM conversions for Kotlin interfaces as well, by marking them with the fun modifier:

kotlin
// Define a Kotlin functional interface with the 'fun' modifier
fun interface Processor {
fun process(input: String): String
}

fun kotlinSamInterface() {
// Now we can use SAM conversion with our Kotlin interface
val upperCaseProcessor = Processor { input ->
input.uppercase()
}

val result = upperCaseProcessor.process("hello kotlin sam")
println(result)
}

Output:

HELLO KOTLIN SAM

Without the fun modifier, you would need to use an object expression to implement the interface.

Summary

Kotlin's SAM conversions provide an elegant way to work with Java functional interfaces by allowing you to:

  • Replace verbose anonymous class implementations with concise lambda expressions
  • Write more readable code when interacting with Java libraries
  • Reduce boilerplate code, especially in event handling scenarios

This feature is a perfect example of how Kotlin enhances Java interoperability while making your code more expressive and maintainable.

Additional Resources

Exercises

  1. Create a simple application that uses Java's Predicate interface with SAM conversion to filter a list.
  2. Implement a custom Kotlin functional interface and use it with SAM conversion.
  3. Write a small Swing or JavaFX application that demonstrates multiple uses of SAM conversions for different event listeners.
  4. Compare the readability and line count of code written with and without SAM conversions for a complex UI with multiple event handlers.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)