1. Lambda expressions in Kotlin
If you have already started using Koltin, or have some understanding of it, then you must be familiar with this writing method: < /p>
// Code 1: Kotlin code view.setOnClickListener{
println("click")
}1234
It is similar to the following The Java code is equivalent:
//Code 2: java code view.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
System.out.println("click");
}
});1234567
Like Java8, Kotlin is Supporting Lambda expressions, as shown in code 1, is a specific application of Lambda.
It can be seen that using lambda reduces a lot of redundancy, making the code more concise and elegant to write, and reading more smoothly and naturally.
But, have you ever thought about why Kotlin can be written like this and why lambda can be used here?
2. Why can it be written like this?
In Kotlin, a Lambda is an anonymous function.
Code one is actually the abbreviation of code three below:
// Code three: Kotlin code view.setOnClickListener({
v -> println(" click")
})1234
The reason why it is abbreviated as code 1 is based on these two characteristics:
If lambda is the only one of a function parameters, then you can omit the parentheses when calling this function
If the anonymous function represented by lambda has only one parameter, then its declaration and -> symbol can be omitted (it will be used to name the omitted parameter by default Naming)
OK, from the structure of code three, it can be seen more clearly that the view.setOnClickListener function here receives a lambda as a parameter. In Kotlin, what kind of function can take lambda (that is, another function) as a parameter?
——Yes, it is a higher-order function.
What are higher-order functions?
Higher-order functions are functions that use functions as parameters or return values.
This is one of the differences between Kotlin and Java. There is no support for higher-order functions in Java (java8 has higher-order functions). When we need to use similar concepts in Java, the usual approach is to pass an anonymous class as a parameter and then implement some of its abstract methods - just like the code 2 above.
In fact, if you look at the definition of the view.setOnClickListener function from the Kotlin code in Android Studio, you will find that the function signature you see is the definition of a higher-order function:
< p>Function signature tipsAs shown in the picture above, the function signature is:
public final fun setOnClickListener(l: ((v:View!)->Unit)!): Unit
Of course, because the method is defined in Java, it also lists the Java declaration, like this:
public void setOnClickListener(OnClickListener l)
We know that many types of Kotlin and Java are different, so when they call each other, there will be a conversion according to the corresponding relationship.
As for the conversion of the setOnClickListener method above, it is easy to understand elsewhere. What is more difficult to understand is why the parameters are converted from the OnClickListener type to (View) -> Unit.
(View) -> Unit? is a function type, which represents a function that receives a parameter of View type and returns Unit.
It is this conversion of parameter types that makes the setOnClickListener method become a higher-order function in Kotlin, which is why it can use lambda as a parameter.
And this conversion is the protagonist of this article mentioned in our title - SAM conversion (Single Abstract Method Conversions).
3. What is SAM conversion?
Okay, after talking so much, I finally got to the point.
SAM conversion, that is, Single Abstract Method Conversions, is the conversion of interfaces with only a single non-default abstract method - for interfaces that meet this condition (called ?SAM Type?), you can directly convert it in Kotlin Use Lambda to represent it - of course, the premise is that the function type represented by Lambda can match the method in the interface.
The definition of OnClickListener in java is as follows: // Code 4: The definition of OnClickListener interface in java public interface OnClickListener { void onClick(View v);
}1234
—— It happens to be a qualified ?SAM Type, and the type of the onClick function is ?(View) -> Unit. Therefore, in Kotlin, the lambda expression { println("click")} can be used instead of OnClickListener as the parameter of the setOnClickListener function.
4. Disambiguation of SAM conversion
The existence of SAM conversion makes it easier for us to call java in Kotlin. It can work most of the time. very good.
Of course, there are occasional exceptions. For example, consider the following code: // Code 5 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);
}
}123456789101112131415161718
< p>—— TestSAM has two overloaded setSam methods,—— and their parameters (SamType1, SamType2) are all SAM Type interfaces.
——And the function type of the only abstract method of SamType1 and SamType2 is ?(Int) -> Unit?.
o(╯□╰)o
This situation is more like hanging rail, but it may still happen. At this time, if you use a similar code directly in Kotlin, an error will be reported: // Code 6: Called in kotlin, this code cannot be compiled TestSAM().setSam {
println ("dodo") ?
}1234
will prompt this ambiguity. The compiler does not know which interface between SamType1 and SamType2 this Lambda represents.
The solution is to manually indicate the interface type that Lambda needs to replace. There are two ways to indicate it: // Code 7: Disambiguation Elimination // Method 1 TestSAM().setSam (SamType1 { println(" dodo") ?})
// Method 2 TestSAM().setSam ({ println("dodo") } as SamType1)12345
Of course, there is also another method: Instead of using the SAM conversion mechanism, directly use an instance of SamType1 as a parameter: // Code 8: Use an anonymous class that implements the interface as a parameter TestSAM().setSam(object : TestSAM.SamType1 { override fun doSomething(value : Int) {
println("dodo")
}
})123456
Of course this method is also possible. Just compared with lambda, it seems less elegant (elegance is very important!!!).
5. Limitations of SAM conversion
There are two main limitations of SAM conversion:
5.1 only supports java
That is, only applicable The call to java in Kotlin does not support the call to Kotlin
The official explanation is that Kotlin itself already has support for function types and higher-order functions, so there is no need to convert it.
If you want to use similar operations that require lambda as parameters, you should define a higher-order function yourself that requires a specified function type.
5.2 only supports interfaces, not abstract classes.
The official did not provide any further explanation.
I think it is probably to avoid confusion. After all, if abstract classes are supported, there will be too many places that need to be forced. Moreover, the abstract class itself allows a lot of logic code inside. If it is directly abbreviated as a Lambda, it will be much more difficult to locate the error if something goes wrong.
6. Summary
OK, that’s it.
To sum up, SAM conversion is the reason why kotlin can use Lambda when calling java code. Understanding its principles can make us more comfortable writing code, and we can solve problems better and faster when occasional problems arise.