Skip to content

Instantly share code, notes, and snippets.

@hartleybrody
Last active May 23, 2023 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hartleybrody/07e8850b11d497a2a3b5c8ae15f5d625 to your computer and use it in GitHub Desktop.
Save hartleybrody/07e8850b11d497a2a3b5c8ae15f5d625 to your computer and use it in GitHub Desktop.

kotlin cheatsheet

string interpolation

val foo = 1
var bar = "one is $foo"
var baz = "two is ${foo + 1}"

Unit is a placeholder return value for functions that don't actually return a meaninfgul value. It is the default return type for any function if one is not specified explicitly. Nothing is a placeholder return value for functions that should never actually return (ie they throw an exception or have infinite loop). Good SO discussion on differences.

nullable types

var a: String = “abc”
a = null  // won’t compile, can't assign null

var b: String? = “abc”
b = null  // ok

b.length // error, could be null
b?.length // ok, since you're explicit that it could be null

safe calls can be chained and the chain will return null if any of the properties are null, no null errors raised

bob?.department?.head?.name

elvis operator checks for null values

val length = b?.length ?: -1

you can ask for a NullPointerException explicitly with !! operator, if you really really want to...

val length = b!!.length

can also cast the variable to make it no longer nullable

val aInt: Int? = a as? Int

null items in a collection

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

function literals

used for lambda expressions + anonymous functions

max(strings, { a, b -> a.length < b.length })

// equivalent forms:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val sum = { x: Int, y: Int -> x + y }

lambda expression:

  • surrounded by curly braces
  • parameters go inside curly braces, have optional type annotations
  • function body goes after arrow ->
  • last expression is treated as return value
  • return type is inferred from last expression

data classes

while kotlin does have a map collection that works like standard hashes or dictionaries in other languages, a preferred method is to use data classes, which creates an interface of the keys you're expecting and the compiler adds handy methods like equals(), toString() and copy() for you automatically

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

// can set default values
data class User(val name: String = "", val age: Int = 0)

regular classes

more info on classes

// name is a primary constructor parameter
class Customer(name: String) {
    val customerKey = name.uppercase()
}

class Person(
    val firstName: String,
    val lastName: String,
    var isEmployed: Boolean = true
    var age: Int, // trailing comma
) { /*...*/ }

more info on properties

can have read-only properties with val keyword or mutable properties with var keyword

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter

can implement custom getters and setters. setters are called every time you assign a value to the property, except its initialization.

class Rectangle(val width: Int, val height: Int) {
    val area: Int // property type is optional since it can be inferred from the getter's return type
        get() = this.width * this.height

    // same as above, shorter without specifying type
    val area get() = this.width * this.height
    
    var stringRepresentation: String
        get() = this.toString()
        set(value) {  // convention to use "value" as the name of the setter parameter
            setDataFromString(value) // parses the string and assigns values to other properties
        }
}

lateinit is used to specify not-null properties after object initilization for things like unit tests or dependency injections.

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

sealed classes allow you to restrict which classes are allowed to subclass the current class, so you know them all at compile time. commonly used for error classes, sealed classes are abstract and therefore cannot be instantiated directly.

extension functions allow you to bolt a function onto an existing type. this inside the function refers to the receiver object, passed before the dot.

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment