Kotlin Language#

Kotlin is a general-purpose, class-based object-oriented language. Although not a functional language, it includes some functional design elements (e.g. higher order functions). Syntactically, it is similar to other C-style programming languages, with a number of modern advances.

Practically, it is an excellent general-purpose language that can replace Java in production environments.

Language Basics#

Types#

Kotlin is a strongly typed language, where variables need to be declared, or the type determined, at compile time. If a type isn’t strictly provided, Kotlin will infer the type at compile time (similar to ‘auto‘ in C++). The compiler is strict about this: if the type cannot be inferred at compile-time, an error will be thrown.

Standard types#

Standard types are represented, including:

Arrays are included for performance and interop reasons, but Kotlin also includes collection types in the standard library which are preferred in most cases e.g., list, set.

Variables#

Kotlin uses the var keyword to indicate a variable, and variables must be declared before use. Type specifiers are always placed to the right of the variable name. Types can be declared explicitly, but will be inferred if the type isn’t provided.

fun main() {
  var a:Int = 10
  var b:String = "Jeff"
  var c:Boolean = false

  var d = "abc"	   // inferred as a String
  var e = 5        // inferred as Int
  var f = 1.5      // inferred as Float
}

Kotlin encourages the use use of immutable variables and data structures. This follows best-practices in other languages (e.g. use of final in Java, const in C++), where we use immutable structures to avoid accidental mutation.

  • var: a standard mutable variable that can be changed or reassigned.
  • val: an immutable variable that cannot be changed once initialized.
var a = 0       // type inferred as Int
a = 5           // a is mutable, so reassignment is ok

val b = 1	    // type inferred as Int as well
// b = 2	    // error because b is immutable

var c:Int = 10  // explicit type provided in this case

click to run

Operators#

Kotlin supports a wide range of operators. The full set can be found on the Kotlin Language Guide.

NULL Safety#

NULL is a special value that indicates that there is no data present (often indicated by the null keyword in other languages). NULL values can be difficult to work with in other programming languages, because once you accept that a value can be NULL, you need to check all uses of that variable against the possibility of it being NULL.

In Kotlin, every type is non-nullable by default. This means that if you attempt to assign a NULL to a normal data type, the compiler is able to check against this and report it as a compile-time error. If you need to work with NULL data, you can declare a nullable variable using the ? annotation. Once you do this, you need to use specific ? methods. You may also need to take steps to handle NULL data when appropriate.

Conventions

  • By default, a variable cannot be assigned a NULL value.
  • ? suffix on the type indicates that it’s NULL-able.
  • ?. accesses properties/methods if the object is not NULL (“safe call operator”)
  • ?: elvis operator is a ternary operator for NULL data
  • !! override operator (calls a method without checking for NULL, bad idea)
fun main() {
	// name is nullable
	var name:String? = null

	// only returns value if name is not null
	var length = name?.length
	println(length) // null

	// elvis operator provides an `else` value
	length = name?.length ?: 0
	println(length) // 0
}

click to run

Control Flow#

Kotlin supports standard control-flow constructs, but expands them in key ways.

if then else#

if... then has both a statement form (no return value) and an expression form (return value).

fun main() {
  val a=5
  val b=7

  // we don't return anything, so this is a statement
  println("a=$a, b=$b")
  if (a > b) {
      println("a is larger")
  } else {
      println("b is larger")
  }

  val number = 6

  // the value from each branch is considered a return value
  // this is an expression that returns a result
  println("number=$number")
  val result =
    if (number > 0)
      "$number is positive"
    else if (number < 0)
      "$number is negative"
    else
      "$number is zero"

  println(result)
}

// a=5, b=7
// b is larger
// number=6
// 6 is positive

click to run

This is why Kotlin does not have a ternary operator: if used as an expression serves the same purpose.

for in#

A for in loop steps through any collection that provides an iterator. This is equivalent to the for each loop in languages like C#.

fun main() {
  val items = listOf("apple", "banana", "kiwifruit")
  for (item in items) {
  	println(item)
  }

  for (index in items.indices) {
  	println("item $index is ${items[index]}")
  }

  for (c in "Kotlin") {
    print("$c ")
  }
}

// apple
// banana
// kiwifruit
// item 0 is apple
// item 1 is banana
// item 2 is kiwifruit
// K o t l i n

click to run

Kotlin doesn’t support a C/Java style for loop. Instead, we use a range collection .. that generates a sequence of values.

fun main() {
  // invalid in Kotlin
  // for (int i=0; i < 10; ++i)

  // range provides the same funtionality
  for (i in 1..3) {
    print(i)
  }
  println() // space out our answers

  // descending through a range, with an optional step
  for (i in 6 downTo 0 step 2) {
    print("$i ")
  }
  println()

  // we can step through character ranges too
  for (c in 'A'..'E') {
    print("$c ")
  }
  println()

  // Check if a number is within range:
  val x = 10
  val y = 9
  if (x in 1..y+1) {
    println("fits in range")
  }
}

// 123
// 6 4 2 0
// A B C D E
// fits in range

click to run

while#

while and do... while exist and use familiar syntax.

fun main() {
  var i = 1
  while ( i <= 10) {
    print("$i ")
    i++
  }
}

// 1 2 3 4 5 6 7 8 9 10

click to run

when#

when replaces the switch operator of C-like languages:

fun main() {
  val x = 2
  when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> print("x is neither 1 nor 2")
  }
}

// x == 2

click to run

fun main() {
    val x = 13
    val validNumbers = listOf(11,13,17,19)

    when (x) {
    	0, 1 -> print("x == 0 or x == 1")
    	in 2..10 -> print("x is in the range")
    	in validNumbers -> print("x is valid")
    	!in 10..20 -> print("x is outside the range")
    	else -> print("none of the above")
  }
}

// x is valid

click to run

We can also return a value from when. Here’s a modified version of this example:

fun main() {
    val x = 13
    val validNumbers = listOf(11,13,17,19)

    val response = when (x) {
        0, 1 -> "x == 0 or x == 1"
        in 2..10 -> "x is in the range"
        in validNumbers -> "x is valid"
        !in 10..20 -> "x is outside the range"
        else -> "none of the above"
    }
    println(response)
}

// x is valid

click to run

When is flexible. To evaluate any expression, you can move the comparison expressions into when statement itself:

fun main() {
    val x = 13

    val response = when {
        x < 0 -> "negative"
        x >= 0 && x <= 9 -> "small"
        x >=10 -> "large"
        else -> "how do we get here?"
    }
    println(response)
}

// large

click to run

return#

Kotlin has three structural jump expressions:

  • return by default returns from the nearest enclosing function or anonymous function
  • break terminates the nearest enclosing loop
  • continue proceeds to the next step of the nearest enclosing loop

Functions#

Functions are preceded with the fun keyword. Function parameters require types, and are immutable. Return types should be supplied after the function name, but in some cases may also be inferred by the compiler.

Named Functions#

Named functions has a name assigned to them that can be used to invoke them directly (this is the expected form of a “function” in most cases, and the form that you’re probably expecting).

// no parameters required
fun main() {
    println(sum1(1, 2))
    println(sum1(3,4))
}

// parameters which require type annotations
fun sum1(a: Int, b: Int): Int {
    return a + b
}

// return types can be inferred based on the value you return
// it's better form to explicitly include the return type in the signature
fun sum2(a: Int, b: Int) {
    a + b // Kotlin knows that (Int + Int) -> Int
}

// 3
// 7

click to run

Single-Expression Functions#

Simple functions in Kotlin can sometimes be reduced to a single line aka a single-expression function.

// previous example
fun sumOf(a: Int, b: Int):Int {
    return a + b
}

// this works since we evaluate a single expression
fun minOf(a: Int, b: Int) = if (a < b) a else b

fun main() {
    println(sumOf(5,10))
    println(minOf(10,20))
}

// 15
// 10

click to run

Default arguments#

We can use default arguments for function parameters. When called, a parameter with a default value is optional; if the caller does not provide the value, then the default will be used.

// Second parameter has a default value, so it’s optional
fun mult(a:Int, b:Int = 5): Int {
	return a * b
}

fun main() {
	println(mult(1)) // a=1, b=5 default
	println(mult(5,2)) // a=5, b=2
	// mult() would throw an error, since `a` must be provided
}

click to run

Named parameters#

You can (optionally) provide the parameter names when you call a function. If you do this, you can even change the calling order!

fun repeat(str:String="*", count:Int=1):String {
    return str.repeat(count)
}

fun main() {
	println(repeat()) // *
	println(repeat(str="#")) // *
	println(repeat(count=3)) // ***
	println(repeat(str="#", count=5)) // #####
	println(repeat(count=5, str="#")) // #####
}

// *
// #
// ***
// #####
// #####

click to run

Collections#

A collection is a finite group of some variable numbers of items (possibly zero) of the same type. Objects in a collection are called elements.

Collections in Kotlin are contained in the kotlin.collections package, which is part of the Kotlin Standard Library.

These collection classes exist as generic containers for a group of elements of the same type e.g. List<Int> would be an ordered list of integers. Collections have a finite size, and are eagerly evaluated.

Kotlin offers functional processing operations (e.g. filter, map and so on) on each of these collections.

fun main() {
  val list = (1..10).toList() // generate list of 1..10
  println( list.take(5).map{it * it} ) // square the first 5 elements
}

// [1, 4, 9, 16, 25]

click to run

Under-the-hood, Kotlin uses Java collection classes, but provides mutable and immutable interfaces to these classes. Kotlin best-practice is to use immutable for read-only collections whenever possible (since mutating collections is often very costly in performance).

{.compact}

Collection Class Description
Pair A tuple of two values.
Triple A tuple of three values.
List An ordered collection of objects.
Set An unordered collection of objects.
Map An associative dictionary of keys and values.
Array An indexed, fixed-size collection of objects.

A tuple is a data structure representing a sequence of n elements.

Pair#

A Pair is a tuple of two values. Use var or val to indicate mutability. Theto keyword can be used to indicate a Pair.

fun main() {
  // mutable
  var nova_scotia = "Halifax Airport" to "YHZ"
  var newfoundland = Pair("Gander Airport", "YQX")
  var ontario = Pair("Toronto Pearson", "YYZ")
  ontario = Pair("Billy Bishop", "YTZ") // reassignment is ok

  // accessing elements
  val canadian_exchange = Pair("CDN", 1.38)
  println(canadian_exchange.first) // CDN
  println(canadian_exchange.second) // 1.38

  // destructuring
  val (first, second) = Pair("Calvin", "Hobbes") // split a Pair
  println(first) // Calvin
  println(second) // Hobbes
}

// CDN
// 1.38
// Calvin
// Hobbes

click to run

Pairs are extremely useful when working with data that is logically grouped into tuples, but where you don’t need the overhead of a custom class., e.g. Pair for 2D points.

List#

A List is an ordered collection of objects.

fun main() {
  // define an immutable list
  var fruits = listOf( "advocado", "banana")
  println(fruits.get(0))
  // advocado

  // add elements
  var mfruits = mutableListOf( "advocado", "banana")
  mfruits.add("cantaloupe")
  mfruits.forEach { println(it) }

  // sorted/sortedBy returns ordered collection
  val list = listOf(2,3,1,4).sorted() // [1, 2, 3, 4]
  list.sortedBy { it % 2 } // [2, 4, 1, 3]

  // groupBy groups elements on collection by key
  list.groupBy { it % 2 } // Map: {1=[1, 3], 0=[2, 4]}

  // distinct/distinctBy returns unique elements
  listOf(1,1,2,2).distinct() // [1, 2]
}

// advocado
// advocado
// banana
// cantaloupe

click to run

Set#

A Set is a generic unordered collection of unique elements (i.e. it does not support duplicates, unlike a List which does). Sets are commonly constructed with helper functions:

fun main() {
	val numbersSet = setOf("one", "two", "three", "four")
    println(numbersSet)

	val emptySet = mutableSetOf<String>()
    println(emptySet)
}

// [one, two, three, four]
// []

click to run

Map#

A Map is an associative dictionary containing Pairs of keys and values.

fun main() {
  // immutable reference, immutable map
  val imap = mapOf(Pair(1, "a"), Pair(2, "b"), Pair(3, "c"))
  println(imap)
  // {1=a, 2=b, 3=c}

  // immutable reference, mutable map (so contents can change)
  val mmap = mutableMapOf(5 to "d", 6 to "e")
  mmap.put(7,"f")
  println(mmap)
  // {5=d, 6=e, 7=f}

  // lookup a value
  println(mmap.get(5))
  // d

  // iterate over key and value
  for ((k, v) in imap) {
    print("$k=$v ")
  }
  // 1=a 2=b 3=c

  // alternate syntax
  imap.forEach { k, v -> print("$k=$v ") }
  // 1=a 2=b 3=c

  // `it` represents an implicit iterator
  imap.forEach {
    print("${it.key}=${it.value} ")
  }
  // 1=a 2=b 3=c
}

// {1=a, 2=b, 3=c}
// {5=d, 6=e, 7=f}
// d
// 1=a 2=b 3=c 1=a 2=b 3=c 1=a 2=b 3=c

click to run

Operations#

Collection classes (e.g. List, Set, Map, Array) have built-in operations for working with the data that they contain. These are functions that frequently accept other functions as parameters.

Filter#

filter produces a new list of those elements that return true from a predicate function.

fun main() {
	val list = (1..100).toList()
	val filtered = list.filter { it % 5 == 0 }
    println(filtered)
	// 5 10 15 20 ... 100

	val below50 = filtered.filter { it in 0..49 }
    println(below50)
	// [5, 10, 15, 20]
}

// [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]
// [5, 10, 15, 20, 25, 30, 35, 40, 45]

click to run

Map#

map produces a new list that is the results of applying a function to every element that it contains.

fun main() {
	val list = (1..100).toList()
	val doubled = list.map { it * 2 }
    println(doubled)
}

// 2 4 6 8 ... 200

click to run

Reduce#

reduce accumulates values starting with the first element and applying an operation to each element from left to right.

fun main() {
	val strings = listOf("a", "b", "c", "d")
	println(strings.reduce { acc, string -> acc + string }) // abcd
}

// abcd

click to run

Zip#

zip combines two collections, associating their respective pairwise elements.

fun main() {
	val foods = listOf("apple", "kiwi", "broccoli", "carrots")
	val fruit = listOf(true, true, false, false)
    println(fruit)

	val results = foods.zip(fruit)
	println(results)
}

// [true, true, false, false]
// [(apple, true), (kiwi, true), (broccoli, false), (carrots, false)]

click to run

A more realistic scenario might be where you want to generate a pair based on the results of the list elements:

fun main() {
	val list = listOf("123", "", "456", "def")
	val exists = list.zip(list.map { !it.isBlank() })
    println(exists)

	val numeric = list.zip(list.map { !it.isEmpty() && it[0] in ('0'..'9') })
    println(numeric)
}

// [(123, true), (, false), (456, true), (def, true)]
// [(123, true), (, false), (456, true), (def, false)]

click to run

ForEach#

forEach calls a function for every element in the collection.

fun main() {
	val fruits = listOf("advocado", "banana", "cantaloupe" )
	fruits.forEach { print("$it ") }
}

// advocado banana cantaloupe

click to run

We also have helper functions to extract specific elements from a list.

Take#

take returns a collection containing just the first n elements. drop returns a new collection with the first n elements removed.

fun main() {
	val list = (1..50)
	val first10 = list.take(10)
	println(first10)

	val last40 = list.drop(10)
	println(last40)
}

// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// [11, 12, 13, 14, 15, 16, 17, 18, 19, ...]

click to run

First, Last, Slice#

first and last return those respective elements. slice allows us to extract a range of elements into a new collection.

fun main() {
	val list = (1..50)
	val even = list.filter { it % 2 == 0 }

	println(even.first()) // 2
	println(even.last()) // 50
	println(even.slice(1..3)) // 4 6 8
}

// 2
// 50
// [4, 6, 8]

click to run

Object-Oriented Kotlin#

Object-oriented programming is a refinement of the structured programming model that was discovered in 1966 by Ole-Johan Dahl and Kristen Nygaard.

It is characterized by the use of classes as a template for common behaviour (methods) and data (state) required to model that behaviour.

  • Abstraction: Model entities in the system to match real world objects. Each object exposes a stable high-level interface that can be used to access its data and behaviours. “Changing the implementation should not break anything.”
  • Encapsulation: Keep state and implementation private.
  • Inheritance: Specialization of classes. Create a specialized (child) class by deriving from another (parent) class, and reusing the parent’s fields and methods.
  • Polymorphism: Base and derived classes share an interface, but have specialized implementations.

Object-oriented programming has benefits over iterative programming:

  • It supports abstraction and allows us to express computation in a way that models our problem domain (e.g. customer classes, file classes).
  • It handles complex state more effectively, by delegating to classes.
  • It’s also a method of organizing your code. This is critical as programs grow.
  • There is some suggestion that it makes code reuse easier (debatable).
  • It became the dominant programming model in the 80s, and our most used languages are OO languages. e.g. C++, Java, Swift, Kotlin.

Classes#

Kotlin is a class-based object-oriented language, with some advanced features that it shares with some other recent languages. The class keyword is used to define a Class. You create an instance of the class using the class name (no new keyword required!)

 // define class
 class Person

 // create two instances and assign to p, q
 // note that we have an implicit no-arg constructor
 val p = Person()
 val q = Person()

Classes include properties (values) and methods.

Properties#

A property is a variable declared in a class, but outside methods or functions. They are analogous to class members, or fields in other languages.

class Person() {
   var firstName = "Vanilla"
   var lastName = "Ice"
}

fun main() {
	val p = Person()

    // we can access properties directly
    // this calls an implicit get() method; default returns the value
    println("${p.firstName} ${p.lastName} ${p.lastName} Baby")
}

click to run

Properties have implicit backing fields that store their data. We can override the get() and set methods to determine how our properties interact with the backing fields.

For example, for a City class, we can decide that we want the city name always reported in uppercase, and we want the population always stored as thousands.

 // the backing field is just referred to as `field`
 // in the set() method, we use `value` as the argument
 class City() {
   var name = ""
     get() = field.uppercase()
     set(value) {
       field = value
     }
   var population = 0
     set(value) {
       field = value/1_000
     }
 }

 fun main() {
     // create our city, using properties to access values
     val city = City()
     city.name = "Halifax"
     city.population = 431_000
     println("${city.name} has a population of ${city.population} thousand people")
 }

click to run

Behind-the-scenes, Kotlin is actually creating getter and setter methods, using the convention of getField and setField. In other words, you always have corresponding methods that are created for you. If you directly access the field name, these methods are actually getting called in the background.

Venkat Subramaniam has an excellent example of this (Subramaniam 2019). Write the class Car in a separate file named Car.kt:

 class Car(val yearOfMake: Int, var color: String)

Then compile the code and take a look at the bytecode using the javap tool, by running these commands:

 $ kotlinc-jvm Car.kt
 $ javap -p Car.class

This will display the bytecode generated by the Kotlin Compiler for the Car class:

 public final class Car {
   private final int yearOfMake;
   private java.lang.String color;
   public final int getYearOfMake();
   public final java.lang.String getColor();
   public final void setColor(java.lang.String);
   public Car(int, java.lang.String);
 }

That concise single line of Kotlin code for the Car class resulted in the creation of two fields—the backing fields for properties, a constructor, two getters, and a setter.

Constructors#

Like other OO languages, Kotlin supports explicit constructors that are called when objects are created.

Primary Constructors#

A primary constructor is the main constructor that your class will support (representing how you want it to be instantiated most of the time). You define it by expanding the class definition:

 // class definition includes the primary constructor
 class Person constructor() { }

 // we can collapse this to define an explicit no-arg constructor
 class Person() {}

In the example above, the primary constructor is called when this class is instantiated.

Optionally, you can include parameters in the primary constructor, and use these to initialize parameters in the constructor body.

 // constructor with arguments
 // this uses the parameters to initialize properties (i.e. variables)
 class Person (first:String, last:String) {
   val firstName = first.take(1).uppercase() + first.drop(1).lowercase()
   val lastName = last.take(1).uppercase() + last.drop(1).lowercase()

   // adding a statement like this will prevent the code from compiling
   // println("${firstname} ${lastname}")  // will not compile
 }

 fun main() {
	 // this does not work! we do not have a no-arg constructor
	 // val person = Person() // error since no matching constructor

	 // this works and demonstrates the properties
	 val person = Person("JEFF", "AVERY")
	 println("${person.firstName} ${person.lastName}") // Jeff Avery
 }

click to run

Constructors are designed to be minimal:

  • Parameters can only be used to initialize properties. They go out of scope immediately after the constructor executes.
  • You cannot invoke any other code in your constructor (there are other ways to handle that, which we will discuss below).
Secondary Constructors#

What if you need more than a single constructor?

You can define secondary constructors in your class. Secondary constructors must delegate to the primary constructor. Let’s rewrite this class to have a primary no-arg constructor, and a second constructor with parameters.

// primary constructor
class Person() {
	// initialize properties
   	var firstName = "PAULA"
   	var lastName = "ABDUL"

   	// secondary constructor
   	// delegates to the no-arg constructor, which will be executed first
   	constructor(first: String, last: String) : this() {
   		// assign to the properties defined in the primary constructor
   		firstName = first.take(1).uppercase() + first.drop(1).lowercase()
   		lastName = last.take(1).uppercase() + last.drop(1).lowercase()
   	}
}

fun main() {
	val person1 = Person() // primary constructor using default property values
	println("${person1.firstName} ${person1.lastName}")

	val person2 = Person("JEFF", "AVERY") // secondary constructor
	println("${person2.firstName} ${person2.lastName}")
}

click to run

Init Blocks#

How do we execute code in the constructor? We often want to do more than initialize properties.

Kotlin has a special method called init() that is used to manage initialization code. You can have one or more of these init blocks in your code, which will be called in order after the primary constructor (they’re actually considered part of the primary constructor). The order of initialization is (1) primary constructor, (2) init blocks in listed order, and then finally (3) secondary constructor.

class InitOrderDemo(name: String) {
   val first = "$name".also(::println)

   init {
     println("First init: ${first.length}")
   }

   val second = "$name".also(::println)
   init {
     println("Second init: ${second.length}")
     }
 }

fun main() {
    InitOrderDemo("Jeff")
}

Why does Kotlin split the constructor up like this? It’s a way to enforce that initialization MUST happen first, which results in cleaner and safer code.

Class Methods#

Similarly to other programming languages, functions defined inside a class are called methods.


class Person(var firstName: String, var lastName: String) {
   fun greet() {
     println("Hello! My name is $firstName")
   }
 }

 fun main() {
 	val person = Person ("Jeff", "Avery")
 	println("${person.firstName} ${person.lastName}")
 }

Inheritance#

To derive a class from a supertype, we use the colon : operator. We also need to delegate to the base class constructor using ().

By default, classes and methods are closed to inheritance. If you want to extend a class or method, you need to explicitly mark it as open for inheritance.

 class Base
 class Derived : Base() // error!

 open class Base
 class Derived : Base() // ok

Kotlin supports single-inheritance.

open class Person(val name: String) {
   open fun hello() = "Hello, I am $name"
 }

 class PolishPerson(name: String) : Person(name) {
   override fun hello() = "Dzien dobry, jestem $name"
 }

 fun main() {
     val p1 = Person("Jerry")
     val p2 = PolishPerson("Beth")
     println(p1.hello())
     println(p2.hello())
 }

All classes in Kotlin have a common superclass Any, that is the default superclass for a class with no supertypes declared:

 class Example // Implicitly inherits from Any

Any has three methods: equals(), hashCode() and toString(). Thus, they are defined for all Kotlin classes.

Abstract Classes#

Classes can be declared abstract, which means that they cannot be instantiated, only used as a supertype. The abstract class can contain a mix of implemented methods (which will be inherited by subclasses) and abstract methods, which do not have an implementation.

// useful way to represent a 2D point
 data class Point(val x:Int, val y:Int)

 abstract class Shape() {
   // we can have a single representation of position
   var x = 0
   var y = 0

   fun position(): Point {
     return Point(x, y)
   }
   // subtypes will have their own calculations for area
   abstract fun area():Int
 }

 class Rectangle (var width: Int, var height: Int): Shape() {
   constructor(x: Int, y: Int, width: Int, height: Int): this(width, height) {
     this.x = x
     this.y = y
   }
   // must be overridden since our base is abstract
   override fun area():Int {
     return width * height
   }
 }

 fun main() {
	// this won't compile, since Shape() is abstract
	// val shape = Shape()

	// this of course is fine
	val rect = Rectangle(10, 20, 50, 10)
	println("Rectangle at (${rect.position().x},${rect.position().y}) with area ${rect.area()}")
	// => Rectangle at (10,20) with area 500
 }

Interfaces#

Interfaces in Kotlin are similar to abstract classes, in that they can contain a mix of abstract and implemented methods. What makes them different from abstract classes is that they cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.

Data Classes#

A data class is a special type of class, which primarily exists to hold data, and does not have custom methods. Classes like this are more common than you expect – we often create trivial classes to just hold data, and Kotlin makes them simple to create.

Why would you use a data class over a regular class? It generates a lot of useful methods for you:

  • hashCode()
  • equals() // compares fields
  • toString()
  • copy() // using fields
  • destructuring

Here’s an example of how useful this can be:

data class Person(val name: String, var age: Int)

fun main() {
	val mike = Person("Mike", 23)

	// toString() displays all properties
	println(mike.toString())

	// structural equality (==) compares properties
	println(mike == Person("Mike", 23)) // True
	println(mike == Person("Mike", 21)) // False

	// referential equality (===) compares object references
	println(mike === Person("Mike", 23)) // False

	// hashCode based on primary constructor properties
	println(mike.hashCode() == Person("Mike", 23).hashCode()) // True
	println(mike.hashCode() == Person("Mike", 21).hashCode()) // False

	// destructuring based on properties
	val (name, age) = mike
	println("$name $age") // Mike 23

	// copy that returns a copy of the object
	// with concrete properties changed
	val jake = mike.copy(name = "Jake") // copy
}

Enum Classes#

Enums in Kotlin are classes, so enum classes support type safety.

We can use them in expected ways. Enum num constants are separated with commas. We can also do interesting things with our enums, e.g. use them in when clauses (Example from Sommerhoff 2020).

 enum class Suits {
     HEARTS, SPADES, DIAMONDS, CLUBS
 }

fun main() {
 	val color = when(Suits.SPADES) {
   		Suits.HEARTS, Suits.DIAMONDS -> "red"
   		Suits.SPADES, Suits.CLUBS -> "black"
 	}
 	println(color)
}

Each enum constant is an object, and can be instantiated.

enum class Direction(val degrees: Double) {
	NORTH(0.0), SOUTH(180.0), WEST(270.0), EAST(90.0)
}

fun main() {
	val direction = Direction.EAST
	print(direction.degrees)
}

Operator Overloading#

Kotlin allows you to provide custom implementations for the predefined set of operators. These operators have predefined symbolic representation (like + or *) and precedence if you combine them.

Basically, you use the operator keyword to define a function, and provide a member function or an extension function with a specific name for the corresponding type. This type becomes the left-hand side type for binary operations and the argument type for the unary ones.

Here’s an example that extends a class named ClassName by overloading the + operator.

data class Point(val x: Double, val y: Double)

// -point
operator fun Point.unaryMinus() = Point(-x, -y)

// p1+p2
operator fun Point.plus(other: Point) = Point(this.x + other.x, this.y + other.y)

// p1*5
operator fun Point.times(scalar: Int) = Point(this.x * scalar, this.y * scalar)
operator fun Point.times(scalar: Double) = Point(this.x * scalar, this.y * scalar)

fun main() {
    val p1 = Point(5.0, 10.0)
	val p2 = Point(10.0, 12.0)

    println("p1=${p1}")
    println("p2=${p2}\n")
    println("-p1=${-p1}")
    println("p1+p2=${p1+p2}")
    print("p2*5=${p2*5}")
}

We can override any operators by using the keyword that corresponds to the symbol we want to override.

Note that this is the reference object on which we are calling the appropriate method. Parameters are available as usual.

Description Expression Translated to
Unary prefix +a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
Increments, decrements a++ a.inc()
a– a.dec()
Arithmetic a+b a.plus(b)
a-b a.minus(b)
a*b a.times(b)
a/b a.div(b)
a%b a.rem(b)
a..b a.rangeTo(b)
In a in b b.contains(a)
Augmented assignment a+=b a.plusAssign(b)
a-=b a.minusAssign(b)
a*=b a.timesAssign(b)
a/=b a.divAssign(b)
a%b a.remAssign(b)
Equality a==b a?.equals(b) ?: (b === null)
a!=b !(a?.equals(b) ?: (b === null))
Comparison a>b a.compareTo(b) > 0
a<b a.compareTo(b) < 0
a>=b a.compareTo(b) >= 0
a<=b a.compareTo(b) <= 0

Infix Functions#

Functions marked with the infix keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must meet the following requirements:

For example, we can add a “shift left” function to the built-in Int class:


infix fun Int.shl(x: Int): Int {
	return (this shl x)
}

fun main() {
	// calling the function using the infix notation
	// shl 1 multiples an int by 2
 	println(212 shl 1)

 	// is the same as
 	println(212.shl(1))
}

Extension Functions#

Kotlin supports extension functions: the ability to add functions to existing classes, even when you don’t have access to the original class’s source code, or cannot modify the class for some reason. This is also a great alternative to inheritance when you cannot extend a class.

For a simple example, imagine that you want to determine if an integer is even. The “traditional” way to handle this is to write a function:

fun isEven(n: Int): Boolean = n % 2 == 0

fun main() {
	println(isEven(4))
	println(isEven(5))
}

In Kotlin, the Int class already has a lot of built-in functionality. It would be a lot more consistent to add this as an extension function to that class.

fun Int.isEven() = this % 2 == 0

fun main() {
    println(4.isEven())
    println(5.isEven())
}

You can use extensions with your own types and also types you do not control, like List, String, and other types from the Kotlin standard library.

Extension functions are defined in the same way as other functions, with one major difference: When you specify an extension function, you also specify the type the extension adds functionality to, known as the receiver type. In our earlier example, Int.isEven(), we need to include the class that the function extends, or Int.

Note that in the extension body, this refers to the instance of the type (or the receiver for this method invocation).

fun String.addEnthusiasm(enthusiasmLevel: Int = 1) = this + "!".repeat(enthusiasmLevel)

fun main() {
    val s1 = "I'm so excited"
    val s2 = s1.addEnthusiasm(5)
    println(s2)
}

Defining an extension on a superclass#

Extensions do not rely on inheritance, but they can be combined with inheritance to expand their scope. If you extend a superclass, all of its subclasses will inherit the extension method that you defined.

Define an extension on the Any class called print. Because it is defined on Any, it will be directly callable on all types.

// Any is the top-level class from which all classes derive i.e. the ultimate superclass.

fun Any.print() {
  println(this)
}

fun main() {
	"string".print()
	42.print()
}

Extension Properties#

In addition to adding functionality to a type by specifying extension functions, you can also define extension properties.

For example, here is an extension property that counts a string’s vowels:

val String.numVowels
    get() = count { it.lowercase() in "aeiou" }

fun main() {
    println("abcd".numVowels)
}

Destructuring#

Sometimes it is convenient to destructure an object into a number of variables. This syntax is called a destructuring declaration. A destructuring declaration creates multiple variables at once. In the example below, you declared two new variables: name and age, and can use them independently:

data class Person(val name: String, val age: Int)

fun main() {
  val p = Person("Janine", 38)
	val (name, age) = p  // destructuring
	println(name)
	println(age)
}

A destructuring declaration is compiled down to the following code:

 val name = person.component1()
 val age = person.component2()

component1(), component2() are aliases to the named properties in this class, in the order they were declared (and, of course, there can be component3() and component4() and so on). You would never normally refer to them using these aliases.

Here’s an example from the Kotlin documentation on how to use this to return multiple values from a function:

 // data class with properties `result` and `status`
 data class Result(val result: Int, val status: Status)

 fun function(...): Result {
     // computations
     return Result(result, status)
 }

 // Destructure into result and status
 val (result, status) = function(...)

 // We can also choose to not assign fields
 // e.g. we could just return `result` and discard `status`
 val (result, _) = function(...)

Companion Objects#

OO languages typically have some idea of static members: methods that are associated with a class instead of an instance of a class. Static methods can be useful when attempting to implement the singleton pattern, for instance.

Kotlin doesn’t support static members directly. To get something comparable in Kotlin, you need to declare a companion object as an inner class of an existing class. Any methods that are created as part of the companion object are considered to be static methods in the enclosing class.

The examples below are taken from: https://livevideo.manning.com/module/59_5_16/kotlin-for-android-and-java-developers

class House(val numberOfRooms: Int, val price: Double) {
   companion object {
     val HOUSES_FOR_SALE = 10
     fun getNormalHouse() = House(6, 599_000.00)
     fun getLuxuryHouse() = House(42, 7_000_000.00)
   }
 }

 fun main() {
   val normalHouse = House.getNormalHouse()  // works
   println(normalHouse.price)
   println(House.HOUSES_FOR_SALE)
 }

We can also use object types to implement singletons. All we need to do is use the object keyword.

 class Country(val name:String) {
     var area = 0.0
 }

 // there can be only one
 object CountryFactory {
     fun createCountry() = Country("Canada")
 }

 fun main() {
     val obj = CountryFactory.createCountry()
     println(obj.name)
 }

Final Word#

XKCD Bad Code
XKCD: Bad Code