1. Welcome
This codelab is part of the Kotlin Bootcamp for Programmers course. You'll get the most value out of this course if you work through the codelabs in sequence. Depending on your knowledge, you may be able to skim some sections. This course is geared towards programmers who know an object-oriented language, and want to learn Kotlin.
Introduction
In this codelab, you create a Kotlin program and learn about functions in Kotlin, including default values for parameters, filters, lambdas, and compact functions.
Rather than build a single sample app, the lessons in this course are designed to build your knowledge, but be semi-independent of each other so you can skim sections you're familiar with. To tie them together, many of the examples use an aquarium theme. And if you want to see the full aquarium story, check out the Kotlin Bootcamp for Programmers Udacity course.
What you should already know
- The basics of a modern, object-oriented, statically typed programming language
- How to program with classes, methods, and exception handling in at least one language
- How to work with Kotlin's REPL (Read-Eval-Print Loop) in IntelliJ IDEA
- The basics of Kotlin, including types, operators, and loops
This codelab is geared towards programmers who know an object-oriented language and want to learn more about Kotlin.
What you'll learn
- How to create a program with a
main()
function and arguments in IntelliJ IDEA - How to use default values and compact functions
- How to apply filters for lists
- How to create basic lambdas and higher-order functions
What you'll do
- Work with the REPL to try out some code.
- Work with IntelliJ IDEA to create basic Kotlin programs.
2. Task: Explore the main() function
In this task, you create a Kotlin program and learn about the main()
function, as well as how to pass arguments to a program from the command line.
You may remember the printHello()
function that you entered into the REPL in a previous codelab:
fun printHello() {
println ("Hello World")
}
printHello()
⇒ Hello World
You define functions using the fun
keyword, followed by the name of the function. As with other programming languages, the parentheses ()
are for function arguments, if any. Curly braces {}
frame the code for the function. There is no return type for this function, because it doesn't return anything.
Step 1: Create a Kotlin file
- Open IntelliJ IDEA.
- The Project pane on the left in IntelliJ IDEA shows a list of your project files and folders. Find and right-click the src folder under Hello Kotlin. (You should already have the Hello Kotlin project from the previous codelab.)
- Select New > Kotlin File / Class.
- Keep Kind as File, and name the file Hello.
- Click OK.
There is now a file in the src folder called Hello.kt.
Step 2: Add code and run your program
- As with other languages, the Kotlin
main()
function specifies the entry point for execution. Any command line arguments are passed as an array of strings.
Type or paste the following code into the Hello.kt file:
fun main(args: Array<String>) {
println("Hello, world!")
}
Like your earlier printHello()
function, this function has no return
statement. Every function in Kotlin returns something, even when nothing is explicitly specified. So a function like this main()
function returns a type kotlin.Unit
, which is Kotlin's way of saying no value.
- To run your program, click the green triangle to the left of the
main()
function. Select Run ‘HelloKt' from the menu. - IntelliJ IDEA compiles the program and runs it. The results appear in a log pane at the bottom, as shown below.
Step 3: Pass arguments to main()
Because you are running your program from IntelliJ IDEA and not from the command line, you need to specify any arguments to the program a little differently.
- Select Run > Edit Configurations. The Run/Debug Configurations window opens.
- Type
Kotlin!
in the Program arguments field. - Click OK.
Step 4: Change the code to use a string template
A string template inserts a variable or expression into a string, and $
specifies that part of the string will be a variable or expression. Curly braces {}
frame the expression, if any.
- In Hello.kt, change the greeting message to use the first argument passed into the program,
args[0]
, instead of"world"
.
fun main(args: Array<String>) {
println("Hello, ${args[0]}")
}
- Run the program, and the output includes the argument you specified.
⇒ Hello, Kotlin!
3. Task: Learn why (almost) everything has a value
In this task, you learn why almost everything in Kotlin has a value, and why that's useful.
Some other languages have statements, which are lines of code that don't have a value. In Kotlin, almost everything is an expression and has a value—even if that value is kotlin.Unit
.
- In Hello.kt, write code in
main()
to assign aprintln()
to a variable calledisUnit
and print it. (println()
does not return a value, so it returnskotlin.Unit
.)
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
- Run your program. The first
println()
prints the string"This is an expression"
. The secondprintln()
prints the value of the firstprintln()
statement, that is,kotlin.Unit
.
⇒ This is an expression kotlin.Unit
- Declare a
val
calledtemperature
and initialize it to 10. - Declare another
val
calledisHot
and assign the return value of anif
/else
statement toisHot
, as shown in the following code. Because it is an expression, you can use the value of theif
expression right away.
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
⇒ false
- Use the value of an expression in a string template. Add some code to check the temperature to determine whether a fish is safe or too warm, then run your program.
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
⇒ The water temperature is OK.
4. Task: Learn more about functions
In this task, you learn more about functions in Kotlin, and more about the very useful when
conditional expression.
Step 1: Create some functions
In this step, you put together some of what you've learned and create functions with different types. You can replace the contents of Hello.kt with this new code.
- Write a function called
feedTheFish()
that callsrandomDay()
to get a random day of the week. Use a string template to print afood
for the fish to eat that day. For now, the fish eat the same food every day.
fun feedTheFish() {
val day = randomDay()
val food = "pellets"
println ("Today is $day and the fish eat $food")
}
fun main(args: Array<String>) {
feedTheFish()
}
- Write the
randomDay()
function to pick a random day from an array and return it.
The nextInt()
function takes an integer limit, which limits the number from Random()
to 0 through 6 to match the week
array.
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
- The
Random()
andnextInt()
functions are defined injava.util.*
. At the top of the file, add the needed import:
import java.util.* // required import
- Run your program, and check the output.
⇒ Today is Tuesday and the fish eat pellets
Step 2: Use a when expression
Extending this further, change the code to pick different food for different days using a when
expression. The when
statement is similar to switch
in other programming languages, but when
automatically breaks at the end of each branch. It also makes sure your code covers all the branches if you are checking an enum.
- In Hello.kt, add a function called
fishFood()
that takes a day as aString
and returns the fish's food for the day as aString
. Usewhen()
, so that each day the fish gets a specific food. Run your program a few times to see different outputs.
fun fishFood (day : String) : String {
var food = ""
when (day) {
"Monday" -> food = "flakes"
"Tuesday" -> food = "pellets"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Saturday" -> food = "lettuce"
"Sunday" -> food = "plankton"
}
return food
}
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
}
⇒ Today is Thursday and the fish eat granules
- Add a default branch to the
when
expression usingelse
. For testing, to make sure the default is taken sometimes in your program, remove theTuesday
andSaturday
branches.
Having a default branch ensures that food
gets a value before being returned, so it doesn't need to be initialized anymore. Because the code now assigns a string to food
only once, you can declare food
with val
instead of var
.
fun fishFood (day : String) : String {
val food : String
when (day) {
"Monday" -> food = "flakes"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Sunday" -> food = "plankton"
else -> food = "nothing"
}
return food
}
- Because every expression has a value, you can make this code a little more concise. Return the value of the
when
expression directly, and eliminate thefood
variable. The value of thewhen
expression is the value of the last expression of the branch that satisfied the condition.
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}
The final version of your program looks something like the code below.
import java.util.* // required import
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
}
fun main(args: Array<String>) {
feedTheFish()
}
5. Task: Explore default values and compact functions
In this task, you learn about default values for functions and methods. You also learn about compact functions, which can make your code more concise and readable, and can reduce the number of code paths for testing. Compact functions are also called single-expression functions.
Step 1: Create a default value for a parameter
In Kotlin, you can pass arguments by parameter name. You can also specify default values for parameters: if an argument isn't supplied by the caller, the default value is used. Later, when you write methods (member functions), it means you can avoid writing lots of overload versions of the same method.
- In Hello.kt, write a
swim()
function with aString
parameter namedspeed
that prints the fish's speed. Thespeed
parameter has a default value of"fast"
.
fun swim(speed: String = "fast") {
println("swimming $speed")
}
- From the
main()
function, call theswim()
function three ways. First call the function using the default. Then call the function and pass thespeed
parameter without a name, then call the function by naming thespeed
parameter.
swim() // uses default speed
swim("slow") // positional argument
swim(speed="turtle-like") // named parameter
⇒ swimming fast swimming slow swimming turtle-like
Step 2: Add required parameters
If no default is specified for a parameter, the corresponding argument must always be passed.
- In Hello.kt, write a
shouldChangeWater()
function that takes three parameters:day
,temperature
, and adirty
level. The function returnstrue
if the water should be changed, which happens if it's Sunday, if the temperature is too high, or if the water is too dirty. The day of the week is required, but the default temperature is 22, and the default dirty level is 20.
Use a when
expression without an argument, which in Kotlin acts as a series of if/else if
checks.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
temperature > 30 -> true
dirty > 30 -> true
day == "Sunday" -> true
else -> false
}
}
- Call
shouldChangeWater()
fromfeedTheFish()
and supply the day. Theday
parameter doesn't have a default, so you must specify an argument. The other two parameters ofshouldChangeWater()
have default values, so you don't have to pass arguments for them.
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
println("Change water: ${shouldChangeWater(day)}")
}
=> Today is Thursday and the fish eat granules Change water: false
Step 3: Make compact functions
The when
expression that you wrote in the previous step packs a lot of logic into a small amount of code. If you wanted to unpack it a little, or if the conditions to check were more complicated, you could use some well-named local variables. But the Kotlin way to do it is with compact functions.
Compact functions, or single-expression functions, are a common pattern in Kotlin. When a function returns the results of a single expression, you can specify the body of the function after an =
symbol, omit the curly braces {}
, and omit the return
.
- in Hello.kt, add compact functions to test the conditions.
fun isTooHot(temperature: Int) = temperature > 30
fun isDirty(dirty: Int) = dirty > 30
fun isSunday(day: String) = day == "Sunday"
- Change
shouldChangeWater()
to call the new functions.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
isTooHot(temperature) -> true
isDirty(dirty) -> true
isSunday(day) -> true
else -> false
}
}
- Run your program. The output from the
println()
withshouldChangeWater()
should be the same as it was before you switched to using compact functions.
Default values
The default value for a parameter doesn't have to be a value. It can be another function, as shown in the following partial sample:
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
...
6. Task: Get started with filters
In this task, you learn a bit about filters in Kotlin. Filters are a handy way to get part of a list based on some condition.
Step 1: Create a filter
- In Hello.kt, define a list of aquarium decorations at the top level with
listOf()
. You can replace the contents of Hello.kt.
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
- Create a new
main()
function with a line to print only the decorations that start with the letter ‘p'. The code for the filter condition is in curly braces{}
, andit
refers to each item as the filter loops through. If the expression returnstrue
, the item is included.
fun main() {
println( decorations.filter {it[0] == 'p'})
}
- Run your program, and you see the following output in the Run window:
⇒ [pagoda, plastic plant]
Step 2: Compare eager and lazy filters
If you're familiar with filters in other languages, you may wonder whether filters in Kotlin are eager or lazy. Is the result list created immediately, or when the list is accessed? In Kotlin, it happens whichever way you need it to. By default, filter
is eager, and each time you use the filter, a list is created.
To make the filter lazy, you can use a Sequence
, which is a collection that can only look at one item at a time, starting at the beginning, and going to the end. Conveniently, this is exactly the API that a lazy filter needs.
- In Hello.kt, change your code to assign the filtered list to a variable called
eager
, then print it.
fun main() {
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
// eager, creates a new list
val eager = decorations.filter { it [0] == 'p' }
println("eager: $eager")
- Below that code, evaluate the filter using a
Sequence
withasSequence()
. Assign the sequence to a variable calledfiltered
, and print it.
// lazy, will wait until asked to evaluate
val filtered = decorations.asSequence().filter { it[0] == 'p' }
println("filtered: $filtered")
When you return the filter results as a Sequence
, the filtered
variable won't hold a new list—it'll hold a Sequence
of the list elements and knowledge of the filter to apply to those elements. Whenever you access elements of the Sequence
, the filter is applied, and the result is returned to you.
- Force evaluation of the sequence by converting it to a
List
withtoList()
. Print the result.
// force evaluation of the lazy list
val newList = filtered.toList()
println("new list: $newList")
- Run your program and observe the output.
⇒ eager: [pagoda, plastic plant] filtered: kotlin.sequences.FilteringSequence@386cc1c4 new list: [pagoda, plastic plant]
To visualize what's going on with the Sequence
and lazy evaluation, use the map()
function. The map()
function performs a simple transformation on each element in the sequence.
- With the same
decorations
list as above, make a transformation withmap()
that does nothing, and simply returns the element that was passed. Add aprintln()
to show each time an element is accessed, and assign the sequence to a variable calledlazyMap
.
val lazyMap = decorations.asSequence().map {
println("access: $it")
it
}
- Print
lazyMap
, print the first element oflazyMap
usingfirst()
, and printlazyMap
converted to aList
.
println("lazy: $lazyMap")
println("-----")
println("first: ${lazyMap.first()}")
println("-----")
println("all: ${lazyMap.toList()}")
- Run your program, and observe the output. Printing
lazyMap
just prints a reference to theSequence
—the innerprintln()
isn't called. Printing the first element accesses only the first element. Converting theSequence
to aList
accesses all the elements.
⇒ lazy: kotlin.sequences.TransformingSequence@5ba23b66 ----- access: rock first: rock ----- access: rock access: pagoda access: plastic plant access: alligator access: flowerpot all: [rock, pagoda, plastic plant, alligator, flowerpot]
- Create a new
Sequence
using the original filter before applyingmap
. Print that result.
val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
println("access: $it")
it
}
println("-----")
println("filtered: ${lazyMap2.toList()}")
- Run your program and observe the additional output. As with getting the first element, the inner
println()
is only called for the elements that are accessed.
⇒ ----- access: pagoda access: plastic plant filtered: [pagoda, plastic plant]
7. Task: Get started with lambdas and higher-order functions
In this task, you get an introduction to lambdas and higher-order functions in Kotlin.
Lambdas
In addition to traditional named functions, Kotlin supports lambdas. A lambda is an expression that makes a function. But instead of declaring a named function, you declare a function that has no name. Part of what makes this useful is that the lambda expression can now be passed as data. In other languages, lambdas are called anonymous functions, function literals, or similar names.
Higher-order functions
You can create a higher-order function by passing a lambda to another function. In the previous task, you created a higher-order function called filter
. You passed the following lambda expression to filter
as the condition to check: {it[0] == 'p'}
Similarly, map
is a higher-order function, and the lambda you passed to it was the transformation to apply.
Step 1: Learn about lambdas
- Like named functions, lambdas can have parameters. For lambdas, the parameters (and their types, if needed) go on the left of what is called a function arrow
->
. The code to execute goes to the right of the function arrow. Once the lambda is assigned to a variable, you can call it just like a function.
Using the REPL (Tools > Kotlin > Kotlin REPL), try out this code:
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10
In this example, the lambda takes an Int
named dirty
, and returns dirty / 2
. (Because filtering removes dirt.)
- Kotlin's syntax for function types is closely related to its syntax for lambdas. Use this syntax to cleanly declare a variable that holds a function:
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
Here's what the code says:
- Make a variable called
waterFilter
. waterFilter
can be any function that takes anInt
and returns anInt
.- Assign a lambda to
waterFilter
. - The lambda returns the value of the argument
dirty
divided by 2.
Note that you don't have to specify the type of the lambda argument anymore. The type is calculated by type inference.
Step 2: Create a higher-order function
So far, the examples for lambdas look mostly like functions. The real power of lambdas is using them to create higher-order functions, where the argument to one function is another function.
- Write a higher-order function. Here's a basic example, a function that takes two arguments. The first argument is an integer. The second argument is a function that takes an integer and returns an integer. Try it out in the REPL.
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
return operation(dirty)
}
The body of the code calls the function that was passed as the second argument, and passes the first argument along to it.
- To call this function, pass it an integer and a function.
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15
The function you pass doesn't have to be a lambda; it can be a regular named function instead. To specify the argument as a regular function, use the ::
operator. This way Kotlin knows that you are passing the function reference as an argument, not trying to call the function.
- Try passing a regular named function to
updateDirty()
.
fun increaseDirty( start: Int ) = start + 1
println(updateDirty(15, ::increaseDirty))
⇒ 16
var dirtyLevel = 19;
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
println(dirtyLevel)
⇒ 42
8. Summary
- To create a Kotlin source file in IntelliJ IDEA, start with a Kotlin project.
- To compile and run a program in IntelliJ IDEA, click the green triangle next to the
main()
function. Output appears in a log window below. - In IntelliJ IDEA, specify command line arguments to pass to the
main()
function in Run > Edit Configurations. - Almost everything in Kotlin has a value. You can use this fact to make your code more concise by using the value of an
if
orwhen
as an expression or return value. - Default arguments remove the need for multiple versions of a function or method. For example:
fun swim(speed: String = "fast") { ... }
- Compact functions, or single-expression functions, can make your code more readable. For example:
fun isTooHot(temperature: Int) = temperature > 30
- You've learned some basics about filters, which use lambda expressions. For example:
val beginsWithP = decorations.filter { it [0] == 'p' }
- A lambda expression is an expression that makes an unnamed function. Lambda expressions are defined between curly braces
{}
. - In a higher-order function, you pass a function such as a lambda expression to another function as data. For example:
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
There's a lot in this lesson, especially if you're new to lambdas. A later lesson revisits lambdas and higher-order functions.
9. Learn more
Kotlin documentation
If you want more information on any topic in this course, or if you get stuck, https://kotlinlang.org is your best starting point.
- Kotlin coding conventions
- Kotlin idioms
- String templates
when
expression- Single-expression functions
- Higher-order functions and lambdas
- Filters
- Sequences
- Last parameter call syntax
Kotlin tutorials
The https://play.kotlinlang.org website includes rich tutorials called Kotlin Koans, a web-based interpreter, and a complete set of reference documentation with examples.
Udacity course
To view the Udacity course on this topic, see Kotlin Bootcamp for Programmers.
IntelliJ IDEA
Documentation for the IntelliJ IDEA can be found on the JetBrains website.
10. Homework
This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:
- Assign homework if required.
- Communicate to students how to submit homework assignments.
- Grade the homework assignments.
Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.
If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.
Answer these questions
Question 1
The contains(element: String)
function returns true
if the string element
is contained in the string it's called on. What will be the output of the following code?
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
println(decorations.filter {it.contains('p')})
▢ [pagoda, plastic, plant]
▢ [pagoda, plastic plant]
▢ [pagoda, plastic plant, flowerpot]
▢ [rock, alligator]
Question 2
In the following function definition, which one of the parameters is required? fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20, numDecorations: Int = 0): Boolean {...}
▢ numDecorations
▢ dirty
▢ day
▢ temperature
Question 3
You can pass a regular named function (not the result of calling it) to another function. How would you pass increaseDirty( start: Int ) = start + 1
to updateDirty(dirty: Int, operation: (Int) -> Int)
?
▢ updateDirty(15, &increaseDirty())
▢ updateDirty(15, increaseDirty())
▢ updateDirty(15, ("increaseDirty()"))
▢ updateDirty(15, ::increaseDirty)
11. Next codelab
For an overview of the course, including links to other codelabs, see "Kotlin Bootcamp for Programmers: Welcome to the course."