Learning Kotlin For the First Time
6 min read
For a number of work-related reasons, I've decided to start learning Kotlin. Whilst the vast majority of the backend services I work in will still be Python-based, any new services I create will be Kotlin (and Spring Boot). I've got an idea of a small service I'd like to create, the logic of which currently sits in a larger service where it doesn't really feel like it belongs. As a learning exercise, I thought I'd learn and practice Kotlin + Spring Boot by trying to extract this logic out into a new service.
First things first though, is learning enough Kotlin to get by. The nice thing about knowing at least one backend language is that it makes learning the next one a lot easier. I guess it's akin to learning French when you already know English - there's a large repository of shared words and ways of describing things, with the main task being to learn new grammatical rules to get that tiny bit of proficiency to start you off. From there, it's just about practice. ๐
I've only just started my Kotlin learning journey, but thought I'd make a few notes to try to consolidate what I've learnt so far. If you think any of my understanding below is wrong, please let me know!
JVM and Types
One big difference between Kotlin and Python is that Kotlin utilises the Java Virtual Machine (JVM) for executing Java bytecode. I don't know about you, but for whatever reason, I never seem to have the right version of Java on my dev machine and this always ends up being a pain. ๐ I've yet to go through how the day-to-day development cycle works when you're having to throw compilation in as a step, but I'll update on this once I have direct experience.
The other main thing to note is that Kotlin is a statically typed language, which means the type of a variable is known at compile-time, rather than run-time. Compared to Python, a dynamically typed language (with optional types), Kotlin is very strict about this, which can feel restrictive to start with, but saves a lot of headaches down the line. To me, Kotlin feels a little like using Typescript with "strict mode" on the frontend, so I do quite like the guardrails that Kotlin provides in this sense, versus Python.
Key concepts
Functions
- A function is defined using the
fun
keyword. The code for the function is surrounded by curly braces. - Types must be declared explicitly.
Classes
- Kotlin defines classes in a similar way to Typescript.
- Kotlin has the specific concept of "data classes" which doesn't exist in Python or Typescript. Marking a class with
data
allows the compiler to automatically derive explicit implementations of specific methods likeequals()
andtoString()
.
data class Person(val name: String, val age: Int)
- Kotlin also has a concept of "smart casts" which automatically type casts variables based on certain conditions being met (similar to later versions of Typescript).
fun exampleFunction(x: Any) =
when (x) {
is String -> println(x.length)
is Int -> println(x)
else -> println("not a string or integer")
}
- It can be combined with sealed classes (a special type of class that is used to represent a restricted hierarchy of classes) to further improve type safety by allowing the compiler to perform exhaustive checks of
when
expressions. - Extension functions can be used to add functionality to an existing class, outside the definition of the class.
- Overwriting (aka method overriding) is the process of creating a new implementation of a method that is already defined in a superclass or interface.
- i.e. the child class provides its own implementation of a method with the same name as in the parent class.
- Overloading is the process of defining a new method with the same name as an existing method, but with different parameters or types.
- This is useful if you have a method with the same name, but want to be able to pass it different parameter types. We can overload the method by allowing for different implementations based on the type of parameter passed to the method.
Properties
- Declare mutable properties with
var
and read-only properties withval
. - You can optionally define a custom getter and setter for your properties.
- Use the not-null assertion operator (
!!
) to assert that a value is not null. If it is null, an error will be thrown. - Kotlin has a concept of "delegate properties" that allow you to implement the delegation design pattern.
- This serves as a convenient way to implement a common set of behaviours for a group of properties. Benefits include:
- Reusability (allows sharing of common behaviour among multiple classes e.g. logging).
- Separation of concerns and adhering to the single responsibility principle (e.g. delegating secondary logic to a Delegate so that the class can focus on its main responsibility).
- Using the delegate to encapsulate certain implementation details of a property (e.g. just a read-only getter method).
- Using the delegate to provide extensibility to a property without changing its original behaviour.
- This serves as a convenient way to implement a common set of behaviours for a group of properties. Benefits include:
Builders
- Kotlin includes 5 scope functions that allow you to access an object without its name. You'll need to call these functions on an object with a lambda expression.
- The 5 scope functions:
let
,run
,with
,apply
andalso
.
- The 5 scope functions:
- Kotlin supports the builder design pattern, allowing the construction of a complex object, with different configurations and options. This is typically implemented using functions that take a lambda with a receiver as an argument.
- This is an example of how we might build an HTML page using our own domain-specific language (DSL):
// source: ChatGPT
fun html(block: Tag.() -> Unit): String {
val html = Tag("html")
html.block()
return html.render()
}
class Tag(val name: String) {
private val children = mutableListOf<Tag>()
fun render(): String {
return "<$name>${children.joinToString("")}</$name>"
}
fun tag(name: String, block: Tag.() -> Unit) {
val child = Tag(name)
children.add(child)
child.block()
}
fun text(value: String) {
children.add(Tag(value))
}
}
fun main() {
val page = html {
tag("head") {
tag("title") {
text("My Page")
}
}
tag("body") {
tag("h1") {
text("Welcome!")
}
tag("p") {
text("This is my page.")
}
}
}
println(page)
}
Generic functions
- You can declare a generic function like this (where both arguments and the return value are of the same type):
fun <T> genericFunction(arg1: T, arg2: T): T {
// code..
}
- If you need two generic types, just add to the generic definition before the function name:
fun <T, U> secondGenericFunction(arg1: T, arg2: U): String {
// code...
}