Let's learn, share & inspire each other. Sign up now đ¤đź
Why you should totally switch to Kotlin
Kotlin is developed by JetBrains, and the fact that these are the people behind a suite of IDEs, such as IntelliJ and ReSharper, really shines through in Kotlin. It’s pragmatic and concise and makes coding a satisfying and efficient experience.
Although Kotlin compiles to both JavaScript and soon machine code, I’ll focus on its prime environment, the JVM.
So here’s a couple of reasons why you should totally switch to Kotlin (in no particular order):
Kotlin is 100% interoperable with Java. You can literally continue work on your old Java projects using Kotlin. All your favorite Java frameworks are still available, and whatever framework you’ll write in Kotlin is easily adopted by your stubborn Java loving friend.
Kotlin isn’t some weird language born in academia. Its syntax is familiar to any programmer coming from the OOP domain and can be more or less understood from the get-go. There are of course some differences from Java such as the reworked constructors or the val
var
variable declarations. The snippet below presents most of the basics:
class Foo { val b: String = "b" // val means unmodifiable var i: Int = 0 // var means modifiable fun hello() { val str = "Hello" print("$str World") } fun sum(x: Int, y: Int): Int { return x + y } fun maxOf(a: Float, b: Float) = if (a > b) a else b }
It’s as if a smarter and more readable version of Java’s String.format()
was built into the language:
val x = 4 val y = 7 print("sum of $x and $y is ${x + y}") // sum of 4 and 7 is 11
Kotlin will infer your types wherever you feel it will improve readability:
val a = "abc" // type inferred to String val b = 4 // type inferred to Int val c: Double = 0.7 // type declared explicitly val d: List<String> = ArrayList() // type declared explicitly
The Kotlin compiler tracks your logic and auto-casts types if possible, which means no more instanceof
checks followed by explicit casts:
if (obj is String) { print(obj.toUpperCase()) // obj is now known to be a String }
You can stop calling equals()
explicitly because the ==
operator now checks for structural equality:
val john1 = Person("John") val john2 = Person("John")john1 == john2 // true (structural equality) john1 === john2 // false (referential equality)
No need to define several similar methods with varying arguments:
fun build(title: String, width: Int = 800, height: Int = 600) { Frame(title, width, height) }
Combined with default arguments, named arguments eliminates the need for builders:
build("PacMan", 400, 300) // equivalent build(title = "PacMan", width = 400, height = 300) // equivalent build(width = 400, height = 300, title = "PacMan") // equivalent
The switch case is replaced with the much more readable and flexible when expression:
when (x) { 1 -> print("x is 1") 2 -> print("x is 2") 3, 4 -> print("x is 3 or 4") in 5..10 -> print("x is 5, 6, 7, 8, 9, or 10") else -> print("x is out of range") }
It works both as an expression or a statement, and with or without an argument:
val res: Boolean = when { obj == null -> false obj is String -> true else -> throw IllegalStateException() }
Custom set & get behavior can be added to public fields, which means we can stop bloating our code with mindless getters & setters.
class Frame { var width: Int = 800 var height: Int = 600 val pixels: Int get() = width * height }
It’s a POJO complete with toString()
, equals()
, hashCode()
, and copy()
, and unlike in Java it won’t take up 100 lines of code:
data class Person(val name: String, var email: String, var age: Int) val john = Person("John", "john@gmail.com", 112)
A predefined set of operators can be overloaded to improve readability:
data class Vec(val x: Float, val y: Float) { operator fun plus(v: Vec) = Vec(x + v.x, y + v.y) } val v = Vec(2f, 3f) + Vec(4f, 1f)
Some objects can be destructured, which is for example useful for iterating maps:
for ((key, value) in map) { print("Key: $key") print("Value: $value") }
For readability’s sake:
for (i in 1..100) { ... } for (i in 0 until 100) { ... } for (i in 2..10 step 2) { ... } for (i in 10 downTo 1) { ... } if (x in 1..10) { ... }
Remember the first time you had to sort a List
in Java? You couldn’t find a sort()
function so you had to ask either your tutor or google to learn of Collections.sort()
. And later when you had to capitalize a String
, you ended up writing your own helper function because you didn’t know of StringUtils.capitalize()
.
If only there was a way to add new functions to old classes; that way your IDE could help you find the right function in code-completion. In Kotlin you can do exactly that:
fun String.replaceSpaces(): String { return this.replace(' ', '_') } val formatted = str.replaceSpaces()
The standard library extends the functionality of Java’s original types, which was especially needed for String
:
str.removeSuffix(".txt") str.capitalize() str.substringAfterLast("/") str.replaceAfter(":", "classified")
Java is what we should call an almost statically typed language. In it, a variable of type String
is not guaranteed to refer to a String
— it might refer to null
. Even though we are used to this, it negates the safety of static type checking, and as a result Java developers have to live in constant fear of NPEs.
Kotlin resolves this by distinguishing between non-null types and nullable types. Types are non-null by default, and can be made nullable by adding a ?
like so:
var a: String = "abc" a = null // compile error var b: String? = "xyz" b = null // no problem
Kotlin forces you to guard against NPEs whenever you access a nullable type:
val x = b.length // compile error: b might be null
And while this might seem cumbersome, it’s really a breeze thanks to a few of its features. We still have smart casts, which casts nullable types to non-null wherever possible:
if (b == null) return val x = b.length // no problem
We could also use a safe call ?.
, which evaluates to null instead of throwing a NPE:
val x = b?.length // type of x is nullable Int
Safe calls can be chained together to avoid those nested if-not-null checks we sometimes write in other languages, and if we want a default value other than null
we can use the elvis operator ?:
:
val name = ship?.captain?.name ?: "unknown"
If none of that works for you, and you absolutely need an NPE, you will have to ask for it explicitly:
val x = b?.length ?: throw NullPointerException() // same as below val x = b!!.length // same as above
Oh boy, is this a good lambda system — perfectly balanced between readability and terseness, thanks to some clever design choices. The syntax is first of all straight forward:
val sum = { x: Int, y: Int -> x + y } // type: (Int, Int) -> Int val res = sum(4,7) // res == 11
And here come the clever bits:
it
.These facts combined make the following three lines equivalent:
numbers.filter({ x -> x.isPrime() }) numbers.filter { x -> x.isPrime() } numbers.filter { it.isPrime() }
And this allows us to write concise functional code — just look at this beauty:
persons .filter { it.age >= 18 } .sortedBy { it.name } .map { it.email } .forEach { print(it) }
Kotlin’s lambda system combined with extension functions makes it ideal for DSL creation. Check out Anko for an example of a DSL that aims to enhance Android development:
verticalLayout { padding = dip(30) editText { hint = “Name” textSize = 24f } editText { hint = “Password” textSize = 24f } button(“Login”) { textSize = 26f } }
You have a number of options if you intend to get started with Kotlin, but I highly recommend using IntelliJ which comes bundled with Kotlin—its features demonstrate the advantage of having the same people design both language and IDE.
Just to give you a minor but clever example, this thing popped up when I first tried to copy-paste some Java code from Stack Overflow:
Cutipie 23 Sep, 2022
Without jumping into too many details, the fact that the difference between mutable and immutable variables is a single character (var vs val) means developers are prone to typos and readability problems. Overall this language seems to be a step backwards in terms of readability. Iâm all in favor of better/newer language, but only if they improve readability. Adding âcool stuffâ into the language just to seem hip doesnât hold any value.
Garima 23 Sep, 2022
I see these âmodernâ languages have less and less restrictions on code syntax and such. I do not think type inference is a good thing at all, even if you get help from your IDE of choice or whatever. Makes large projects less likely to be readable as time goes on. One good thing about Java is that itâs verbose, despite what theyâre saying. Coming from someone who knows both Java and Javascript I probably wont tell anyone who actually worked on huge projects why the former is better by miles when it comes to actually working with a codebase.