Meet Kotlin, the best programming language ever
I first met Kotlin on June 17 this year, and I haven't used another language since then. (I mean, almost!)
By Juraj Mičko (19), software developer
It all started a few weeks before at HackPrague, a hackathon in Prague I discovered thanks to StarLift. I didn’t know much about the event, but my friends and I put together a nice PDF cover letter, and, luckily, we got in.
It was going to be our very first hackathon.
But how does it relate to Kotlin? Well, there was a sponsored prize “Let’s Program in Kotlin!” at the hackathon, encouraging participants to utilize the new-born language.
My first impression was what a terrible language it must be that they need to reward people for using it? But from what I heard around, it was supposed to innovative while interoperable with the good old Java.
Of course, we didn't have the time to learn the new language during the hackathon, but we won the 2nd place for “The most innovative UX for mobile event discovery,” and, motivated by the victory, we attended another hackathon in Prague just a week later. During the second day of coding, we realized that our concept was not the winning one, and we wanted to make our project more innovative. At that time, my Github included a few classes written in Kotlin, and I got the first insight into what Kotlin may be — and that, perhaps, it may not be so bad.
(Side not: it did not help us to win.)
So I started learning more about Kotlin the following weekend, and it turned out to be a life-changing experience. The more I learnt, the more I was fascinated by it. And I hope I can motivate you to try it, too, so let's take a look at some of its great features.
Not a boring language
Java programmers benefit a lot from having everything declared with a type that is known during compile time. The drawback is writing boring parts of code, i.e. things a monkey could write being given only the line of code without even knowing the context.
String s = "Hello World!";
BufferedStreamReader bsr = new BufferedStreamReader(stream);
In Kotlin, the same can be achieved without the boring code. For instance, Kotlin developers consider type declarations, new
keyword and semicolons as boring, hence the equivalent in Kotlin is:
val s = "Hello World!"
val bsr = BufferedStreamReader(stream)
Of course, you can override the inferred type, or provide it in case it can’t be inferred.
val pet: Animal = Dog()
val pet2 = Dog() as Animal // downcasting or upcasting
Whenever you specify val
, the variable is final. For mutable variables use var
.
Null safety
In Kotlin, every type either can hold null values (String?
) or can’t (String
). Assign a possibly null value to a variable not accepting null values and the compiler will complain. Neither invoking a method on a nullable type is permitted. So how do we sort it out? All the following examples produce the same result.
// This is basically what one would write in Java style
fun whoIs(user: User?): String? {
if (user != null) {
return user.name
} else {
return null
}
}
// But the `if` block is also an expression in Kotlin
fun whoIs(user: User?): String? {
return if (user != null) user.name else null
}
// The ?. operator will invoke the method only if the object is not null,
// otherwise the whole expression evaluates to null
fun whoIs(user: User?): String? {
return user?.name
}
// Finally, using a short syntax of methods (the inferred return type is still String?)
fun whoIs(user: User?) = user?.name
// We can supply default values with the Elvis operator ?: that will evaluate
// to the right side if and only if the expression on the left is null
// Note that in this particular case, the compiler infers the return type to by non-null `String`
fun whoIs(user: User?) = user?.name ?: "no one"
Extension functions
Some languages support extending existing classes by defining their members (fields or methods) without changing the class definitions at all. Libraries tend to define many extension functions on common objects and the methods eventually collide since a class can’t have two methods with the same signature. However, Kotlin has everything covered.
First, it’s important to understand how Kotlin’s extension functions work and how they can be compiled to Java. Consider the following definition:
fun Boolean.thenRun(block: () -> Unit): Boolean {
if (this) {
block.invoke()
}
return this
}
The method has one argument, a lambda without parameters that returns Unit
(equivalent to void
in Java). If the receiver of the function (i.e. the boolean value) is true, the lambda is invoked. After that, the original boolean value is returned.
Let’s save the code to MyFile.kt
, compile it and decompile the resulting MyFileKt.class
back to Java. The output is roughly the following (I removed unimportant parts):
public final class MyFileKt {
public static final boolean thenRun(boolean $receiver, @NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
if($receiver) {
block.invoke();
}
return $receiver;
}
}
As you see, Kotlin tackles the problem by defining a public static method instead of taking the boolean value as its first argument. Amazing! We can have as many libraries as we want, and member collision will not occur since you always need to import the right one.
Few notes here:
- Kotlin’s type
Boolean
changed toprimitive boolean
as the compiler knows that it won’t hold null values. - We defined the method directly in the file, but not as a class member. Kotlin supports also extension functions as class members.
- As you may have noticed, things in Kotlin are usually
public
andfinal
unless other is specified.
Now consider what use this function may have. Look at the following logic written in Java:
public class MyClass {
...
public boolean doSomething() {
boolean isSuccess = doTheTask(); // returns true on success
if (isSuccess) {
System.out.println("OK");
}
return isSuccess;
}
}
Pretty simple. We run a task, store its result. If positive, we run another block of code (printing to console in this case). Then we return the original value. Now look at what Kotlin offers:
fun doSomething(): Boolean {
return doTheTask().thenRun {
println("OK")
}
}
// or even better
fun doSomething() = doTheTask().thenRun { println("OK") }
Not only is the code much shorter, we completely got rid of a variable!
Note that if the last argument of a function is a lambda, you can put it out of parentheses, and you can omit them if the parentheses are empty. Kotlin has many extension functions built in that you don’t have to write.
No boilerplate code
Code in Kotlin:
data class Human(var name: String?, private val birth: Date) : Mammal(birth)
Code decompiled to Java follows. Again, I removed the uninteresting parts.
public final class Human extends Mammal {
@Nullable
private String name;
private final Date birth;
public Human(@Nullable String name, @NotNull Date birth) {
Intrinsics.checkParameterIsNotNull(birth, "birth");
super(birth);
this.name = name;
this.birth = birth;
}
@Nullable
public final String getName() {
return this.name;
}
public final void setName(@Nullable String var1) {
this.name = var1;
}
@Nullable
public final String component1() {
return this.name;
}
private final Date component2() {
return this.birth;
}
@NotNull
public final Human copy(@Nullable String name, @NotNull Date birth) {
Intrinsics.checkParameterIsNotNull(birth, "birth");
return new Human(name, birth);
}
public String toString() {
return "Human(name=" + this.name + ", birth=" + this.birth + ")";
}
public int hashCode() {
return (this.name != null?this.name.hashCode():0) * 31 + (this.birth != null?this.birth.hashCode():0);
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof Human) {
Human var2 = (Human)var1;
if(Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.birth, var2.birth)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
I guess you get my point that Kotlin is concise. Much more concise.
Properties and delegated properties
Whenever there are getSomething()
and setSomething(value)
methods, Kotlin considers it to be a property and you can access it just by writing obj.something
and obj.something = value
. When you create a class with a property such as the following example, it is compiled to a class with private property with appropriate getters and setters. This is exactly what happened in the previous example. Of course, you can modify everything.
open class HeyWorld(val abc: String) { // not final class
// `abc` is a constructor argument and an immutable property with a setter
var foo = "bar" // public getter and setter
private val now = Date() // private getter and no setter (because of `val`)
protected var amount: Int? = null // protected property initialized to null
// here we define custom getter; field is a special keyword in this scope
get() = field ?: 0 // returns the value if not null, otherwise returns 0
private set // setter is private
open var abracadabra: Boolean? = true // not final field (unlike the rest)
set(value) { // value is of type Boolean?
field = if (value != null)
// but we negate it as it has been smart-casted to Boolean
!value
else null
// the same could be achieved by writing
// field = value?.not()
}
}
A delegated property means that you delegate the responsibility of accepting and providing value to someone (or something) else.
class Example {
var p: String by Delegate()
}
The Delegate
object must have specific getValue
and setValue
methods defined, but let’s focus on the usability again.
// lazy evaluation - the value is evaluated only when it's first accessed
val lazyValue by lazy {
print("The property is being evaluated for the first time!")
"Hello World!"
}
// managing more properties by one object
class Person(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
Conclusion
Kotlin has many magical features. What struck me at the beginning was that everything is compilable to Java and therefore must abide with its restrictions. And indeed, every bit of magic here has an explanation.
There are many things left unexplained in the article and you may be wondering if the language really can work as expected, and if there are any drawbacks. Well, you can either read the docs yourself or trust me when I say it’s amazing. Good luck and happy coding!
Are you a young developer from Czechia or Slovakia? Would you like to launch your career in a startup in the US or the EU? Contact us right now via our website, Facebook or Twitter!