Quickly learn basic fundamentals of Kotlin
Guide about basic fundamentals of Kotlin
Kotlin is a very popular programming language in the mobile application development world, but it is a bit of a new experience for backend developers.
Kotlin is designed to interoperate fully with Java and the JVM version of its standard library depends on the Java Class Library, but type inference allows its syntax to be more concise. Kotlin mainly targets the JVM but also compiles JavaScript or native code.
The purpose of this article is to give a quick start to Kotlin. Comparisons with other languages just to be easier to understand.
Variable Initialize
As it can be seen, In Kotlin variables can be defined by using val and var keywords.
Variables produced using the var keyword are known as mutable variables, and they can be assigned multiple times.
Variables produced using the val keyword are like the ones produced using final keyword in Java, and they are known as immutables. They can be initialized only a single time.
Also, type can be given to variables manually but it’s optional, Kotlin can understand the types automatically.
And maybe you noticed that in Kotlin you don’t need to use “;” at the end of each line :)
Data Class
In Kotlin, there is a data keyword, which overrides toString, hashCode, equals automatically.
Data classes have a copy method, by using that a new object from the existing one can be created while changing some fields.
Here, the primary constructor in Kotlin which is a little bit different from Java can be seen.
Constructors or methods can be called without any order by using named calls. I prefer to use named calls for every method and constructor call as it prevents developers from making mistakes.
Null Safety
Getting a null reference exception while accessing a null referenced variable is one of the most common dangers in programming languages. Java raises NullPointerException analogs to null reference exception.
But in Kotlin these exceptions can be prevented, and the code becomes safer. In Kotlin variables are non-null by default.
Still, if you want to make “abc” nullable you need to add “?” to the end of the field type.
In Kotlin we have safe calls, which is a great feature for accessing nullable properties.
Let’s say you want to learn some user’s school department name, and every field is nullable.
And lastly, Kotlin has Elvis operator to handle default values for null expression.
Coroutine ❤
It may be the biggest reason I love Kotlin. As you know writing asynchronous or non-blocking code is a little bit painful according to synchronous programming. Before speaking coroutines let me tell you some other techniques and solutions.
1- Threading: To achieve more scale you can create threads, which may avoid applications from blocking. However, there are some drawbacks:
- Creating a new operating system thread is a very expensive operation.
- Threads require context switches which are costly too.
- There are limited threads that we can use.
- Debugging and avoiding race conditions in thread techniques are a little bit painful.
2- Callbacks: When callbacks are used, a function can be passed as a parameter to another function, and the function which passed as the parameter is invoked when the process has been done. Drawbacks:
- Callback hell.
- Error handling is complicated.
3- Futures, promises, and others: Futures or promises assure that they will return a usable object named Promise, at a later time. Drawbacks:
- You need to change your code style.
- Error handling still can be complicated.
4- Reactive: Reactive hopes to achieve a state where the data is observable and in an infinite amount, in other words, observable streams. Drawbacks:
- Writing reactive code is very hard, you need to change your thinking and coding style to write reactive code.
5- Virtual Thread: Also known as Project Loom, is a new feature for the Java programming language that aims to improve the way threads are managed within the JVM. It introduces lightweight threads that consume fewer resources and are highly responsive, making it easier to write efficient and scalable applications.
I couldn’t try virtual threads but it is promising.
Kotlin’s approach to working with asynchronous code is using coroutines, which is the idea of suspendable computations. The function is suspended on a line and continues to run after a time or via a system event.
The Kotlin team defines coroutines as “lightweight threads”. But I think it is more than that, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one. You can control the threads that coroutines use.
According to my experience, Kotlin coroutines are similar to Go goroutines.
One of the benefits of coroutines is that when it comes to the developer, writing non-blocking code is essentially the same as writing blocking code.
There are mainly two functions in Kotlin to start the coroutines.
- launch{ } which is fire and forget does not return anything.
- async{ } which can return. You can await the response.
In Kotlin main function starts with the main coroutine.
In line 13, the current thread will not be blocked, this code will be suspended(delay function suspends the current coroutine and invokes it after delay is finished). The current thread can be used for another process while the coroutine is suspended. When this coroutine is invoked(after the delay) a thread(maybe the old one) will pick up this coroutine and continue with line 14.
You shouldn’t think only delay functions, imagine that you listen to a system call after sending an HTTP request. You can suspend the coroutine when the request is sent and the coroutine can be invoked(assign a thread again) when the response comes via a system call. So you can use the threads with max efficiency.
Delay function automatically suspends the coroutine and invokes it, but if you want to do suspend and invoke process manually you should use suspendCoroutine function. Imagine integrating a reactive HTTP client library by using Kotlin coroutine.
After the execution of this.httpRequest code, the current coroutine will be suspended, the code doesn’t wait for onResponse or onFailure events(because of the callback, they will be executed after the response is returned). When the response is successful, it invokes the coroutine again as in line 4 and the code continues from where it left off. So the thread was not blocked until a response came.
As you can see writing async non-blocking code in Kotlin is very easy, and you can easily integrate reactive libraries with Kotlin coroutines. Of course, there are a lot of advanced topics for coroutines but it’s not the subject of this article.
Asynchronous Flow
If you want to return multiple asynchronously computed values you can use Kotlin flows, which is similar to Go channels. If List<Any> is used to create these kinds of channels, it will be blocked because you need to fetch all the values at once, and then process it. But we want to do these stream operations asynchronously. Here is Flow<Any> to be used in these cases.
Flow is cold stream, which means that the code inside the flow block will not be executed until a collect function call.
After the collect function is called, the values can be sent to flow via emit(value) function, and then the code inside the collect method which we just print the collected value will work.
The delay is added to the collect method(line 12), to mock a long process. But as you can see the output didn’t change. The producer part(flow block) can produce(emit) more value but it is blocked because the consumer part(collect block) is blocked. Because emit function waits for all code given in collect to finish successfully, this feature is different from Go channels, in Go channels it will be blocked until the data is consumed by another goroutine.
If the producer code part is faster than the consumer part of the flow, you can use buffers. It creates 2 coroutines, one for the collect part and one for the flow part, so that you can consume and produce concurrently without breaking the order of values. Without the buffer, flow uses only 1 coroutine for these parts.
As you can see collected value order is not broken, but 2 and 3 values are prepared concurrently, and with buffer emit function doesn’t wait for all code given in collect to finish successfully, there are buffered channel between flow coroutine and collect coroutine so that collect method can keep and process values in order.
Collection Methods
Kotlin has a lot of useful collection methods, you can see the list from here. Let’s give an example, imagine that you want to get the first element from a list after filtering.
Both Java and Kotlin produce the same output, and there is no performance difference between them but Kotlin has a cleaner syntax. Kotlin has a lot of these kinds of useful methods, and I love using them.
If you wonder what is it and { }, you can checkout Higher-order functions from Kotlin docs to learn how they work.
Extension Functions
In Kotlin you can create extension functions easily. You can write extension functions to your existing class or built-in classes like String. Extension functions enable developers to add new features to an existing class.
Inline methods
When Kotlin encounters the inline keyword, the inline function’s content is copied to where the inline function was initially called at compile time. This concept reduces the memory overhead. Simply cut the inline method code and paste it where it is called.
Let me give a basic example:
And bytecode of the main function:
As you can see in the line 5, the main function calls the example function directly. Let’s look at the same example using the inline function:
And bytecode of the main function:
As you can see the code inside the example function is cut and pasted to the main function in compile time to avoid memory overhead.
However, be careful when using inline functions, large inline functions can increase the size of your files.
One pitfall of the inline functions is that they can’t be mocked.
Lazy delegated properties
Lazy initialization prevents the unnecessary initialization of objects. The properties will initialize after the first call, other calls won’t initialize just brings the initialized object. You can define lazy properties easily which execute the expression lazily.
Let me give an example:
Static methods and class
Singleton pattern is widely used, Kotlin makes it easy for us to use it with Object.
Also, like Java, you can create static functions and variables.
Returns and Jumps
In Kotlin you can break outer loops easily by using label expression.
Also in Kotlin, you can assign properties as a result of the condition expression easily.
Scope Functions
In Kotlin, there are special methods to execute a block of code within the context of an object. There are five of them: let
, run
, with
, apply
, and also
.
apply
andalso
return the object(same object “it”).let
,run
, andwith
return the lambda result.
let returns lambda result, also returns the context object(same object). But two of them have the same purpose: execute the block of code. run has the same purpose too, the only difference is that if you want to access the object, you should use “this” instead of “it” in run methods.
Also if you want to perform actions on a non-null object, use the safe call operator ?.
on them. In this way, you can compute the block of code if the object is not null.
Most of the time I use let method with safe call operator. I think it’s enough to write a clean null safe executable code block.
If you don’t want to use “it” variable you change it manually like “randomVariable”
Using apply, you won’t need to write the same object again and again if you want to update its properties.
with is very similar to apply method, the only difference is that with method takes the parameter of the object.
I wanted to show you the basic fundamentals of Kotlin. Of course, there are a lot of advanced topics as well. I will try to dive deep into them in my future articles.
All feedbacks are welcome, thank you.