Default parameters for functions of the Kotlin learning tutorial

  • 2021-12-09 10:06:38
  • OfStack

Preface

In Java, it is not allowed to add default values to function parameters to prevent the problem of two meanings when default parameters are used together with function overloading. Consider the following example:


void func(p1: String, p2: String, p3: String = "default") {
	// do something
}

void func(String p1, String p2) {
	// do something
}

Assuming that the above code can be compiled, the compiler will not know which method to call when calling func ("p1", "p2"). Therefore, Java does not support default parameters, but it can still realize the function of default parameters by function overloading, which is also our most commonly used method:


void func(String p1, String p2, String p3) {
	// do something
}

void func(String p1, String p2) {
	func(a, b, "default");
}

Default parameters can also be implemented by overloading the above functions, but there is also an obvious problem, that is, if we want to support default parameters, we need to write a lot of template code, which seems to be not so convenient. However, Kotlin provides support for default parameters. Let's take a look at the support for default parameters in Kotlin and how it solves the problem of 2 meanings we mentioned at the beginning.

Use

In Kotlin, it is also very simple to use default parameters, and you can assign values directly in the function definition:


fun func(p1: String, p2: String, p3: String = "default") {
	// do something
}

In the above function definition, the default value of c is default, and func ("p1", "p2") can be called in this way. Similarly, for constructors, you can also specify default values:


class TestDefaultParameters (
 val name: String,
 val type: String = "default"
){}

So what if you want to call an kotlin function with default arguments in Java? If you call func ("p1", "p2") directly in Java, the compiler will report an error, so you need to annotate the Jvm overload on the method of kotlin:


@JvmOverloads
fun func(p1: String, p2: String, p3: String = "default") {
	// do something
}

Analyse

Next, let's look at how Kotlin implements the default parameters. First, write an example as follows:


fun main(args: Array<String>) {
 val testDefaultParameters = TestDefaultParameters("")
 testDefaultParameters.func("position1", "position2")
}

class TestDefaultParameters (
 val name: String,
 val type: String = "default"
){
 @JvmOverloads
 fun func(p1: String, p2: String, p3: String = "default") {
  // do something
 }

}

The above function definition Decompile of func is implemented as Java:


@JvmOverloads
public final void func(@NotNull String p1, @NotNull String p2, @NotNull String p3) {
 Intrinsics.checkParameterIsNotNull(p1, "p1");
 Intrinsics.checkParameterIsNotNull(p2, "p2");
 Intrinsics.checkParameterIsNotNull(p3, "p3");
}

// $FF: synthetic method
public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) {
 if ((var4 & 4) != 0) {
 	var3 = "default";
	}
	var0.func(var1, var2, var3);
}

@JvmOverloads
public final void func(@NotNull String p1, @NotNull String p2) {
	func$default(this, p1, p2, (String)null, 4, (Object)null);
}
...
//  Call func Function 
TestDefaultParameters.func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null);

I omitted some code in the middle. As you can see, the Kotlin compiler has generated three overloaded methods of func for us. Let's look at what functions are under 1 in turn:

The first function I saw first is func with three parameters, and the inside of the function is checked for null safety, which is the characteristic of kotlin. Because the parameters are not empty when declaring the function, it is necessary to check whether the parameters are empty and throw an exception. The second function, whose name is func $default, is not a method overload of func, but a new method, which is the key method to realize the default parameters. Press the table here for the time being and explain it in detail later. The third function is still an overload of the func method, which you can see has only two arguments, and the second method is called internally. In fact, this method is called for Java. Since we declare the func function as @ JvmOverloads, when Java calls func without passing the default parameter, this method is actually called. If @ JvmOverloads is removed, there is no such method.

After understanding the functions of the three methods, I mainly look at the second method under 1:


// $FF: synthetic method
public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) {
 if ((var4 & 4) != 0) {
 	var3 = "default";
	}
	var0.func(var1, var2, var3);
}

It can be seen that this method has six parameters, var0 is an Class object, var1 ~ var3 correspond to three parameters of func function respectively, and then there is an var4 of int type and an var5 of Object type. var5 is null in most cases, and the secret of default parameter implementation is mainly on var 4. Let's see how the calling function is called when using default parameters:


// kotlin
func("position1", "position2")
 
// Decompile
func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null)

See that the value of var4 is 4. var4 = 222 ^ 222 = 4 because the primitive function is that the third parameter is the default parameter, that is, the parameter at the position position = 2
Before looking at the previous method implementation of func $default:


if ((var4 & 4) != 0) {
 var3 = "default";
}

When var4 & 4! = 0, the value of var3 is equal to the default parameter. As you can see, the int parameter of the func $default function indicates which parameter's value is the default parameter. Here's a slightly more complicated example:


void func(String p1, String p2, String p3) {
	// do something
}

void func(String p1, String p2) {
	func(a, b, "default");
}
0

This time, all three parameters have default values, and the value of p2 is specified as "position2" with the name parameter when calling. Let's take a look at the code after Decompile:


void func(String p1, String p2, String p3) {
	// do something
}

void func(String p1, String p2) {
	func(a, b, "default");
}
1

As you can see, there are three judgments in the method body this time, because all three parameters have default values, and the passed parameter is 5, because the parameters of index=0 and index=2 are the default parameters when the function is called, so var4 = 20 +222 0 + 2 ^ 220 +22 = 5.

Here is a general explanation of why this design is needed:
It is very suitable for bit operation when writing multiple conditions, such as permission judgment, index judgment and so on. For example, in the above example, the index of the parameter can be expressed as a binary number of the power of index of 2, for example, index = 0, i.e. 202 ^ 020, which is expressed as 0001 in binary, index = 1, i.e. 212 ^ 121, which is expressed as 0010 (which can be regarded as the position of 1 in binary number, i.e., index).
What if there are multiple positions such as index=0 and index=2? It can be expressed as: 0101. That is 20 + 22 = 52 ^ 0 + 2 ^ 2 = 520 + 22 = 5. When performing bitwise AND operation with the target ES 130EN, if it is not equal to 0, it means that the ES 131EN meets the conditions. Otherwise it doesn't match.

Looking back at the above func $default function body, it is clear that the default value is not used when the default value is not used.

In the above example, we see that the function with the default parameter has an Object parameter that is always null after Decompile, and it is not used. So what's the use of this parameter? This parameter is used when trying to override a function with a default parameter. For example, the following example:


void func(String p1, String p2, String p3) {
	// do something
}

void func(String p1, String p2) {
	func(a, b, "default");
}
2

Compile the above code 1:


// $FF: synthetic method
public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) {
  if (var5 != null) {
    throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: func");
  } else {
    if ((var4 & 1) != 0) {
     var1 = "position1";
    }

    if ((var4 & 2) != 0) {
     var2 = "position2";
    }

    if ((var4 & 4) != 0) {
     var3 = "position3";
    }

    var0.func(var1, var2, var3);
}
}

As you can see, using default parameters when calling superclasses is not allowed in the current version (it may be allowed in the future). This is what the Object parameter is for.

Summarize

The above is a summary of the default parameter implementation of kotlin.


Related articles: