Kotlin's way of learning -- function definition and call

Function definition and call

Make function calls better

Named parameters

Using object-oriented functions such as Java, we must often encounter function calls, such as:

joinToString(list, "; ", "[", "]")

When we call this function for the first time, we are confused. We don't know the meaning of each parameter, which causes unnecessary trouble.

When a function defined by Kotlin is called, the name that can be displayed indicates the name of the parameter. If you specify the name of a parameter when calling a function, in order to avoid confusion, you need to specify the name of all i parameters, for example:

joinToString(list, separator = "; ", prefix = "[", postfix = "]")

Default parameter value

There is also a problem in Java, that is, there are too many overloaded methods in some classes, so we have to understand the meaning of each parameter and API.

In Kotlin, you can specify the default value of parameters when declaring functions, so as to avoid creating overloaded functions.

fun joinToString(
    collection: Collection<*>,
    separator: String = ", ",	//The default parameter value is "."
    prefix: String = "[",		//The default parameter value is "[“
    postfix: String = "]"		//The default parameter value is "]“
): String {...}

Then we can call this function as follows:

//General call function
joinToString(list
joinToString(list, ",", "", "")
joinToString(list, ";")
//Call the function as a named argument
joinToString(list, separator = "; ", prefix = "[", postfix = "]")
joinToString(list, postfix = "}", prefix = "{")

For the above different calls, we make the following summary:

  1. When using the general call syntax, the parameters must be given in the order of the parameters defined in the function declaration, and only the parameters at the end can be omitted.
  2. When calling a function with named parameters, you can omit some parameters in the middle, or give the parameters you need in any order.

Top level functions and properties

When developing in Java, we must have defined some tool classes, which contain some static tool methods for us to call, such as:

public class Util{
	public static String joinToString(...){
		...
	}
}

Using Kotlin does not need to be so troublesome. You can directly put this function on the top level of the code file as the top-level function without belonging to any class.

pacakage secondUit

fun joinToString(...): String{...}

The top-level attribute is very similar to the top-level function, except that it changes from a method to an attribute.

I understand that the top-level attributes can be roughly divided into three types:

  1. The top-level attribute modified by var represents variable attributes. It has getter and setter accessors, corresponding to variable static variables in Java.
  2. The top-level attribute modified by val indicates that it is only a readable attribute, only has a getter accessor, and corresponds to an immutable static variable in Java.
  3. The top-level attribute modified by const val indicates that it is only a readable attribute and only has a getter accessor, corresponding to the static final variable in Java.

Extension methods and properties

Just like its name, an extension function is to add a new method to an acceptance class, for example:

fun String.lastChar(): Char = this.get(this.length - 1)

The above code is to add a method lastChar() to the String class. The meaning of this function is to obtain the last character of the String.

The following text describes how to create an extension function format:

keyword fun Acceptance class.Extended function name: Extension function return type = ...

Calling an extension function is like an ordinary member method of a class:

println("Kotlin".lastChar())	//Print n

In the extension function, you can directly access other methods and properties of the extended class, just as the methods in this class access them.

However, the extension function cannot break the encapsulation of the extended class, that is, the extension method cannot access private or protected members.

In Kotlin, extension functions are not allowed to be overridden because Kotlin treats them as static functions.

The extension attributes and extension methods are particularly similar. We directly take the following examples:

val String.lastChar: Char = 
	get() = get(length - 1)
var StringBuilder.lastChar: Char
        get() = get(length - 1)
        set(value) {
                this.setCharAt(length - 1 , value)
        }

The above defines immutable and variable extension attributes respectively. Next, we try to access them:

println("Kotlin".lastChar) 	//Print n

val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)		//Print Kotlin!

Processing set: variable parameters, infix calls and library support

Extending the API of collections in Java

The collection in Kotlin is actually a collection in Java, which only extends the API.

val stringSet: List<String> = listOf("one", "two", "fourteenth")
println(stringSet.last())		//Print fourteenth

val numberSet: Collection<Int> = setOf(1, 5, 63)
println(numberSet.maxOrNull())		//Print 63

The last() function or maxOrNull() function here is declared as an extension function.

Variable parameters: let the function support any number of parameters

Remember the functions we used to create collections with kotlin?

val list = listOf(1, 2, 63, 33)

Take a look at the specific declaration of the listOf function:

public fun <T> listOf(vararg elements: T): List<T> = ...

We can see that the vararg modifier modifies the parameter elements, which shows that this parameter is a variable parameter. The difference from Java is that the modifier replaces the... Modifier

There is another difference between Kotlin and Java. When the parameters that need to be passed are wrapped in an array, the syntax of calling the function is different.

In Java, we can pass arrays as is, but Kotlin needs us to explicitly expand the array so that each element in the array is called as a separate element. We call it the expansion operator, that is, the * operator.

fun main(args: Array<String>){
    val list = listOf("args: ", *args)
    println(list)
}

Handling of key value pairs: infix calls and deconstruction declarations

When creating a map collection, we use the mapOf function to create:

val numberMap = mapOf(1 to "one", 2 to "two", 14 to "fourteen")

In fact, to here is also a special function call, which we call infix call.

The usage of 1 to "one" here is equivalent to 1 to(“one”)

Let's take a look at the declaration of the simple to function:

infix fun Any.to(other: Any) = Pair(this,other)

The infix modifier flag is used here because we need to use the infix symbol to call the function.

It can be seen that the to function will return an object of type pair. Pair is the class of the standard library in Kotlin. It represents a pair of elements.

What is the deconstruction statement?

val (number, name) = 1 to "one"

The to function is used to create a pair, which can be expanded with a deconstruction declaration.

In addition, our map(key, value) and withIndex function using collection class can use deconstruction declaration.

Local functions and extensions

We should write our code as concise as possible on the premise of ensuring functional integrity.

If we have a user class, we need to save it to the database after some judgment that the name and address are not empty:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User){
    if(user.name.isEmpty()){
        throw IllegalAccessException("Can't save user ${user.id}: Empty name!")
    }
    if (user.address.isEmpty()){
        throw IllegalAccessException("Can't save user ${user.id}: Empty address!")
    }
        //Logic saved to database (not written)
}

We will find that we can extract the above two short sentences and optimize the code:

fun saveUser(user: User){ 
    //Local functions can directly access the parameters of external functions
    fun validate(value:String, fileName: String){
        if(value.isEmpty()){
            throw IllegalAccessException("Can't save user ${user.id}: Empty $fileName!")
        }
    }

    validate(user.name,"name")
    validate(user.address, "address!")

    //Save logic (not written)
}

From the above code, we can see that the local function can access all parameters and variables in the function.

Of course, we can also use the extension function learned earlier for further optimization:

fun User.validateBeforeSave(){
    fun validate(value:String, fileName: String){
        if(value.isEmpty()){
            throw IllegalAccessException("Can't save user $id: Empty $fileName!")
        }
    }

    validate(name,"name")
    validate(address, "address!")
}

fun saveUser(user: User){ 
	user.validateBeforeSave()
	//Save logic (not written)
}

Keywords: kotlin

Added by globetrottingmike on Sat, 08 Jan 2022 10:35:37 +0200