SAM Conversions in Kotlin

Categories:
4 minute read
SAM (Single Abstract Method) conversions are a powerful feature in Kotlin that allows for more concise and expressive code when working with interfaces that have only one abstract method. This comprehensive guide explores SAM conversions, their usage patterns, and best practices in Kotlin programming.
Understanding SAM Conversions
What is a SAM Interface?
A SAM interface is an interface with a Single Abstract Method. In Java, these are often used for callbacks and event handlers. Common examples include Runnable, Callable, and Comparator.
// Java SAM interface
public interface OnClickListener {
void onClick(View view);
}
// Kotlin usage with SAM conversion
button.setOnClickListener { view ->
println("Button clicked!")
}
SAM Conversions in Java Interop
Basic Usage
When working with Java SAM interfaces, Kotlin provides automatic conversion:
class JavaInteropExample {
fun setupJavaThread() {
// SAM conversion for Java's Runnable
val thread = Thread {
println("Running in new thread")
}
// Equivalent to:
val verboseThread = Thread(Runnable {
println("Running in new thread")
})
}
}
Common Java SAM Interfaces
Working with popular Java SAM interfaces:
class CommonSamExample {
fun demonstrateCommonSAMs() {
// Comparator
val comparator = Comparator<String> { a, b ->
a.length - b.length
}
// Callable
val callable = Callable {
"Result from callable"
}
// Consumer
val consumer = Consumer<String> {
println(it)
}
}
}
Kotlin SAM Interfaces
Creating SAM Interfaces in Kotlin
To create a SAM interface in Kotlin that supports conversion, use the fun
interface:
fun interface Processor {
fun process(input: String): Int
}
class KotlinSamExample {
fun usageExample() {
// SAM conversion for Kotlin interface
val processor = Processor { input ->
input.length
}
// Usage
val result = processor.process("Hello")
}
}
Multiple Function Interfaces
Only interfaces with exactly one abstract method can be SAM converted:
// Not a SAM interface - multiple abstract methods
interface MultiFunction {
fun first()
fun second()
}
// SAM interface with one abstract and one default method
fun interface ValidSam {
fun execute()
fun default() {
println("Default implementation")
}
}
Advanced SAM Conversions
Generic SAM Interfaces
Working with generic SAM interfaces:
fun interface Transformer<T, R> {
fun transform(input: T): R
}
class GenericSamExample {
fun demonstrateGenericSAM() {
// String to Int transformer
val lengthTransformer = Transformer<String, Int> { str ->
str.length
}
// Int to String transformer
val stringTransformer = Transformer<Int, String> { num ->
num.toString()
}
// Usage
val length = lengthTransformer.transform("Hello")
val string = stringTransformer.transform(42)
}
}
SAM with Receivers
Creating SAM interfaces with receivers:
fun interface StringProcessor {
fun String.process(): Int
}
class ReceiverSamExample {
fun demonstrateReceiverSAM() {
val processor = StringProcessor {
// 'this' refers to String
this.length
}
// Usage
val result = with(processor) {
"Hello".process()
}
}
}
Best Practices
1. Type Inference
Let Kotlin’s type inference work with SAM conversions:
class TypeInferenceExample {
fun demonstrate() {
// Good - let type inference work
val handler = EventHandler { event ->
processEvent(event)
}
// Unnecessary - explicit types
val verboseHandler: EventHandler = EventHandler { event: Event ->
processEvent(event)
}
}
}
2. Function References
Use function references when appropriate:
class FunctionReferenceExample {
private fun processItem(item: String) {
println(item)
}
fun demonstrate() {
// Using lambda
val processor1 = ItemProcessor { item ->
processItem(item)
}
// Using function reference - more concise
val processor2 = ItemProcessor(::processItem)
}
}
3. Context Preservation
Be mindful of context when using SAM conversions:
class ContextExample {
private var counter = 0
fun setupHandlers() {
// Captures context
val handler = EventHandler {
counter++
println("Event count: $counter")
}
}
}
Common Patterns
Builder Pattern
Using SAM conversions in builders:
fun interface BuilderAction {
fun apply(builder: StringBuilder)
}
class StringBuilderWrapper {
private val builder = StringBuilder()
fun addContent(action: BuilderAction) {
action.apply(builder)
}
fun build() = builder.toString()
}
// Usage
fun buildString(): String {
val wrapper = StringBuilderWrapper()
wrapper.addContent { it.append("Hello") }
wrapper.addContent { it.append(" World") }
return wrapper.build()
}
Event Handling
Simplified event handling with SAM conversions:
fun interface EventListener<T> {
fun onEvent(event: T)
}
class EventManager<T> {
private val listeners = mutableListOf<EventListener<T>>()
fun addListener(listener: EventListener<T>) {
listeners.add(listener)
}
fun fireEvent(event: T) {
listeners.forEach { it.onEvent(event) }
}
}
// Usage
class EventExample {
fun setupEvents() {
val manager = EventManager<String>()
manager.addListener { event ->
println("Event received: $event")
}
}
}
Conclusion
SAM conversions in Kotlin provide a powerful way to work with single-method interfaces, whether from Java or Kotlin. Key points to remember:
- Use
fun interface
for Kotlin SAM interfaces - Leverage type inference for cleaner code
- Consider function references when appropriate
- Be mindful of context capture
- Use SAM conversions to create expressive DSLs and APIs
By understanding and properly utilizing SAM conversions, you can write more concise and expressive code while maintaining readability and functionality. Experiment with different patterns and best practices to find the most effective approach for your Kotlin projects.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.