Quick Tip: Write Cleaner Code With Kotlin SAM Conversions

If you are an experienced Android application developer, you’re probably used to the verbosity of Java 7. As a result, you might be finding Kotlin’s concise syntax, which is geared towards functional programmers, slightly unsettling.

One common problem beginners encounter while learning Kotlin is understanding how it expects you to work with Java interfaces that contain a single method. Such interfaces are ubiquitous in the Android world and are often referred to as SAM interfaces, where SAM is short for Single Abstract Method.

In this short tutorial, you’ll learn everything you need to know to aptly use Java’s SAM interfaces in Kotlin code.

1. What Is a SAM Conversion?

When you want to make use of a Java interface containing a single method in your Kotlin code, you don’t have to manually create an anonymous class that implements it. Instead, you can use a lambda expression. Thanks to a process called SAM conversion, Kotlin can transparently convert any lambda expression whose signature matches that of the interface’s single method into an instance of an anonymous class that implements the interface.

For example, consider the following one-method Java interface:

public interface Adder {
    public void add(int a, int b);
}

A naive and Java 7-like approach to using the above interface would involve working with an object expression and would look like this:

// Creating instance of an anonymous class
// using the object keyword
val adder = object : Adder {
    override fun add(a: Int, b: Int): Int {
        return a + b
    }
}

That’s a lot of unnecessary code, which is also not very readable. By leveraging Kotlin’s SAM conversion facility, however, you can write the following equivalent code instead:

// Creating instance using a lambda
val adder = Adder { a, b -> a + b }

As you can see, we’ve now replaced the anonymous class with a short lambda expression, which is prefixed with the name of the interface. Note that the number of arguments the lambda expression takes is equal to the number of parameters in the signature of the interface’s method.

2. SAM Conversions in Function Calls

While working with Java classes having methods that take SAM types as their arguments, you can further simplify the above syntax. For example, consider the following Java class, which contains a method that expects an object implementing the Adder interface:

public class Calculator {
    private Adder adder;

    public void setAdder(Adder adder) {
        this.adder = adder;
    }

    public void add(int a, int b) {
        Log.d("CALCULATOR", "Sum is " + adder.add(a,b));
    }
}

In your Kotlin code, you can now directly pass a lambda expression to the setAdder() method, without prefixing it with the name of the Adder interface.

val calculator = Calculator()
calculator.setAdder({ a, b -> a+b })

It is worth noting that while calling a method that takes a SAM type as its only argument, you are free to skip the parenthesis to make your code even more concise.

calculator.setAdder { a, b -> a+b }

3. SAM Conversions Without Lambdas

If you think lambda expressions are confusing, I’ve got good news for you: SAM conversions work just fine with ordinary functions too. For example, consider the following function whose signature matches that of the Adder interface’s method:

fun myCustomAdd(a:Int , b:Int):Int =
    if (a+b < 100)
        -1
    else if (a+b < 200)
        0
    else
        a+b

Kotlin allows you to directly pass the myCustomAdd() function as an argument to the setAdder() method of the Calculator class. Don’t forget to reference the method using the :: operator. Here’s how:

calculator.setAdder (this::myCustomAdd)

4. The it Variable

Many times, SAM interfaces contain one-parameter methods. A one-parameter method, as its name suggests, has only one parameter in its signature. While working with such interfaces, Kotlin allows you to omit the parameter in your lambda expression’s signature and use an implicit variable called it in the expression’s body. To make things clearer, consider the following Java interface:

public interface Doubler {
    public int doubleIt(int number);
}

While using the Doubler interface in your Kotlin code, you don’t have to explicitly mention the number parameter in your lambda expression’s signature. Instead, you can simply refer to it as it.

// This lambda expression using the it variable
val doubler1 = Doubler { 2*it }

// is equivalent to this ordinary lambda expression
val doubler2 = Doubler { number -> 2*number }

5. SAM Interfaces in Kotlin

As a Java developer, you might be inclined to create SAM interfaces in Kotlin. Doing so, however, is usually not a good idea. If you create a SAM interface in Kotlin, or create a Kotlin method that expects an object implementing a SAM interface as an argument, the SAM conversion facility will not be available to you—SAM conversion is a Java-interoperability feature and is limited to Java classes and interfaces only.

Because Kotlin supports higher-order functions—functions that can take other functions as arguments—you’ll never need to create SAM interfaces in it. For example, if the Calculator class is rewritten in Kotlin, its setAdder() method can be written such that it directly takes a function as its argument, instead of an object that implements the Adder interface.

class Calculator {
    var adder:(a:Int, b:Int)->Int = {a,b -> 0} 
                                    // Default implementation
                                    // Setter is available by default

    fun add(a:Int, b:Int) {
        Log.d("CALCULATOR", "Sum is " + adder(a,b))
    }
}

While using the above class, you can set adder to a function or a lambda expression using the = operator. The following code shows you how:

val calculator = Calculator()
calculator.adder = this::myCustomAdd
// OR
calculator.adder = {a,b -> a+b}

Conclusion

Android’s APIs are largely written in Java, and many use SAM interfaces extensively. The same can be said of most third-party libraries too. By using the techniques you learned in this tutorial, you can work with them in your Kotlin code in a concise and easy-to-read way.

To learn more about Kotlin’s Java-interoperability features, do refer to the official documentation. And do check out some of our other tutorials on Android app development!

Leave a Reply

Your email address will not be published. Required fields are marked *