CS349 User Interfaces
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Learning Kotlin

Why Kotlin?

Kotlin is a modern language designed by JetBrains in 2011. Originally designed as a drop-in replacement for Java, Kotlin has a number of language features that make it desireable for building applications. We’ll focus on Kotlin/JVM, which gives us the ability to build programs that will run anywhere where we have a JVM installed (including Windows, macOS, Linux).

  • It has a very clean syntax, and supports quality-of-life features like default arguments, variable argument lists and rich collection types. It’s syntax closely resembles modern languages like Swift or Scala.
  • It’s a hybrid language: it can be used for declarative programming or class-based object-oriented programming. It also supports a number of functional features, especially with the use of collection classes. This allows a programmer to use the best appropach for a particular task.
  • Kotlin is statically compiled, so it catches many potential errors during compilation (not just at runtime). It’s also strongly typed, with type inference.
  • Critically, it supports compilation to a number of targets: JVM for Windows/macOS/Linux Desktop, Android native1, or Web. It can also build to native macOS and Windows (with some restrictions).
  • It has outstanding tools support with IntelliJ IDEA.

Kotlin is an ecosystem

For reference, see Kotlin documentation online.

Getting Started

Installing Kotlin

You need the Kotlin compiler and runtime. We’ll run on the Java JVM. You can either install the command-line tools, or install IntelliJ IDEA and run everything from within the IDE (which is recommended).

See the Getting Started section for details on installing IntelliJ IDEA and the Kotlin Compiler.

Compiling Code

Compiled languages require an explicit step to compile code and generate native executables. This is done ahead of time, and the executables are distributed to users. e.g. C++

  • The compilation cost is incurred before the user runs the program, so we get optimal startup performance.
  • The target system architecture must be known ahead of time, since we’re distributing native binaries.

Interpreted languages allow developers to distribute the raw source code which can be interpreted when the user executes it.

  • This requires some ‘runtime engine‘ that can convert source code to machine code on-the-fly. Results of this operation can often be cached so that the compilation cost is only incurred when it first executes.

compilers_interpreters

Some languages can be compiled to a secondary format (IR, ”intermediate representation”) and then interpreted. Languages running on the Java Virtual Machine (JVM) are compiled ahead of time to IR, and then interpreted at runtime.

Kotlinc compiled to JVM

Kotlin can be compiled or interpreted!

  • Kotlin/JVM compiles Kotlin code to JVM bytecode, which can run on any Java virtual machine.
  • Kotlin/Android compiles Kotlin code to native Android binaries, which leverage native versions of the Java Library and Kotlin standard libraries.
  • Kotlin/Native compiles Kotlin code to native binaries, which can run without a virtual machine. It is an LLVM based backend for the Kotlin compiler and native implementation of the Kotlin standard library.
  • Kotlin/JS transpiles (converts) Kotlin to JavaScript. The current implementation targets ECMAScript 5.1 (with plans to eventually target ECMAScript 2015).

There are three primary ways of executing Kotlin code:

  1. Read-Evaluate-Print-Loop (REPL): Interact directly with the Kotlin runtime, one line at-a-time. In this environment, it acts like a dynamic language.
  2. KotlinScript: Use Kotlin as a scripting language, by placing our code in a script and executing directly from our shell. The code is compiled automatically when we execute it, which eliminates the need to compile ahead-of-time.
  3. Application: We can compile standalone applications, targetting native or JVM [ed. we will use JVM in this course].

REPL

REPL is a paradigm where you type and submit expressions to the compiler one line-at-a-time. It’s commonly used with dynamic languages for debugging, or checking short expressions. It’s not intended as a means of writing full applications!

$ kotlin
Welcome to Kotlin version 1.6.10 (JRE 17.0.2+8-86)
Type :help for help, :quit for quit
>>> val message="Hello Kotlin!"
>>> println(message)
Hello Kotlin!

KotlinScript

KotlinScript is Kotlin code in a script file that we can execute from our shell. This makes Kotlin an interesting alternative to a language like Python for shell scripting.

$ cat hello.kts 
#!/usr/bin/env kotlin
val message="Hello Kotlin!"
println(message)

$ ./hello.kts 
Hello Kotlin!

Kotlin compiles scripts in the background before executing them, so there’s a delay before it executes [ed. I fully expect that later versions of Kotlin will allow caching the compilation results to speedup script execution time].

This is a great way to test functionality, but not a straight-up replacement for shell scripts, due to the runtime costs2.

Applications

Kotlin applications are fully-functional, and can be compiled to native code, or to the JVM. Kotlin application code looks a little like C, or Java. Here’s the world’s simplest Kotlin program, consisting of a single main method3.

fun main() {
	println("Hello Kotlin!")
}

To compile from the command-line, we can use the Kotlin compiler, kotlinc. By default, it takes Kotlin source files (.kt) and compiles them into corresponding class files (.class) that can be executed on the JVM.

$ kotlinc Hello.kt 

$ ls 
Hello.kt HelloKt.class

$ kotlin HelloKt
Hello Kotlin!

Notice that the compiled class is named slightly differently than the source file. If your code isn’t contained in a class, Kotlin wraps it in an artificial class so that the JVM (which requires a class) can load it properly. Later when we use classes, this won’t be necessary.

This example compiles Hello.kt into Hello.jar and then executes it:

$ kotlinc Hello.kt -include-runtime -d Hello.jar

$ ls
Hello.jar Hello.kt

$ java -jar Hello.jar
Hello Kotlin!

Using Libraries

Kotlin has full access to it’s own class libraries, plus any others that are imported and made available. Kotlin is 100% compatible with Java libraries, and makes extensive use of Java libraries when possible. For example, Kotlin collection classes actually use some of the underlying Java collection libraries!

In this section, we’ll discuss how to use existing libraries in your code. We need to talk about namespaces and qualifying classes before we can talk about libraries.

Packages

You can declare a namespace in Kotlin by using the package declaration to the top of a source file. Classes or modules in the same package have full visibility by default.

Names of packages are always lowercase and do not use underscores (org.example.project). Using multi-word names is generally discouraged, but if you do need to use multiple words, you can either just concatenate them together or use camel case (org.example.myProject).

For example, in the file below, contents are contained in the ca.uwaterloo.cs346 package. The full name of the class could be qualified as ca.uwaterloo.cs346.ErrorMessage . If you were referring to it from a different package, you would need to use this fully qualified name.

package ca.uwaterloo.cs346

class ErrorMessage(val msg:String) {
	fun print() {
    println(msg)
  }
}

fun main() {
  val error = ErrorMessage("testing an error condition")
  error.print()
}

Importing Classes

To use a class in a different namespace, we need to import the related class by using the import keyword. This applies to any class that we wish to use. In the example below, we import our ErrorMessage class into a different namespace so that we can instantiate and use it.

import ca.uwaterloo.cs346.ErrorMessage

class Logger {
  val error = ErrorMessage()
  error.printMessage()
}

Type Aliases

Type aliases provide alternative names for existing types. If the type name is too long you can introduce a different shorter name and use the new one instead. This can make your code much more readable!

Note that type aliases do not introduce new types. They are equivalent to the corresponding underlying types, so there is no runtime overhead.

 typealias NodeSet = Set<Network.Node>
 typealias FileTable<K> = MutableMap<K, MutableList<File>>

Kotlin Standard Library

The Kotlin Standard Library is included with the Kotlin language, and contained in the kotlin package. This is automatically imported and does not need to be specified in an import statement.

Some of the features that will be discussed below are actually part of the standard library (and not part of the core language). This includes essential classes, such as:

  • Higher-order scope functions that implement idiomatic patterns (let, apply, use, etc).
  • Extension functions for collections (eager) and sequences (lazy).
  • Various utilities for working with strings and char sequences.
  • Extensions for JDK classes making it convenient to work with files, IO, and threading.

Using Java Libraries

Kotlin is completely 100% interoperable with Java, so all of the classes available in Java/JVM can also be imported and used in Kotlin.

// import all classes in the java.io package
// this allows us to refer to any class in this namespace
import java.io.*

// we can also just import a single class
// this allows us to refer to just the ListView class in code
import javafx.scene.control.ListView

// Kotlin code calling Java IO libraries
import java.io.FileReader
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.FileWriter
import java.io.BufferedWriter

if (writer != null) {
	writer.write(
  	row.toString() + delimiter +
    	s + row + delimiter +
      pi + endl
  )
Importing a class requires your compiler to locate the file containing these classes! The Kotlin Standard Library can always be referenced by the compiler, and as long as you’re compiling to the JVM, the Java class libraries will also be made available. However, to use any other Java or Kotlin library, you will need to take additional steps. We’ll discuss this when we cover build systems and Gradle.

  1. Kotlin has been adopted as the “official” language for Android development! ↩︎

  2. Scripts will be compiled and cached locally, but there’s still some runtime performance issues. ↩︎

  3. This chapter focuses mainly on the Kotlin language. In the next chapter, we’ll dive deeper into constructing applications. ↩︎