Functions in Kotlin
There are three things you need to know about how Kotlin differs from Java or Groovy methods.
- Functions don’t need to be defined within a class.
- Functions are declared by using the
fun
keyword. - Functions can have default and named parameters.
Let’s look at the Kotlin function deconstructed.
fun connect(addr: URI) : Boolean {
}
Here we have a function, declared with fun
, called connect that is taking in a typed parameters addr
, which is a URI. The return type is declared after the colon. If we wanted to define a function that had no arguments and an unspecified return type, it would look like this:
fun run() {
}
That’s pretty straightforward!
Kotlin also has a built in function for printing to the console just like Groovy - println
Good!
Using Kotlin fun in Java
So, how do we call these Kotlin functions in normal Java? Again, it’s fairly straightforward.
When compiled, Kotlin wraps functions in a class based on the file name by default. So if you have functions in a Kotlin file named Program.kt with a package ktf, then it will compile into a package ktf
with a class named Programkt
. You can access it by simply importing kyf.* and using the function as a staticly typed method, such as Programkt.run()
.
Pretty Name
If you don’t want to have the file name as your class, you can add an annotation to the top of your Kotlin file, @file:Jvmname("<className>")
to customize the complied class name.
Defaults
fun log(message: String, logLevel: Int = 1) {
println(message + logLevel)
}
In Kotlin, this is easy. However, Java does not have this concept of default parameters, so if we tried to use log
in java, we would get an error unless we add the annotation on our Kotlin function @JvmOverloads
. This is essentially unrolling the Kotlin function when it complies, so we get two java methods with the optional parameter formatted as an overloading method.
Named Parameters
Named parameters allow you to explicitly declare your parameters in a function. This is helpful when you have many parameters or don’t want to have to worry about the order of the parameters when you call a function. If you are familiar with Groovy, then named parameters are nothing new here, and Kotlin is very similar. Although, unlike Groovy, you cannot mix named and partitioned parameters in Kotlin - it’s all or nothing here.
Extending functions?!
In Kotlin, you can add functions to classes that are not owned by you. When you do this, the Kotlin compiler will just generate static functions fo the class. This reduces the need for utility classes and makes our code a lot cleaner and easier to read.
For instance, if we wanted a function to remove whitespace, we could make a function to do so like this:
fun removeWhiteSpace(value:String) : String {
var regex = Regex("\\s+")
return regex.replace(value, "")
}
But, this is still scoped to the class it’s defined in. To extend this, we could just change a few things and add it to String directly.
fun String.removeWhiteSpace() : String {
var regex = Regex("\\s+")
return regex.replace(this, "")
}
The context of this
here is the value of the string, so we don’t have to pass in a value anymore.
println("Hello World".removeWhiteSpace())
Extension functions allow us to think more functionally about our code! You can add extension classes to existing classes and your own classes. This makes more cleaner code and reduces the ceremony around utility functions.
The Kotlin complier is pretty intelligent and useful for allowing Java to behave functionally.
Infix Functions
The infix
function is a bit odd at first but it acts similarly to extension functions. We can use this special type of function to modify classes.
For example
infix fun Header.plus(other:Header) : Header {
return Header(this.Name + other.Name)
}
This makes plus look more like an operator, allowing us to use this as h1 plus h2
. With infix functions you don’t need to use dots or parentheses to call the function. It’s also possible to overload operators in Kotlin. There’s a predefined list of operators that can be overloaded though - only unary and binary operators are allowed. To overload a operator, the keyword operator
needs to be added to the infix function. This allows us to use that previous code as h1 + h2
without having to write out plus.
W
hile the overuse of operator overloaders can lead to confusing code, it’s valuable that Kotlin grants the ability to do so. This is especially helpful if you are writing domain-specific languages.
Recursive Functions
Kotlin is a functional programming language, and in functional languages you will see a common pattern of tail recursive function, or functions that calls itself repeatedly until the desired result is calculated.
Tail recursive functions can be an issue in languages that are not functional in design as running the function can lead to stack overloads or running out of memory. The Kotlin compiler optimizes away the recursion by translating it into a series of loops, thereby protecting the underlying JVM.
To use tail recursion in Kotlin, functions have to have the correct form and include the tailrec
keyword. Again, this is something that is supported right out of the box.
Kotlin has wide-scoped function support, to the extent that we don’t need classes. It’s so much easier to use functions in Kotlin with extension functions, infix, and tail recursions.