kotlin is java 2

This commit is contained in:
Wouter Groeneveld 2021-08-01 10:36:30 +02:00
parent 2ee2e72f35
commit 86d2da8ecb
1 changed files with 184 additions and 0 deletions

View File

@ -0,0 +1,184 @@
---
title: Kotlin Is Java 2.0, But It's Still Java
date: 2021-08-01T09:19:00+02:00
categories:
- programming
tags:
- java
---
In April, I [took a few weeks to explore Go](/post/2021/04/exploring-go/), claiming that "Go fixed Java/C#". This month, I'm preparing an Android app development university course where another new programming language crossed my path: Kotlin, birthed by the fine JetBrains folks who are also responsible for many great development environments. Instead of skimming over the essentials---after all, the short course focuses on Android, not on the language API---, I wanted to get a more deeper understanding of the hows and the whys of Kotlin. With the guidance of a friend and [Kotlin in Action](https://www.goodreads.com/book/show/29242249-kotlin-in-action), I can now say that I (somewhat) speak yet another programming dialect. Another win for polyglots!
## Kotlin Is Java 2.0
Kotlin is a language that is not trying to reinvent the wheel, and that's a good thing. Instead, its creators focused on the most prevalent pain points of classic (enterprise) Java development, and tried to alleviate these. Indeed, we're talking about _duplication_ and _plumbing_. Instead of using external libraries such as Google Guava to simplify collection manipulation, now several neat functions are built-in. Well... That's a bit of a stretch: you still need the Kotlin runtime jar where those functions reside, so in that sense it's still "merely" a library. For example, reading a resource is reduced from
```java
String sql = new String(Files.readAllBytes(Paths.get(getClass().getResource("dbcreate.sql").getPath())));
```
to:
```kt
val sql = javaClass.getResource("dbcreate.sql").readText()
```
Digging through [the readText source](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/read-text.html) reveals it's a wrapper around a fancy new concept called _extension methods_, that allow you to extend existing libraries with new functions. Want to add something to the `String` class? In Java, you can't. In Kotlin, you can:
```kt
fun String.last(): String {
return this[this.length - 1].toString()
}
fun main(args: Array<String>) {
println("hi".last())
}
```
Note that the way functions are written have some similarities with Go: the argument name comes _first_, its type second. This takes a while to get used to but does improve readability by a large margin: another of Kotlin's strengths. Oh, and semicolons have disappeared too. Finally.
Go ahead and copy-paste the above code in the [online Kotlin Playground](https://play.kotlinlang.org/). This principle is (ab)used a lot to alleviate an absurd amount of duplication Java developers have to put up with, thanks to clever functions such as `with`, `use`, and `apply`. What do these exactly do? They eliminate the need to repeat yourself while trying to configure something like a transaction or a connection object. In Java, you'd do this:
```java
TransactionManager manager = new TransactionManager("someBaseConfigfile.xml");
manager.setAutoCommit(false);
manager.setBulkStatements(100);
manager.setVerbosity(Verbosity.HIGH);
```
While in Kotlin, that's reduced to:
```kt
val manager = TransactionManager("someBaseConfigfile.xml").apply {
autoCommit = false
bulkStatements = 100
verbosity = Verbosity.HIGH
}
```
No need to repeat yourself with consecutive `manager.` calls. What actually happens here is that the `{ }` block is a closure that's the single argument of the `apply()` function, where the `this` context gets translated to the callable object. So, in essence, these tricks can be part of a Java utility library as well, except that here, they're built-in. Furthermore, in Kotlin, getters/setters are translated into fields and used this way. `autoCommit = false` still results in a call to `setAutoCommit()`, but it requires less hassle.
Another very powerful inclusion is the `when` statement: Java's `switch` on steroids. It can accept an argument---but without it, its body will check for boolean values---and automatically compares between object instances. For example, here's an `isItWorhtIt()` function that compares the card from the argument with a few known ones to return a price:
```kt
data class Magic(val name: String, val rarity: String = "common")
fun isItWorthIt(card: Magic): Double {
return when(card) {
Magic("The Scarab God", "rare") -> 15.0
Magic("Binding Mummy") -> 0.1
else -> 0.0
}
}
fun main(args: Array<String>) {
val theScarabGod = Magic("The Scarab God", "rare")
print(isItWorthIt(theScarabGod))
}
```
This would be impossible to do in Java, as it's not a constant. Behind the scenes, the above code gets translated into a chain of if-else's using `.equals()`.
## Kotlin Is Still Java
As I was trying to understand what happens under the hood with several of Kotlin's mechanics, I decided to decompile the generated `.class` bytecode files. The Kotlin language is primarily written for JVM developers, but it can also output native code with the help of the LLVM compiler, or, as any respectable modern language can, output JavaScript. When we let the [Procyon decompiler](https://github.com/mstrobel/procyon) do its thing on the above simple string extension file, the Java output is as follows:
```java
public final class MainKt
{
@NotNull
public static final String last(@NotNull final String $this$last) {
Intrinsics.checkNotNullParameter((Object)$this$last, "<this>");
return String.valueOf($this$last.charAt($this$last.length() - 1));
}
public static final void main(@NotNull final String[] args) {
Intrinsics.checkNotNullParameter((Object)args, "args");
System.out.println((Object)last("hi"));
}
}
```
There, all the magic gone: it's simply a stupid static method. Now how about data classes, another great example of squandered code duplication? As another example, consider a `Person` with a name and age. The Kotlin way of writing this is:
```kt
data class Person(val name: String, val age: Int)
```
That's it---a one-liner to define a class! This comes with free `toString()`, `equals()` and `hashCode()` implementations, and all `val` properties defined in what is called a _primary constructor_ are translated into immutable fields. Here's the procyon output of the above class:
```java
public final class Person
{
@NotNull
private final String name;
private final int age;
public Person(@NotNull final String name, final int age) {
Intrinsics.checkNotNullParameter((Object)name, "name");
this.name = name;
this.age = age;
}
@NotNull
public final String getName() {
return this.name;
}
public final int getAge() {
return this.age;
}
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final Person copy(@NotNull final String name, final int age) {
Intrinsics.checkNotNullParameter((Object)name, "name");
return new Person(name, age);
}
@NotNull
@Override
public String toString() {
return "Person(name=" + this.name + ", age=" + this.age + ')';
}
@Override
public int hashCode() {
int result = this.name.hashCode();
result = result * 31 + Integer.hashCode(this.age);
return result;
}
@Override
public boolean equals(@Nullable final Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Person)) {
return false;
}
final Person person = (Person)other;
return Intrinsics.areEqual((Object)this.name, (Object)person.name) && this.age == person.age;
}
}
```
Sixty-one lines in Java, reduced to a single one in Kotlin: wow. The language is littered with cool stuff such as this (operators, default arguments, simpler functions, powerful pattern matching, destructing an object into pairs, ...).
However. This easy interoperability with existing Java code comes at a price: in essence, you're still limited to what the Java Virtual Machine can and cannot do. Everything in Kotlin is what you'd call _syntactic sugar_ and gets translated into JDK 1.6/1.8/10/13/whatever compatible bytecode. This means that when the going gets tough, it is still very much required to have a firm grasp of the Java fundamentals.
My friend calls Kotlin **Java 2.0**. But it's still Java. Sure, you can concentrate on the essence and leave the duplication and plumbing up to Kotlin. Sure, it produces much more readable code. Sure, it might reduce classic mistakes as immutability and nullable types are built-in. But it's still Java! Compared to Go, there are still 4 ways to do one thing, there's still the exceptionally complex (but native) JVM threading, and sometimes, you have to write very awkward things like `::class.java.resource` to get hold of the static class instance.
Many languages live on top of the JVM: Groovy, Scala, Kotlin. Why choose Kotlin? Because unlike Scale, it's a general-purpose multi-paradigm language: it promotes functional-style coding, but it doesn't enforce it. Because unlike Groovy, it's statically typed and its compiler does a very good job at smart casting and type inference. But most of all: because it's gaining traction[^tr]: it is the Google-recommended way to develop Android apps. You don't need to install anything if you have IntelliJ ready to go: a very smart move! My interoperability experiments with existing Java codebases was overall very positive. So, if you're limited to the JVM, by all means, increase your productivity and happiness with Kotlin.
[^tr]: There's still a long way to go: [the TIOBE index](https://www.tiobe.com/tiobe-index/) tells me Kotlin is the 38th most popular language, while Scala sits two seats above it, and Groovy is 15th. Guess which one is second. Java. Go is number 13, by the way!
Just remember that there is life besides the JVM.