keywords
Extension function
Higher-order functions
Inline function
The ViewBindingDelegate library, mentioned in the previous article, simplifies the use of ViewBinding in Android projects by using Kotlin delegates. I didn’t want to write any more ViewBindingDelegate analysis, but there are some kotlin points that need to be noted.
Vbpd-full /.. ViewBinding method in/activityViewBinding.kt file:
. @JvmName("inflateViewBindingActivity") public inline fun <reified T : ViewBinding> ComponentActivity.viewBinding( createMethod: CreateMethod = CreateMethod.BIND ) = viewBinding(T::class.java, createMethod) ...Copy the code
You can see that this is an extension function. Here comes the first point!
Extension function
Extension function definition: Extends new functionality without changing existing classes.
First, make sure that extension functions are for classes, providing new functionality for classes. How does that work? Look at the following example:
I define a String extension function to print its length:
private fun String.printLength() {
}
Copy the code
Let’s convert it to Java code
private final void printLength(String $this$printLength) {
}
Copy the code
This is a good understanding of the nature of the extension function: the essence of an extension function is a normal function, it does not make any changes to the original class, the difference is that it defaults to the class object as a function parameter.
Inside the extension function you can use the this keyword to access the object passed before the dot, which is the $this$printLength argument in the example above. And this can also be omitted.
private fun String.printLength() {
Log.e("length", "$length")
}
Copy the code
Convert to Java code as follows:
private final void printLength(String $this$printLength) {
Log.e("length", String.valueOf($this$printLength.length()));
}
Copy the code
Val name = "printLength" name.printlength ()Copy the code
The output is 2.
This is why the Activity variable pops up in the ViewBindingDelegate project:
@JvmName("viewBindingActivity")
public fun <T : ViewBinding> ComponentActivity.viewBinding(
viewBindingClass: Class<T>,
rootViewProvider: (ComponentActivity) -> View
): ViewBindingProperty<ComponentActivity, T> {
return viewBinding { activity -> ViewBindingCache.getBind(viewBindingClass).bind(rootViewProvider(activity)) }
}
Copy the code
conclusion
The difference between an extension function and a normal function:
- Form: Extension functions are prefixed by the extended type
The type being extended. Function name ()
- Usage: The extension function takes the extended target class as its first argument type. This parameter is not visible, but can be passed
this
The (omitted) keyword accesses the extended target class object.
Inline function
The same code as above:
@JvmName("inflateViewBindingActivity")
public inline fun <reified T : ViewBinding> ComponentActivity.viewBinding(
createMethod: CreateMethod = CreateMethod.BIND
) = viewBinding(T::class.java, createMethod)
Copy the code
Two unfamiliar keywords are found: inline and reified. Two concepts should be understood before introducing them:
Higher-order functions
: A function that can use a function as an argument or return value.Inline function
Use:inline
Modified function. Eliminates resource consumption when using higher-order functions.
Higher-order functions
Let’s start with a normal function, adding two numbers:
private fun addTwoNumbers(firstNumber: Int, secondNumber: Int): Int {
return firstNumber + secondNumber
}
Copy the code
Quite simply, there is nothing to say. But found a problem, if the calculation of two numbers subtraction, multiplication, division need to define three functions, but do not want to do so, how to do. This is where higher-order functions come in. The new function looks like this:
private fun calculateTwoNumber(
firstNumber: Int,
secondNumber: Int,
calculate: (Int, Int) -> Int
): Int {
return calculate(firstNumber, secondNumber)
}
Copy the code
The function calculateTwoNumber takes a function parameter calculate, and the Calculate function takes two ints and returns an Int result. CalculateTwoNumber is a higher-order function.
Let’s switch to Java:
private final int calculateTwoNumber(int firstNumber, int secondNumber, Function2 calculate) {
return ((Number)calculate.invoke(firstNumber, secondNumber)).intValue();
}
Copy the code
The parameter type for calculate is Function2, and Function4, 5, 6, 7, 8, and 9 are available. There are, in fact, 23 interface types in Function0 to 22.
public interface Function<out R> public interface Function0<out R> : Function<R> { public operator fun invoke(): R } public interface Function1<in P1, out R> : Function<R> { public operator fun invoke(p1: P1): Function */ public interface Function2<in P1, in P2, out R> R */ public operator fun invoke(P1: P1, P2: P2): R}... public interface Function10<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, out R> : Function<R> { public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R } ...Copy the code
The difference between them is the difference in the number of passed parameters.
It is now clear that higher-order functions are actually compiled to Java as functions that have a Function argument type or return value of Function type.
Kotlin prepared two ways to get Function objects:
lambda
The expression;- Anonymous function;
The syntax for lambda expressions is as follows:
{parameter declaration -> function body}
Note the following when using lambda:
- The parameter declaration type is optional, that is, the parameter type may not be marked.
{a: Int, b: Int -> a + b} is equivalent to {a, b -> a + b}Copy the code
- If the last argument to a higher-order function is a function, it is passed as the corresponding argument
lambda
Expressions can be placed outside parentheses.
CalculateTwoNumber (1, 2, {a: Int, b: Int -> a + b}) is equivalent to calculateTwoNumber(1, 2) {a: Int, b: Int -> a + b}Copy the code
- If the
lambda
The expression is the only argument when called, so the parentheses can be omitted entirely.
run { println("...") }
Copy the code
- when
lambda
An expression with only one argument can be ignored without declaring a unique argument->
.
val ints = listOf<Int>()
ints.filter { it > 0 }
Copy the code
lambda
The expression returns the value of the last expression by defaultreturn
Displays the specified return value.
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
Copy the code
lambda
Cannot be used directly in an expressionreturn
Want to quitlambda
You need tags. However, if the function passed is inline (inline functions are explained below), it can be used directlyreturn
.
fun ordinaryFunction(block: () -> Unit) { println("hi!" )} fun foo() {ordinaryFunction {return // error: cannot make 'foo' return return@ordinaryFunction // correct}} fun main() {foo()}Copy the code
Lambda expression syntax lacks the ability to specify the return type of a function. In most cases the return type can be inferred automatically. But if you do need to specify it explicitly, then you need to use anonymous functions.
Anonymous functions differ from regular functions in that they have no function name. Everything else is exactly the same as the regular function.
fun(x: Int, y: Int): Int {
return x + y
}
Copy the code
If the function return type can be derived then the return type can also be omitted.
Higher order function optimization – inline functions
Now that we know what higher-order functions are, let’s use higher-order functions to calculate the sum from 0 to 10:
var result = 0 for (i in 0.. 10) { result = calculateTwoNumber(result, i) { a: Int, b: Int -> a + b } } Log.e("highfun", "$result") // 55Copy the code
Perfect! The result is clearly correct. Take a look at the compiled code:
int result = 0;
int i = 0;
for(byte var4 = 10; i <= var4; ++i) {
result = this.calculateTwoNumber(result, i, (Function2)null.INSTANCE);
}
Log.e("highfun", String.valueOf(result));
Copy the code
You can see that Function instances are created for each loop, which can result in a large number of objects in a large number of loops, affecting memory, which is obviously optimized. There are two optimization methods:
Optimization one: Define lambda outside of the loop.
val addCalculate = { a: Int, b: Int -> a + b } var result = 0 for (i in 0.. 10) { result = calculateTwoNumber(result, i, addCalculate) }Copy the code
Optimization 2: Use inline to modify higher-order functions as inline functions.
private inline fun calculateTwoNumber(
firstNumber: Int,
secondNumber: Int,
calculate: (Int, Int) -> Int
): Int {
return calculate(firstNumber, secondNumber)
}
Copy the code
View the encoded code after using inline to modify higher-order functions:
for(byte var4 = 10; i <= var4; ++i) {
int $i$f$calculateTwoNumber = false;
int var9 = false;
result += i;
}
Copy the code
You can see that the Function body of the lambda expression is added to where the expression is called, thus avoiding the creation of a Function object.
Note: While inlining improves performance, it also results in more code being generated, so avoid functions that are too heavily inlined.
Noinline, crossinline
As mentioned above, a return can be used in lambda expressions passed to inline functions. Let’s look at the following example:
private fun callFunction() { inlined { Log.e("inline", "2") return } } private inline fun inlined(body: () - > Unit) {the e (" inline ", "1") body () the e (" inline ", "3")} output -- -- -- -- -- - e/inline: 1 e/inline: 2Copy the code
You can see that there are three log prints in the code, but only two in the output. In this case, return terminates the function when it outputs the last log message. Instead of using return directly, use the return@ tag when using inline functions. Modify the code:
Private fun callFunction() {inlined {log.e ("inline", "2") return@inlined}} ------ e /inline: 2 E/inline: 3Copy the code
Kotlin also provides two modifiers to help restrict direct use of returns in lambdas:
noinline
crossinline
Noinline If you want to inline only a portion of the lambda expression arguments passed to an inline function, you can use the noinline modifier to mark function arguments that you do not want inline:
Inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {... }Copy the code
Lambda expressions that can be inlined can only be called inside inline functions or passed as arguments that can be inlined, but noinline can be manipulated in any way we like: assigning to variables, passing to other higher-order functions, and so on.
Use the return@ tag when passing a lambda expression to a function argument that uses the noinline modifier. Modify the above example:
private inline fun inlined(noinline body: () - > Unit) {the e (" inline ", "1") body () the e (" inline ", "3")} output -- -- -- -- -- - e/inline: 1 e/inline: 2 e/inline: 3Copy the code
But there is also a problem with noinline. Let’s look at the compiled Java code:
private final void callFunction() {
Function0 body$iv = (Function0)null.INSTANCE;
int $i$f$inlined = false;
Log.e("inline", "1");
body$iv.invoke();
Log.e("inline", "3");
}
Copy the code
It looks exactly like a higher-order function call without the inline modifier. And you’ll see a warning message:
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of Functional types means that if an inline function has no function parameters that can be inlined and no type parameters that can be specified, then it is probably not beneficial (if you are sure that you need to inline, Turn this warning off with the @suppress (“NOTHING_TO_INLINE”) annotation.
Is it possible to inline functions and ensure that lanmbda does not use return directly in its parameters?
Crossinline Crossinline and noinline can both restrict lambda arguments from using return directly, the difference being that the function arguments modified by Crossinline are still inline. Modify the above example:
private inline fun inlined(crossinline body: () -> Unit) {
Log.e("inline", "1")
body()
Log.e("inline", "3")
}
Copy the code
Look at the compiled Java code:
private final void callFunction() {
int $i$f$inlined = false;
Log.e("inline", "1");
int var3 = false;
Log.e("inline", "2");
Log.e("inline", "3");
}
Copy the code
Specifies the type of argument
Inline inline functions also provide another interesting capability: reified.
Reified mainly simplifies the ability to access type parameters, as shown in the following code:
private inline fun <T: Activity> inlined(clazz: Class<T>) {body() log.e ("inline", "${clazz.name}")}Copy the code
There’s nothing to ask you, it just doesn’t look very elegant, so what to do? Use Reified for a makeover:
private inline fun <reified T: Activity> inlined() {body() log.e ("inline", "${T::class.java.name}")}Copy the code
Viewing compiled Java code makes no difference. It’s just so easy!
conclusion
This section mainly introduces kotlin’s higher-order functions and inline functions. Higher-order functions can use functions as arguments or return values, but there are performance costs associated with using higher-order functions. You can avoid performance costs by using inline modifiers to inline functions, and avoid functions that are too inlined to increase the amount of code. In addition, the noinline, Crossinline modifier can restrict the use of the return keyword directly in lambda arguments to avoid interfering with function execution. Inline functions also provide reified to simplify the use of type parameters in functions.
Welcome to leave a message to exchange learning!