The True Colors Of Kotlin

After Google announced their support of Kolin at the I/O 2017 conference, I thought there was no point in avoiding it any further. The least you can do is check out what capabilities it gives after all…


The True Colors Of Kotlin | ShakuroFrom my experience with Kotlin, I can tell it won’t take too long before you start writing the actual code in it. You just learn the basic syntax and take a course of short interactive exercises called Kotlin Koans.

 

If you have the experience working with Swift, mastering Kotlin will be a piece of cake, the basic concepts (for example, the nullable types processing) as well the syntax of these languages are very similar. Kotlin makes switching between the iOS and Android development organic, unlike it was with the Objective-C and Java case.

⚔️ Kotlin Vs Java

Father Time revealed some of the flawed concepts that have been laid down in Java. Kotlin developers did their best to address some of those flaws:

  • Safety Working With null.The Kotlin type system is built in a way to minimize the possibility of NullPointerException emergence. We’ll talk about it in a bit.
  • No Checked Exceptions. The efficiency and inefficiency of the checked exceptions is a subject of the never-ending arguments. However, imposing error processing might often lead to the appearance of the boilerplate code.
  • Function Types. Unlike Java, Kotlin has the ability to pass a function as a parameter to another function without resorting to SAM interfaces (Single Abstract Method) like Runnable, Callable, etc.
  • Array Invariance. The arrays in Java are covariant, while in Kotlin they are invariant. A simple Java example:
Number[] numbers = new Number[1];
Object[] objects = numbers;
objects[0] = "hello";

We can assign the Number[] value to the Object[] variable because of the Java arrays covariance. There will be no error during the compiling but it will appear during execution.

  • No Raw Types. Before the emergence of generics in Java, the so-called raw types have been widely used. Here’s a problematic example:
List rawList = new ArrayList();
rawList.add(new Object());
List<String> strings = rawList;
System.out.println(strings.get(0)); 

The new ArrayList() class creates a list with a raw type. Once again, an error pops up at execution, not compiling.

  • The Reworked Generics System. The Kotlin creators tried to solve some of the issues with generics found in Java. However, the generics system is still, unfortunately, working at the compiling stage like Java and not in the runtime.

💎 Kotlin Strengths

Kotlin’s main advantages are to be found where there is no Java equivalent. This is what I could spot:

1. The higher-order functions, lambda expressions, and inline functions.

As I said, the Kotlin functions gained the ability to accept other functions as arguments. These functions are called higher-order functions. Besides that, Kotlin has a convenient lambda expressions system implemented.

According to the Kotlin agreements:

  • You can move or omit the parentheses in a method if the lambda is in the last position or is the only argument in the method. For example, instead of customers.filter({ customer -> customer.city == search }), you can write customers.filter { customer -> customer.city == search }.
  • If you decided to not declare an argument of a one-argument lambda, it will be implicitly declared under the name of it. The previous example can be rewritten like this: customers.filter { it.city == search }.

Lambda expressions enable us to write functional code in a safe, brief, and expressive fashion:

// Return the most expensive product among all delivered products
orders
.filter { it.isDelivered }
.flatMap { it.products }
.maxBy { it.price }

2. Extensions

In Kotlin there are the so-called extensions that allow adding new functions and properties to the old classes without affecting their source code.

fun String.lastChar() = get(length - 1)
...
"abc".lastChar()

Also, in Kotlin you can call a lambda expression for a specified receiver object. Inside such expression, you can access fields and functions of the objects without any qualifiers. This allows us to create the so-called Type-safe Groovy-style builders.

html {
body {
div {
a("http://kotlinlang.org") {
target = ATarget.blank
+"Main site"
}
}
}
}

3. Smart Type Casting

The Kotlin compiler tracks the code logic and executes smart casting.

if (obj is String) {
print(obj.length) // obj is automatically cast to String
}

4. Null Safety

As I mentioned above, Kotlin has null safety implemented through the introduction of the nullable and non-null type separation. By default, the types disallow null but they can be modified to do so if we add ?. Kotlin executes forced checks while compiling and prevents the usage of the nullable variables without the null check. So there is no danger of NullPointerException. Also, you can’t assign null to a variable declared as a non-nullable one.

A non-null type:    

 var a: String = "abc"
a = null // compilation error
val l = a.length // it's guaranteed not to cause an NPE

Accessing any field of the a function is safe because it has the non-null type.

A nullable type:

 var a: String = "abc"
a = null // compilation error
val l = a.length // it's guaranteed not to cause an NPE

The Kotlin compiler will not allow the direct access to the b function fields, as it can be null. We have a few options of how to execute this access:

  • You can check the value before accessing. With this, after the check  (b != null), the b type will automatically be specified as the non-null type of String:
val l = if (b != null) b.length else -1
  • You can use the ? operator for a safe access:
val l = b?.length

The result of this expression has the Int? type and is equal to b.length if b is not null or null in the opposite case.

  • If we are looking to receive NullPointerException, we can use the !! operator. The b!! expression will reinstate the String type to non-null if b != null, otherwise the   NullPointerException will appear.
val l = b!!.length

Note: In actual fact, the appearance of NullPointerException is only possible in the following cases:

  • Error is called intentionally: throw NullPointerException() or the !! operator is used.
  • The NPE appears on the Java code side.
  • The error appears as the result of the initialization data non-compliance. For example, the underinitialized this is used somewhere:
 class Foo {
val c: String

init {
bar()
c = ""
}

fun bar() = println(c.length) // NPE here
}

5. Function Default Values

A common practice in Java is overloading methods and constructors for default-value arguments implementation. Kotlin has the default arguments for that.

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}

As a matter of fact, the default arguments allow us to get rid of almost all the cases of methods and constructors overloading which helps avoid this sort of boilerplate code.

6. String Templates

In Kotlin, strings may contain string templates, in other words the code which result will be concatenated into the string.

val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"

7. The Extended Properties & Fields System

Kotlin strings can have fields and properties with extended functionality, including customizable getters and setters:

var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}

One of the super common tasks is implementing a property that is concatenated once at the first access, the so-called ‘lazy property’. In Kotlin, such a field can be created easily:

val lazyValue: String by lazy {
println("computed!")
"Hello"
}

Implemented with the help of the delegated properties:

class Example {
var p: String by Delegate()
}

The expression after by is the delegate that processes (get() and set()). The delegate does not necessarily have to implement some UI, it’s enough for it to have the get() and set() methods with a specific signature.

The standard Kotlin library showcases several useful kinds of delegates: lazy properties, observable properties, and the properties stored in a map.

8. Automated Type Inference

Kotlin has the ability to perform type inference on its own.

val result = sequenceOf(10).map { it.toString() }.flatMap { it.toCharArray().asSequence() }.toList()

It also knows that the expression result has the type of List<Char>.

9. Language-level Singletons

A singleton is a pattern often used in programming. It can be easily declared in Kotlin by means of the object keyword:

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}

10. Coroutines (Co-programs)

Using coroutines enables us to sequentially write asynchronous code so the logic in the code is synchronous while working as asynchronous. With that said, a thread doesn’t get blocked but suspended.

If you had a chance to implement a sequence of asynchronous operations, you might be familiar with a term of Callback Hell. It is the phenomena of callbacks being nested within other callbacks several levels deep. The longer the chain of asynchronous operations, the more nested callbacks there are. This makes code illegible and susceptible to errors.  Enter coroutines that help avoid this problem.

fun sendEmailAsync() = async(CommonPool) {
val emailTask = async(CommonPool) {
Thread.sleep(500)
"email@example.com"
}

val messageTask = async(CommonPool) {
Thread.sleep(200)
"The message"
}

sendEmail(emailTask.await(), messageTask.await())
}

In a nutshell, coroutines are some sort of “lightweight threads” that require minimum effort to create and maintain. At the same time, creating native threads is an expensive process, For example:

fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) { // create a lot of coroutines and list their jobs
launch(CommonPool) {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() } // wait for all jobs to complete
}

This code launches 100K routines and after 1 second each one of them prints a dot. The code is easy to launch and it executes fast. Reenacting this with native threads is quite problematic.

Coroutines is a powerful functionality enabling us to use different approaches to writing competitive code. You can use the off-the-shelf solutions from the kotlinx.coroutine library or implement your own.

Here’s a quick example to demonstrate the broad spectrum of coroutines with a relative ease of usage. Say we have the unreliable (network error susceptible) functions of f1 and f2. We need to get the sum of these functions by calling them multiple times with a predefined timeout but not exceeding the given number of attempts:

suspend fun f1(i: Int): Int {
    delay(if (i != 2) 2000L else 200L)
    return 1
}

suspend fun f2(i: Int): Int {
    delay(if (i != 2) 2000L else 200L)
    return 2
}

inline fun <T> retry(n: Int, block: (Int) -> T): T {
    var ex: Throwable? = null
    repeat(n) { i ->
        try { return block(i) }
        catch (e: Throwable) {
            println("Failed with $e")
            ex = e
        }
    }
    throw ex!! /* rethrow last failure */
}

fun <T> asyncRetry(n: Int, time: Long, block: suspend (Int) -> T) =
    async(CommonPool) {
        retry(n) { i ->
            withTimeout(time) {
                block(i)
            }
        }
    }

suspend fun coroutineExample() {
    val retryCount = 3
    val timeout = 500
    val v1 = asyncRetry(retryCount, timeout) { i -> f1(i) }
    val v2 = asyncRetry(retryCount, timeout) { i -> f2(i) }
    println(r1.await() + r2.await())
}

Completing a similar task in Java would have required writing way more code. Perhaps RxJava could have helped a bit, but it can barely provide the same simplicity and legibility.

💡Kotlin Issues Nuances

For the sake of integrity, I feel like throwing in a couple issues I encountered in my Kotlin project experiment. I should rather say not issues but the nuances of the language usage.

Java Code Autoconversion

Android Studio, as well as IntelliJ IDEA, allow automatic conversion of Java code into Kotlin, including the entire files and snippets at copy/paste. The conversion works fairly good, however the converted code still requires a check. In my case, some string constants were lost during conversion.

Type Casting

Kotlin is extremely strict when it comes to casting. The following code won’t compile:

var longValue = 1L
...
if (longValue == 0) println("zero") // compile-time error

We won’t be able to compare the Long variable with the Int value of 0. We have to compare it to the Long value of 0L. Nevertheless, the compiler allows this comparison (at least version 1.1 does):

if (longValue > 0) println("positive")

Inconsistency in its finest 🤷🏻‍♂️

The var Variables Usage Rigor

The compiler is rigor when accessing the mutable variables, expecting the variable to be used in a multithreaded context. An exaggerated example:

var value : String? = null

fun foo() : Int {
if (value != null) {
println("Length is: ${value.length}") // ошибка компиляции 
}
}

The compiler throws an error: Smart cast to ‘String’ is impossible, because ‘value’ is a mutable property that could have been changed by this time.

In this case, it’s better to write:

 value?.let {
println("Length is: ${it.length}")
}

The Modified for Loop

The C and Java-familiar for construction for the looped actions was modified in Kotlin:

for (step in 0..10) println(step)

This means the for construction works only with iterators. In this case, 0..10 is a range that has an iterator.

🙊 Wrap-Up

In all my love to Java I should admit it is getting outdated. Java was created in the last century and in spite of its active development, its syntax and concepts are restricted by the backward compatibility with the previous versions starting from 1995.

If you’d like to try out something new without straying too far from the Java path, Kotlin is the best option out there.