"Advanced Kotlin Programming" Notes
These are my personal notes. The complete course can be found at https://www.safaribooksonline.com/videos/advanced-kotlin-programming/9781491964149/
Local functions
fun global(globalParam: Int) {
// local is accessible only inside global
fun local(localParam: Int) {
// Here we can access globalParam and localParam
}
}
Infix functions
Applicable to both member functions and extension functions that have single parameters.
infix fun Int.isDivisibleBy(other: Int) = this % other == 0
fun main(args: Array<String>) {
println(8 isDivisibleBy 2) // true
}
Anonymous functions
someOtherFunction(fun(x: Int): Int {
if (x == 25) {
return 50;
} else {
return x * 20;
}
})
An anonymous function has two advantages over lambdas:
- It can have an explicit return type.
- It can have multiple return points.
Inline functions
If we add the inline
modifier to a function, it’ll get inlined at every call site. This is used mostly for optimization purposes when dealing with higher-order functions, and using it with other functions that aren’t of higher-order may provide negligible benefits.
inline fun executeAndLog(op: () -> Unit) {
println("Execution starts!")
op()
println("Execution has finished.")
}
If we wish to NOT inline one of the lambdas, we can add the noinline
modifier to the parameter.
inline fun executeAndLog(noinline op: () -> Unit) { ... }
With that said, in the previous example it wouldn’t make sense to inline the function if the only lambda parameter isn’t going to be inlined. IntelliJ will raise a warning if we try to do this.
More information about inlining: https://kotlinlang.org/docs/reference/inline-functions.html
Returns and local returns
Wall of code incoming!
fun <T> Iterable<T>.nonInlineForEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
fun main(args: Array<String>) {
val numbers = 1..10
nonLocalReturn(numbers)
defaultLabelLocalReturn(numbers)
customLabelLocalReturn(numbers)
anonymousFunctionLocalReturn(numbers)
// Non local returns are allowed only in inline functions used as lambda
//numbers.nonInlineForEach {
// if (it % 7 == 0) {
// return // this doesn't compile
// }
//}
println("main finished.")
}
fun anonymousFunctionLocalReturn(numbers: Iterable<Int>) {
numbers.forEach(fun(element) {
if (element % 7 == 0) {
return
}
})
println("anonymousFunctionLocalReturn")
}
fun customLabelLocalReturn(numbers: Iterable<Int>) {
numbers.forEach myLabel@{
if (it % 7 == 0) {
return@myLabel
}
}
println("customLabelLocalReturn")
}
fun nonLocalReturn(numbers: Iterable<Int>) {
numbers.forEach {
if (it % 7 == 0) {
return // this returns out from nonLocalReturn
}
}
// This will never be called.
println("nonLocalReturn")
}
fun defaultLabelLocalReturn(numbers: Iterable<Int>) {
numbers.forEach {
if (it % 7 == 0) {
return@forEach
}
}
println("defaultLabelLocalReturn")
}
What will get printed after executing that is:
defaultLabelLocalReturn
customLabelLocalReturn
anonymousFunctionLocalReturn
main
Key points:
- An unlabeled
return
returns out of the nearestfun
keyword. While a not very elaborate explanation, is a more than adequate heuristic. - Unlabeled
return
s can only be used inside lambdas if they’re passed to inline functions. - Labeled
return
s can be used inside lambdas for both inline and non-inlined functions.
Tail recursion
Tail recursion is a common optimization that compiles a recursive operation into a looping one, preventing stack overflows and providing better performance (citation required) as long as our function meets some criteria.
Kotlin can optimize tail recursion, but it doesn’t do it by default. If the last operation of a function is a call to itself, we can add the tailrec
modifier to it to convert it into tail recursive.
tailrec fun factorial(number: BigInteger, accumulator: BigInteger = BigInteger.ONE): BigInteger {
when (number) {
BigInteger.ZERO -> return accumulator
else -> return factorial(number - BigInteger.ONE, accumulator * number)
}
}
fun main(args: Array<String>) {
val result = factorial(BigInteger.valueOf(10000))
println(result)
}
The previous example will throw a StackOverflowException if we remove the tailrec
from the factorial function.
Operator overloading
To implement an operator for our custom data type, we must create a function named accordingly as per the list that’s below, and add the operator
modifier to it.
class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(this.x + other.x, this.y + other.y)
}
}
fun main(args: Array<String>) {
val a = Point(10, 10)
val b = Point(5, 5)
val c = a + b
println("${c.x}, ${c.y}")
}
Only the following operators can be implemented in our custom data types:
Unary operators
+a
=a.unaryPlus()
-a
=a.unaryMinus()
!a
=a.not()
a++
=a.inc()
a-
=a.dec()
Binary operators
a + b
=a.plus(b)
a - b
=a.minus(b)
a * b
=a.times(b)
a / b
=a.div(b)
a % b
=a.mod(b)
a .. b
=a.rangeTo(b)
a in b
=b.contains(a)
a !in b
=!b.contains(a)
a += b
=a.plusAssign(b)
a -= b
=a.minusAssign(b)
a *= b
=a.timesAssign(b)
a /= b
=a.divAssign(b)
a %= b
=a.modAssign(b)
a > b
=a.compareTo(b) > 0
a < b
=a.compareTo(b) < 0
a >= b
=a.compareTo(b) >= 0
a <= b
=a.compareTo(b) <= 0
Indexing operations
a[i]
=a.get(i)
a[i, j]
=a.get(i, j)
a[i1, i2, ..., iN]
=a.get(i1, i2, ..., iN)
a[i] = b
=a.set(i, b)
a[i, j] = b
=a.set(i, j, b)
a[i1, i2, ..., iN] = b
=a.set(i1, i2, ..., iN, b)
Invocation operations
a()
=a.invoke()
a(x)
=a.invoke(x)
a(x, y)
=a.invoke(x, y)
a(x1, x2, ... xN)
=a.invoke(x1, x2, ..., xN)
It’s possible to implement operator functions as extension functions too.
Lambda extensions
Extension functions can access properties of the extended class.
Lambda extensions (AKA lambdas with receivers) are lambdas that can access properties of the extended class.
It sounds simple, but… it can be confusing as fuck if we don’t start from the beginning and add stuff as we go.
class Something(val name: String)
fun Something.printName() {
println("Inside printName. name is $name.")
}
fun executeOnThing(thing: Something, operation: Something.() -> Unit) {
operation(thing)
}
fun main(args: Array<String>) {
val ball = Something("Ball")
executeOnThing(ball, Something::printName)
executeOnThing(ball) {
println("Inside lambda extension. name is $name.")
}
}
While very abstract and uncreative, at least it’s not an animal example. First:
operation: Something.() -> Unit
It’s a parameter of type “function with receiver that returns Unit”. We can pass an extension function as an argument, or a lambda expression.
We did the former when we passed Something.printName()
as an argument in executeOnThing(ball, Something::printName)
.
We did the latter when we passed a lambda function. Just like a normal extension function, the lambda will be able to access the relevant object instance by means of this
.
When we call the function with receiver, we must pass an instance of the corresponding type: the receiver. We did that on the executeOnThing
function.
An actual, less forced example, can be seen at https://kotlinlang.org/docs/reference/type-safe-builders.html. The course video for this particular functionality was very confusing, so it’s best to just check the official documentation.
Invoking instances
Easily missed in the operator overloading section. We can make any object instance invocable by implementing the invoke
operator function.
class PowerOffCommand() : Command() {
override fun execute() {
// poweroff computer, etc.
}
operator fun invoke() {
execute()
}
}
val cmd = PowerOffCommand()
cmd()
Obviously this example wouldn’t make that much sense in a language with higher-order functions like Kotlin, but that’s not the point ;).
Functional constructs
Beyond passing functions from place to place, there are several other concepts that make the toolbox of a functional programmer. Currying, function composition, and several other techniques aren’t implemented OOB in Kotlin, but can be replicated by making use of the already discussed constructs.
There’s a Kotlin library that already implements several of those things for us:
I’m no functional expert, so I’m leaving further exploration to some other time.
Fields
In custom getters and setters we can access the automatically created backing field by using the keyword field
.
If we can’t (or don’t want to) use the auto backing field, then we should make a private property and reference it.
Late initialization
class RegistrationService {
lateinit var userRepository: UserRepository
}
If for whatever reason we have a property that’s not nullable, but that we are unable to assign in the constructor for whatever reason, we can add the lateinit
modifier to it, telling the compiler this way that we are responsible for assigning it a value later on.
If we try to access a uninitialized property, we’ll get a kotlin.UninitializedPropertyAccessException
.
Nested classes
You can declare classes inside other classes, just like in C#. By default, the outer class acts like a namespace for the inner class and not much anything else.
val instance = OuterClass.InnerClass()
It becomes different to (and much useful than) C# when we add the inner
modifier to the mix. When we add the inner
modifier to a nested class, it becomes a inner class, instances of which can only be created by an instance of the outer class, but in exchange it becomes able to access properties of it’s enclosing class.
Companion objects
In Kotlin there are no static methods. There are top level functions, and functions in objects, though.
But what if we really, really need to use static functions?
class ExampleClass {
companion object Objeto {
@JvmStatic fun exampleStaticMethod() {
println("Static method!")
}
}
}
fun main(args: Array<String>) {
ExampleClass.Objeto.exampleStaticMethod() // syntax for non companion objects
ExampleClass.exampleStaticMethod() // companion object syntax
}
Notes
- We can have only ONE (1) object marked as the companion object.
- If we mark an object as the companion object, then it doesn’t need to have a name (i.e. we could delete “Objeto” from the source example).
- If we want Java-bois to be able to access the companion object method as a static method, then we’ll need to add the
JvmStatic
annotation to our function. Otherwise they’ll need to access it with theExampleClass.Objeto.exampleStaticMethod
syntax if the companion object is called Objeto (as per our example), orExampleClass.Companion.exampleStaticMethod
if the companion object has no name.
Hiding constructors
class ExampleClass private constructor() {
// ...
}
We explicitly declare the main constructor as private. That’s it.
Sealed classes
https://kotlinlang.org/docs/reference/sealed-classes.html
Sealed classes are classes that can only be subclassed by other classes in the same file. They’re used to represent restricted class hierarchies, like for example possible results of an operation (e.g. results from an application layer service call).
The big selling point for sealed classes is to use them in when expressions (not statements), as when checking for types of a sealed class, then we can make sure we’re covering all cases, voiding the need of a superfluous else case.
fun eval(result: Result): Boolean = when(expr) {
is Success -> true
is Failure -> false
}
Yes, there are enums too, but enums have a single instance for each enum value. With a sealed class’ derived classes we can have several instances for each derived type, where we can include additional context as needed.
Type aliases
typealias Point = Pair<Int, Int>
var point: Point = Point(5, 7)
No new type is created, any function that takes a Pair<Int, Int>
will also be able to take a Point
.
The concept of delegation
In OOP it’s possible to make use of other classes by either inheriting from them or by composition. After more-than-enough unmaintainable hierarchies in practice, it has become common practice to prefer composition over the alternative.
Delegation is the practice of… err… delegating some functionality to another class.
class Rectangle(val width: Int, val height: Int) {
fun area() = width * height
}
class Window(val bounds: Rectangle) {
// Delegation
fun area() = bounds.area()
}
Delegating member functions
Kotlin supports delegating member function calls automatically by means of the by
keyword.
class Controller(repository: Repository): Repository by repository {
fun index(): String {
getById() // this is automatically delegated to repository
}
}
Delegating properties
https://kotlinlang.org/docs/reference/delegated-properties.html
class ExampleClass {
var someProperty: String by SomeOtherClass()
}
class SomeOtherClass {
var backingField = "Default"
operator fun getValue(service: Service, property: KProperty<*>): String {
return backingField
}
operator fun setValue(service: Service, property: KProperty<*>, value: String) {
backingField = value
}
}
property
contains metadata about the delegated property. Creating custom delegates is something better covered in the official documentation, so I’m omitting further study of it for now.
Built-in delegated properties
https://kotlinlang.org/docs/reference/delegated-properties.html#standard-delegates https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/index.html
There are at least three notable standard delegated properties:
lazy
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/lazy.htmlobservable
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/observable.htmlvetoable
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/vetoable.html
observable example:
var name: String by Delegates.observable("<no name>") { prop, old, new ->
println("$old -> $new")
}
vetoable example:
var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue > oldValue
}
Local delegates
Local variables can also be delegated properties.
val cachedComputation by lazy(computeResult)
computeResult
will be executed once, to initialize cachedComputation
. Later accesses to the later will not execute computeResult
again.
Extension properties
https://kotlinlang.org/docs/reference/extensions.html#extension-properties
They can’t have a backing field, nor an initializer (which would need a the backing field). Their behavior must be explicitly defined in their getter and setter.
val Int.isTheAnswer: Boolean
get() = this == 42
Generic constraints
By default, any generic type parameter is constrained to Any?
.
We can add a single constraint by defining the type parameter as <T: MyConstraint>
.
We can add multiple constraints by defining the type parameter as <T> where T: MyFirstConstraint, T: SecondConstraint
For the record, using constrained generics in functions is done as follows: fun <T:Serializable> etc(myObj: T) { }
Invariance
Given Employee
is a subtype of Person
.
And given Structure<Employee>
it’s not a subtype of Structure<Person>
And given Structure<Person>
it’s not a subtype of Structure<Employee>
We say that Structure
is invariant.
As long as Structure
meets some criteria, we can make it covariant or contravariant.
Covariance & contravariance
See: https://blog.sinenie.cl/covariance-and-contravariance/
Type projections
Variance in Kotlin is usually defined at declaration-site.
It’s possible to define it at the call site, which will create a restricted form of the type. This is known as type projection.
Star projections ?? if (element is List<*>) safe way to indicate a subtype of a projection??
An introduction to reflection & metaprogramming
Metaprogramming is about observing and modifying programs in runtime. For this purpose, Kotlin can both use the classic Java reflection API, and it’s own API.
Scarce notes are taken about this topic, as I still don’t think I need to deep dive into reflection and metaprogramming. I still struggle with the “programming” part of Kotlin.
Using Java reflection
To access the Java Class object for an instance, call javaClass
on it.
To access the Java Class object for a class: MyClass::class.java
. Omitting the latter java
function call would leave us with a KClass
object, which is part of the Kotlin reflection API.
Using Kotlin reflection
Due to size concerns, and given that only a small percentage of projects actually need to use reflection functionality, the Kotlin reflection API is distributed as a separate .jar, not included with kotlin-stdlib
.
To include it in out Maven project, add the following to dependencies:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
Further documentation: https://kotlinlang.org/docs/reference/reflection.html
Type erasure on the JVM
JVM doesnt not maintain information about type parameters actually used in a generic class. This is called type erasure.
It mostly means that we can’t do this:
// This doesn't compile.
fun <T> printList(list: List<T>) {
when (list) {
// cannot check type of erased type.
is List<String> -> println("This is list of String")
is List<Int> -> println("This is list of Int")
}
}
And at the very best we can do THIS:
fun <T> printList(list: List<T>) {
if (list is List<*>) { // is this a list of something?
println("This is a list")
}
}
We can still do some checks on non generic data types, for example:
fun <T> printThing(thing: T) {
when (thing) {
is String -> println("This is String")
is Int -> println("This is Int")
}
}
Reified generics
https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md https://stackoverflow.com/questions/45949584/how-does-the-reified-keyword-in-kotlin-work
Simplified example:
inline fun <reified T> printTypeParameter() {
println(T::class)
}
fun main(args: Array<String>) {
printTypeParameter<String>()
}
Somewhat “solving” our previous issue:
inline fun <reified T> printList(list: List<T>) {
when (T::class) {
String::class -> println("T is String")
Int::class -> println("T is Int")
}
}
fun main(args: Array<String>) {
val myInts = listOf(1, 2, 3)
val myStrings = listOf("Hello", "World")
printList(myInts)
printList(myStrings)
}
Custom annotations
https://kotlinlang.org/docs/reference/annotations.html
// YO DAWG
@Target(AnnotationTarget.CLASS)
annotation class Table(val name: String)
@Target(AnnotationTarget.PROPERTY)
annotation class Field(val name: String)
@Table(name = "tbl_contact")
data class Contact(val id: Int, val name: String, @Field(name = "email") val email: String)
fun main(args: Array<String>) {
// Access class annotations
val annotations = Contact::class.annotations
// Access email property annotations
val emailAnnotations = Contact::class.memberProperties
.first { it.name == "email" }
.annotations
}
Asynchronous Kotlin Programming
https://kotlinexpertise.com/kotlin-coroutines-guide/ https://kotlinlang.org/docs/reference/coroutines.html
- Threading
- Async/Await pattern
- Futures
- Promises (kinda the JS implementation of futures)
- Reactive extensions
- Callbacks (please no)
Kotlin tries not to lock the developer into any of those possiblities for async programming, but by itself it brings something really interesing into the table: couroutines. Some of the listed patterns are implemented by making use of coroutines under the hood.
Coroutines & coroutine implementation
https://github.com/Kotlin/kotlinx.coroutines https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
NOTE: Not a trivial topic. Must investigate further.
Async and await
async and await aren’t keywords, but rather library functions. This pattern is implemented by means of coroutines.
import kotlinx.coroutines.async
import java.util.concurrent.CompletableFuture
private fun startLongAsyncOperation(v: Int) =
CompletableFuture.supplyAsync {
Thread.sleep(1000)
"Result: $v"
}
fun main(args: Array<String>) {
val future = async<String> {
(1..5).map {
await(startLongAsyncOperation(it))
}.joinToString("\n")
}
println("Before future")
println(future.get())
println("After future")
}
Yield (generators)
Neither a keyword.
import kotlinx.coroutines.generate
fun main(args: Array<String>) {
val sequence = generate<Int> {
for (i in 1..5) {
yield(i)
}
}
println(sequence.joinToString(" "))
}
Reactive extensions
TODO: Investigate further.