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 typeThe 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 passedthisThe (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 functionUse:inlineModified 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:

  • lambdaThe expression;
  • Anonymous function;

The syntax for lambda expressions is as follows:

{parameter declaration -> function body}

Note the following when using lambda:

  1. 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
  1. If the last argument to a higher-order function is a function, it is passed as the corresponding argumentlambdaExpressions 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
  1. If thelambdaThe expression is the only argument when called, so the parentheses can be omitted entirely.
run { println("...") }
Copy the code
  1. whenlambdaAn expression with only one argument can be ignored without declaring a unique argument->.
val ints = listOf<Int>()
ints.filter { it > 0 }
Copy the code
  1. lambdaThe expression returns the value of the last expression by defaultreturnDisplays the specified return value.
ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}
Copy the code
  1. lambdaCannot be used directly in an expressionreturnWant to quitlambdaYou 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!