Let’s start with a directory:

  • 1. Lambda expressions in Kotlin

  • 2. Why do you write this way?

  • 3. What is SAM transformation?

  • 4. Disambiguation of SAM transformation

  • 5. Limitations of SAM conversion

    • 5.1 Only Java is supported

    • 5.2 Only interfaces are supported, not abstract classes.

  • 6. Summary

  • 7. About the author

1. Lambda expressions in Kotlin

If you’re already using Koltin, or know something about it, you’re probably familiar with this:

// Code 1: Kotlin code

view.setOnClickListener{ println("click")

}Copy the code

↑ is equivalent to the following Java code ↓ :

// Code 2: Java code

view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("click"); }

});Copy the code

Like Java8, Kotlin supports Lambda expressions, as shown in code 1, which is a specific application of Labmda.

As you can see, using Lambda reduces a lot of redundancy, making code simpler and more elegant to write and more natural to read.

But have you ever wondered why Kotlin can write this way, and why Lambda is used here as an argument to setOnClickListener?

2. Why do you write this way?

In Kotlin, a Lambda is an anonymous function.

Code 1 is actually a shorthand for code 3:

// Code 3: Kotlin code

view.setOnClickListener({ v -> println("click")

})Copy the code

The shorthand for code 1 is based on two features:

1. If Lambda is the only argument to a function, then the function can be called without the parentheses

2. If Lambda represents an anonymous function that takes only one argument, omit its declaration and the -> symbol (the default is to name the omitted argument with it)

OK, it’s clear from the structure of code 3 that the view.setOnClickListener function here takes a Lambda as an argument. In Kotlin, what kind of function can take a Lambda (that is, another function) as an argument? Yes, higher order functions.

What is theHigher-order functions?

Higher-order functions are functions that use functions as arguments or return values.

This is one of the differences between Kotlin and Java, where higher-order functions are not supported. When we need to use similar concepts in Java, it is common to pass an anonymous class as a parameter and then implement some abstract method within it — as in code two above.

In fact, if you look at the definition of the view.setOnClickListener function from Kotlin’s code in Android Studio, you’ll see that the function signature is a higher-order function definition:

As shown above, you can see that the function signature is:

public final fun setOnClickListener(l: ((v:View!) ->Unit)!) : Unit

Of course, since methods are defined in Java, it also lists Java declarations, like this:

public void setOnClickListener(OnClickListener l)

As we know, Kotlin differs from many Java types, so when they call each other, there is a corresponding conversion.

The above conversion to the setOnClickListener method is easy to understand elsewhere, but it is more difficult to understand why the parameter type is converted from OnClickListener to (View) -> Unit.

(View) -> Unit is a function type that takes one argument of type View and returns Unit.

By the way: What isFunction types?

In contrast to Java, Kotlin has the concept of function types, that is, functions are typed, and a function’s type is represented by the types of all its parameters and the types of its function values. For example, (String, Int) -> Int denotes a function type where the first argument is String, the second argument is Int, and the return value is also Int.

It is this conversion of the parameter type that makes the setOnClickListener method a higher-order function in Kotlin, which is why it can use Lambda as an argument.

SAM Conversions (Single Abstract Method Conversions) are the main topics of this article.

3. What isSAM conversion?

Well, with all that said, here we are.

SAM Conversions, or Single Abstract Method Conversions, are transformations that have only a Single non-default Abstract Method interface — for interfaces that meet this condition (called SAM Type), In Kotlin it is possible to use Lambda directly — provided, of course, that the function type represented by Lambda matches the Chinese method of the interface.

As we know, OnClickListener is defined in Java like this:

// Code 4: OnClickListener interface definition in Java

public interface OnClickListener { void onClick(View v);

}Copy the code

The onClick function is of Type (View) -> Unit. So, in Kotlin, you can replace OnClickListener with a Lambda expression {println(“click”)} as an argument to the setOnClickListener function.

Note: the SAM transformation is not Kotlin’s original creation, it’s already in Java8, Scala.

4. Disambiguation of SAM transformation

The SAM transformation makes it much easier to call Java in Kotlin, and it works fine most of the time.

Of course, there are occasional exceptions. For example, consider the following code:

5 / / code

public class TestSAM {    SamType1 sam1;    SamType2 sam2;    public void setSam(SamType1 sam1) {        this.sam1 = sam1;    }    public void setSam(SamType2 sam2) {        this.sam2 = sam2;    }    public interface SamType1 {        void doSomething(int value);

   }    public interface SamType2 {        void doSomething2(int value);    }

}Copy the code

This code has the following characteristics:

  • TestSAM has two overloaded setSam methods,

  • And their arguments (SamType1, SamType2) are SAM Type interfaces.

  • The only abstract method of SamType1 and SamType2 has a function type of (Int) -> Unit.

It’s a weird situation, but it could happen. If Kotlin uses code like this directly, an error will be reported:

// Code 6: called in kotlin, this code is not compiled

TestSAM().setSam { println("dodo")

}Copy the code

The compiler does not know which interface in SamType2 or SamType1 the Lambda represents.

The solution is to manually specify the type of interface the Lambda needs to replace. There are two ways to specify this:

// Code 7: disambiguation

/ / way

TestSAM().setSam (SamType1 { println("dodo") })

2 / / way

TestSAM().setSam ({ println("dodo") } as SamType1) Copy the code

Of course, there is an alternative to using an instance of SamType1 instead of using the mechanism of SAM transformation:

// Code 8: Use an anonymous class that implements the interface as an argument

TestSAM().setSam(object:TestSAM.SamType1{

 override fun doSomething(value: Int){

   println("dodo")

 }

})Copy the code

This method works, but it’s just not as elegant as Lambda (elegance matters!!). .

5. Limitations of SAM conversion

There are two main limitations of SAM transformation in Kotin:

5.1 Only Java is supported

That is, Kotlin only applies to Java calls, not Kotlin calls

The official explanation is that Kotlin already has support for function types and higher-order functions, so there is no need to convert.

If you want to use similar operations that require Lambda as an argument, you should define higher-order functions that require the type of the function.

5.2 Only interfaces are supported, not abstract classes.

The official did not elaborate.

I think it’s to avoid confusion, because if you support abstract classes, there are a lot of things that need to be strengthened. Moreover, the abstract class itself is allowed to have a lot of logical code inside, which is simply called a Lambda, making it much more difficult to locate errors if they go wrong.

6. Summary

OK, that’s it.

In summary, SAM transformations are why Kotlin can use Lambda when calling Java code. Knowing how it works makes it easier to write code and solve the occasional problem faster.

In addition, I hope this article can help you. Recently started a wechat public account to share some Android related and unrelated dry goods.

Now that you’ve seen this, why don’t you press /scan the codeKeep an eye on?



7. About the author

http://www.barryzhang.com

https://github.com/barryhappy

http://www.jianshu.com/users/e4607fd59d0d