1. Overview

Calling functions with lots of arguments can result in hard to read code or mistakes where inputs are accidentally switched around. As we increase the number of optional inputs to a function, providing defaults via function overloading can also become cumbersome.

We can solve all of this by using two language features of Kotlin.

In this tutorial, we’ll discuss Kotlin’s language support for named and default arguments.

2. Positional Arguments

Before we investigate Kotlin’s named arguments, let’s remind ourselves of the challenges of positional arguments.

We use positional arguments all the time. Positional arguments are method arguments that must be passed in the same order they are declared. They’re a bit like a fixed-size array of inputs in a strict order, with strict types.

2.1. The Problem With Positional Arguments

When a function with several positional arguments is invoked, the calling code does not always clearly explain which parameter is which.

Let’s look at an example:

fun resizePane(newSize: Int, forceResize: Boolean, noAnimation: Boolean) {
    println("The parameters are newSize = $newSize, forceResize = $forceResize, noAnimation = $noAnimation")
}

We’ll invoke this using positional argument:

resizePane(10, true, false)

In the absence of IDE support or without looking at the resizePane function declaration, it is hard to understand what the arguments in the call to resizePane represent. We might imagine that the numeric value is a size, but the two Boolean values could mean anything, and we might easily have them in the wrong order.

The problem worsens as the number of parameters increases, or if we try to allow the input of null values. Kotlin’s named arguments offer a neat solution to this problem.

3. Kotlin’s Named Arguments

When invoking a Kotlin function, we can name one or more arguments. The name used for the argument must match the parameter name specified in the function declaration.

3.1. Invoking With Named Arguments

Let’s re-write the invocation of resizePane using named arguments:

resizePane(newSize = 10, forceResize = true, noAnimation = false)

This version of the code is more self-documenting and easier to double-check.

3.2. Named Arguments Order

As we saw earlier, the order of positional arguments cannot be changed. What about the order of named arguments?

Kotlin’s named arguments can be passed in any order, so let’s swap the two Boolean values:

resizePane(newSize = 11, noAnimation = false, forceResize = true)

As all arguments are named, we can pass them in any order we like:

resizePane(forceResize = true, newSize = 12, noAnimation = false)

3.3. Mixing Named and Positional Arguments

Though there are readability advantages of named arguments, they also add more text to the code. It would be useful to be able to use positional arguments for brevity but mix in the use of named parameters for clarity where it helps.

Luckily, we can mix the named and positional arguments in a single call. However, we should note that in Kotlin 1.3, all positional arguments had to be placed before the named arguments. Kotlin 1.4 doesn’t impose this restriction anymore.

In Kotlin 1.4, we can mix named and positional arguments provided the order is maintained.

Let’s try this out. Kotlin 1.3 would allow us to name only the arguments after the positional ones:

resizePane(20, true, noAnimation = false)

And in more recent versions, we can use a positional argument in the middle of the named arguments:

resizePane(newSize = 20, true, noAnimation = false)

Similarly, we can pass only the last argument as a positional argument:

resizePane(newSize = 30, forceResize = true, false)

On the other hand, we can use a named argument in the middle of the positional arguments:

resizePane(40, forceResize = true, false)

Each of these has the same effect.

The power of named parameters is much greater when it’s combined with defaults, allowing us to specify only the inputs we wish to, regardless of where they are in the function definition.

4. Default Arguments

Sometimes, a function may want to treat some of its parameters as optional and assume default values for them if a value is not supplied. Many languages, including Java, do not support this feature. In such cases, we have to write code that assigns a default value to a parameter if a value is not supplied.

Additionally, we may use function overloading to provide different versions of the method, where each version allows the callers to skip one or more optional parameters.

However, as the number of parameters increases, method overloading can quickly spiral out of control and lead to more difficulty in identifying what are parameters mean.

Kotlin offers language support for default arguments. As a result, function overloading can be reduced.

4.1. Default Arguments in Kotlin

Default parameter values are specified using the = symbol after the parameter type in the function declaration:

fun connect(url: String, connectTimeout: Int = 1000, enableRetry: Boolean = true) {
    println("The parameters are url = $url, connectTimeout = $connectTimeout, enableRetry = $enableRetry")
}

Here, we’ve specified default values for the connectTimeout and enableRetry parameters.

Next, let’s see how to invoke a function with default arguments.

4.2. Invoking a Function With Default Arguments

Because the connectTimeout and enableRetry parameters are declared with default values, we can exclude them in the function call:

connect("http://www.baeldung.com")

This uses the default values for both the skipped arguments.

Next, let’s skip only the enableRetry argument:

connect("http://www.baeldung.com", 5000)

This uses the default value for the enableRetry argument.

4.3. Skipping the Middle Argument

Next, let’s skip only the middle argument connectTimeout:

connect("http://www.baeldung.com", false)

We get a compiler error:

The boolean literal does not conform to the expected type Int

What went wrong? We skipped the second argument connectTimeout and passed the Boolean argument enableRetry in its place. However, the compiler expects an Int value as the second argument. Once an argument with a default value is skipped, all subsequent arguments must be passed as named arguments.

Because we skipped the connectTimeout argument, we must pass the enableRetry as a named argument:

connect("http://www.baeldung.com", enableRetry = false)

This fixes the error. This is a good example of how the named parameters complement the default arguments. Without named parameters, we cannot get the benefits of defaults using positional arguments.

5. Overriding Functions and Default Arguments

So far, we’ve looked at default arguments in normal functions. Let’s now understand default arguments in the context of overridden functions.

5.1. Inheriting the Default Parameter Value

Overrides use the default parameter values declared in the base class function.

Let’s try this out by declaring an abstract class called AbstractConnector:

open class AbstractConnector { 
    open fun connect(url: String = "localhost") { 
        // function implementation 
    }
}

The connect function in the AbstractConnector class has a url parameter with a default value.

Next, let’s extend this class and override the connect function:

class RealConnector : AbstractConnector() {
    override fun connect(url: String) {
        println("The parameter is url = $url")
    }
}

As we might expect, we can invoke the overridden connect method by passing in a value for the url argument:

val realConnector = RealConnector()
realConnector.connect("www.baeldung.com")

However, the url parameter in the overridden connect function also uses the default value declared in the base class function.

So, we can invoke the override without the url argument:

realConnector.connect()

This can be understood in terms of the Liskov Substitution Principle. All overridden functions should be thought of as being called from the super-type that declares them. So, what if we wanted to change the default?

5.2. Override Default Values

Let’s try to specify a default value for the url parameter in the subclass RealConnector:

class RealConnector : AbstractConnector() {
    override fun connect(url: String = "www.baeldung.com") { // Compiler Error 
        // function implementation
    }
}

This is not allowed and results in a compiler error. Overriding functions cannot specify default values for their parameters. Kotlin requires that default values can only be specified in the base class functions.

6. Conclusion

In this article, we looked at Kotlin’s support for named arguments and default argument values. To begin with, we looked at how positional arguments can be hard to read. Then we looked at Kotlin’s native support for named arguments, and how this helps improve readability with a larger number of arguments.

Finally, we saw that Kotlin’s support for default arguments saves developers from writing code to implement default values and helps avoid excessive function overloading.

As always, the example code from this article is available over on GitHub.

guest
0 Comments
Inline Feedbacks
View all comments