Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and promises to make coding for Android even more fun.
In the previous article, you learned about ranges and collections in Kotlin. In this tutorial, we'll continue to learn the language by looking at how to organize code using packages, and then go on to an introduction to functions in Kotlin.
1. Packages
If you are familiar with Java, you know that Java uses packages to group related classes; for example, the java.util
package has a number of useful utility classes. Packages are declared with the package
keyword, and any Kotlin file with a package
declaration at the beginning can contain declarations of classes, functions, or interfaces.
Declaration
Looking at the code below, we have declared a package com.chikekotlin.projectx
using the package
keyword. Also, we declared a class MyClass
(we'll discuss classes in Kotlin in future posts) inside this package.
package com.chikekotlin.projectx class MyClass
Now, the fully qualified name for the class MyClass
is com.chikekotlin.projectx.MyClass
.
package com.chikekotlin.projectx fun saySomething(): String { return "How far?" }
In the code above, we created a top-level function (we'll get to that shortly). So similarly to MyClass
, the fully qualified name for the function saySomething()
is com.chikekotlin.projectx.saySomething
.
Imports
In Kotlin, we use the import
declaration to enable the compiler to locate the classes, functions, interfaces or objects to be imported. In Java, on the other hand, we can't directly import functions or methods—only classes or interfaces.
We use import
to access a function, interface, class or object outside the package where it was declared.
import com.chikekotlin.projectx.saySomething fun main(args: Array<String>) { saySomething() // will print "How far?" }
In the code snippet above, we imported the function saySomething()
from a different package, and then we executed that function.
Kotlin also supports wildcard imports using the *
operator. This will import all the classes, interfaces and functions declared in the package all at once. This is not recommended, though—it's usually better to make your imports explicit.
import com.chikekotlin.projectx.*
Import Aliasing
When you have libraries that have conflicting class or function names (e.g. they each declare a function with the same name), you can use the as
keyword to give that imported entity a temporary name.
import com.chikekotlin.projectx.saySomething import com.chikekotlin.projecty.saySomething as projectYSaySomething fun main(args: Array<String>) { projectYSaySomething() }
Note that the temporary name is used only within the file where it was assigned.
2. Functions
A function groups together a series of code statements that perform a task. The details of the implementation of the function are hidden from the caller.
In Kotlin, functions are defined using the fun
keyword, as shown in the following example:
fun hello(name: String): String { return "Hello $name" } val message = hello("Chike") print(message) // will print "Hello Chike"
In the code above, we defined a simple function hello()
with a single parameter name
of type String
. This function returns a String
type. The parameter definition format for functions is name: type
, e.g. age: Int
, price: Double
, student: StudentClass
.
fun hello(name: String): Unit { print("Hello $name") } hello("Chike") // will print "Hello Chike"
The function above is similar to the previous one, but notice that this one has a return type of Unit
. Because this function doesn't return any significant value to us—it just prints out a message—its return type is Unit
by default. Unit
is a Kotlin object (we'll discuss Kotlin objects in later posts) that is similar to the Void
types in Java and C.
public object Unit { override fun toString() = "kotlin.Unit" }
Note that if you don't explicitly declare the return type to be Unit
, the type is inferred by the compiler.
fun hello(name: String) { // will still compile print("Hello $name") }
Single-Line Functions
Single-line or one-line functions are functions that are just single expressions. In this function, we get rid of the braces and use the =
symbol before the expression. In other words, we get rid of the function block.
fun calCircumference(radius: Double): Double { return (2 * Math.PI) * radius }
The function above can be shortened into a single line:
fun calCircumference(radius: Double) = (2 * Math.PI) * radius
Looking at the updated function above, you can see that we have made our code more concise by removing the curly braces {}
, the return
keyword, and also the return type (which is inferred by the compiler).
You can still include the return type to be more explicit if you want.
fun calCircumference(radius: Double): Double = (2 * Math.PI) * radius
Named Parameters
Named parameters allow more readable functions by naming the parameters that are being passed to a function when called.
In the following example, we created a function that prints my full name.
fun sayMyFullName(firstName: String, lastName: String, middleName: String): Unit { print("My full name is $firstName $middleName $lastName"); }
To execute the function above, we would just call it like so:
sayMyFullName("Chike", "Nnamdi", "Mgbemena")
Looking at the function call above, we don't know which String
type arguments equate to which function parameters (though some IDEs such as IntelliJ IDEA can help us). Users of the function will have to look into the function signature (or the source code) or documentation to know what each parameter corresponds to.
sayMyFullName(firstName = "Chike", middleName = "Nnamdi", lastName = "Mgbemena")
In the second function call above, we supplied the parameter names before the argument values. You can see that this function call is clearer and more readable than the previous one. This way of calling functions helps reduce the possibility of bugs that can happen when arguments of the same type are swapped by mistake.
The caller can also alter the order of the parameters using named parameters. For example:
sayMyFullName(lastName = "Mgbemena", middleName = "Nnamdi", firstName = "Chike") // will still compile
In the code above, we swapped the argument position of the firstName
with the lastName
. The argument order doesn't matter with named parameters because the compiler will map each of them to the right function parameter.
Default Parameters
In Kotlin, we can give a function default values for any of its parameters. These default values are used if nothing is assigned to the arguments during the function call. To do this in Java, we'd have to create different overloaded methods.
Here, in our calCircumference()
method, we modified the method by adding a default value for the pi
parameter—Math.PI
, a constant from the java.lang.Math
package.
fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius
When we call this function, we can either pass our approximated value for pi
or use the default.
print(calCircumference(24.0)) // used default value for PI and prints 150.79644737231007 print(calCircumference(24.0, 3.14)) // passed value for PI and prints 150.72
Let's see another example.
fun printName(firstName: String, middleName: String = "N/A", lastName: String) { println("first name: $firstName - middle name: $middleName - last name: $lastName") }
In the following code, we tried to call the function, but it won't compile:
printName("Chike", "Mgbemena") // won't compile
In the function call above, I'm passing my first name and last name to the function, and hoping to use the default value for the middle name. But this won't compile because the compiler is confused. It doesn't know what the argument "Mgbemena" is for—is it for the middleName
or the lastName
parameter?
To solve this issue, we can combine named parameters and default parameters.
printName("Chike", lastName = "Mgbemena") // will now compile
Java Interoperability
Given that Java doesn't support default parameter values in methods, you'll need to specify all the parameter values explicitly when you call a Kotlin function from Java. But Kotlin provides us with the functionality to make it easier for the Java callers by annotating the Kotlin function with @JvmOverloads
. This annotation will instruct the Kotlin compiler to generate the Java overloaded functions for us.
In the following example, we annotated the calCirumference()
function with @JvmOverloads
.
@JvmOverloads fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius
The following code was generated by the Kotlin compiler so that Java callers can then choose which one to call.
// Java double calCircumference(double radius, double pi); double calCircumference(double radius);
In the last generated Java method definition, the pi
parameter was omitted. This means that the method will use the default pi
value.
Unlimited Arguments
In Java, we can create a method to receive an unspecified number of arguments by including an ellipsis (...
) after a type in the method's parameter list. This concept is also supported by Kotlin functions with the use of the vararg
modifier followed by the parameter name.
fun printInts(vararg ints: Int): Unit { for (n in ints) { print("$n\t") } } printInts(1, 2, 3, 4, 5, 6) // will print 1 2 3 4 5 6
The vararg
modifier allows callers to pass in a comma-separated list of arguments. Behind the scenes, this list of arguments will be wrapped into an array.
When a function has multiple parameters, the vararg
parameter is typically the last one. It is also possible to have parameters after the vararg
, but you'll need to use named parameters to specify them when you call the function.
fun printNumbers(myDouble: Double, myFloat: Float, vararg ints: Int) { println(myDouble) println(myFloat) for (n in ints) { print("$n\t") } } printNumbers(1.34, 4.4F, 2, 3, 4, 5, 6) // will compile
For example, in the code above, the parameter with the vararg
modifier is in the last position in a multiple parameters list (this is what we usually do). But what if we don't want it in the last position? In the following example, it is in the second position.
fun printNumbers(myDouble: Double, vararg ints: Int, myFloat: Float) { println(myDouble) println(myFloat) for (n in ints) { print("$n\t") } } printNumbers(1.34, 2, 3, 4, 5, 6, myFloat = 4.4F) // will compile printNumbers(1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will not compile printNumbers(myDouble = 1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will also not compile
As you can observe in the updated code above, we used named arguments on the last parameter to solve this.
Spread Operator
Let's say we want to pass an array of integers to our printNumbers()
function. The function expects the values to be unrolled into a list of parameters, though. If you try to pass the array directly into printNumbers()
, you'll see that it won't compile.
val intsArray: IntArray = intArrayOf(1, 3, 4, 5) printNumbers(1.34, intsArray, myFloat = 4.4F) // won't compile
To solve this problem, we need to use the spread operator *
. This operator will unpack the array and then pass the individual elements as arguments into the function for us.
val intsArray: IntArray = intArrayOf(1, 3, 4, 5) printNumbers(1.34, *intsArray, myFloat = 4.4F) // will now compile
By inserting the spread operator *
in front of intsArray
in the function's arguments list, the code now compiles and produces the same result as if we had passed the elements of intsArray
as a comma-separated list of arguments.
Return Multiple Values
Sometimes we want to return multiple values from a function. One way is to use the Pair
type in Kotlin to create a Pair
and then return it. This Pair
structure encloses two values that can later be accessed. This Kotlin type can accept any types you supply its constructor. And, what's more, the two types don't even need to be the same.
fun getUserNameAndState(id: Int): Pair<String?, String?> { require(id > 0, { "Error: id is less than 0" }) val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane") val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu") val userName = userNames[id] val userState = userStates[id] return Pair(userName, userState) }
In the function above, we constructed a new Pair
by passing the userName
and userState
variables as the first and second arguments respectively to its constructor, and then returned this Pair
to the caller.
Another thing to note is that we used a function called require()
in the getUserNameAndState()
function. This helper function from the standard library is used to give our function callers a precondition to meet, or else an IllegalArgumentException
will be thrown (we'll discuss Exceptions in Kotlin in a future post). The optional second argument to require()
is a function literal returning a message to be displayed if the exception is thrown. For example, calling the getUserNameAndState()
function and passing -1
as an argument to it will trigger:
Retrieving Data From Pair
val userNameAndStatePair: Pair<String?, String?> = getUserNameAndState(101) println(userNameAndStatePair.first) // Chike println(userNameAndStatePair.second) // Lagos
In the code above, we accessed the first and second values from the Pair
type by using its first
and second
properties.
However, there is a better way of doing this: destructuring.
val (name, state) = getUserNameAndState(101) println(name) // Chike println(state) // Lagos
What we have done in the updated code above is to directly assign the first and second values of the returned Pair
type to the variables name
and state
respectively. This feature is called destructuring declaration.
Triple Return Values and Beyond
Now, what if you want to return three values at once? Kotlin provides us with another useful type called Triple
.
fun getUserNameStateAndAge(id: Int): Triple<String?, String?, Int> { require(id > 0, { "id is less than 0" }) val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane") val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu") val userName = userNames[id] val userState = userStates[id] val userAge = 6 return Triple(userNames[id], userStates[id], userAge) } val (name, state, age) = getUserNameStateAndAge(101) println(name) // Chike println(state) // Lagos println(age) // 6
I am sure some of you are wondering what to do if you want to return more than three values. The answer for that will be in a later post, when we discuss Kotlin's data classes.
Conclusion
In this tutorial, you learned about the packages and basic functions in the Kotlin programming language. In the next tutorial in the Kotlin From Scratch series, you'll learn more about functions in Kotlin. See you soon!
To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts+!
- Android SDKIntroduction to Android Architecture Components
- Machine LearningCreate Chatbots on Android With IBM Watson
- Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?
- Android SDKQuick Tip: Write Cleaner Code With Kotlin SAM Conversions
- Android SDKAndroid O: Phone Number Verification With SMS Tokens