4

If Kotlin Runs on the JVM Just Like Java, Then How Does It Provide So Many Great...

 1 year ago
source link: https://betterprogramming.pub/kotlin-runs-on-the-jvm-just-like-java-how-does-it-provide-so-many-great-features-b27ed269015b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

If Kotlin Runs on the JVM Just Like Java, Then How Does It Provide So Many Great Features?

A look at Kotlin’s generated bytecode and how it runs on the JVM

1*vLhpU3CI1koYvToQ0oe2NQ.jpeg

Arm supporting a tree

Looking at the very fundamentals; what exactly do we mean when we refer to a JVM languageand how does the Kotlin language relate to that? Wasn’t only Java meant to run on the JVM? In this article, I want to talk about the basics of what the JVM is and how it all plays together. Especially, I will demonstrate how other languages than Java can be executed on the JVM. To do so, we will learn what the JVM bytecode is and how it makes JVM languages possible.

The Kotlin programming language provides many features that aren’t available in Java such as proper function types, extension functions, or the built-in singleton support via the object declaration. How can Kotlin provide more features than the language that is meant to run on the JVM in the first place? I’ve taken a deeper look at how Kotlin works under the hood and what JVM language actually means. We’ll be having a look at Kotlin’s bytecode generation. If you also thought about these things before, this article should bring some light into the darkness 🙂

The Java Virtual Machine

Most people will have heard the term Java Virtual Machine, or JVM before. But what is it actually? A simple definition is the following: The Java Virtual Machine is used by computers to run Java bytecode.
Or course, there’s a lot more to learn about this complex tool and it’s described in much more detail in Oracle’s specification. As you may already know, the JVM is an abstract virtual computer running on various operating systems. In fact, the JVM is what makes Java “platform independent” since it acts as an abstraction between the executed code and your operating system.
Just like any real computer, the JVM provides a defined set of instructions that can be used by a program and are translated to machine-specific instructions by the JVM itself later on in the process.

As described in the JVM specification, the Java Virtual Machine doesn’t know anything about the programming language Java. However, it defines the binary format class which is a file containing machine instructions (= bytecodes) to be executed (besides some more information). This is an extremely relevant point that has the following implications:

  1. the JVM isn’t only dedicated to running Java as a programming language.
  2. you are free to choose a technology for creating JVM programs as long as you provide proper aclass file format and comply with its very strict constraints.
  3. regardless of its source (read: programming language), any Java bytecode can interoperate with other Java bytecode on the JVM.

The creation of Class files

The process of creating class files from human-readable source code is what a compiler does. One example is Oracle’s Java Compiler javac, shipped with the JDK, which compiles.java files to .class files.
In Addition to Java, many other JVM languages have emerged in the last few years, all of which try to provide an alternative abstraction for the developers to create programs for the JVM. One of these languages is Kotlin.

Kotlin Bytecode Generation — What is Kotlin?

As stated in the official FAQs, “Kotlin produces Java compatible bytecode”. This means that the Kotlin compiler is capable of transforming all the wonderful Kotlin features into JVM-compatible instructions. Luckily, we can observe this complex process using tools provided by IntelliJ IDEA. Those tools let us inspect the bytecode that is generated by the Kotlin compiler. Now, bytecode isn’t particularly readable if you’re not used to it. As a simplification, IDEA also lets us transform the bytecode back into Java instructions which we will look at in the following section.

Let’s go through some interesting Kotlin features and learn what bytecode they translate to!

Top Level Functions

This simple top-level function defined in a .kt file can be investigated with IntelliJ:
Tools → Kotlin → Show Kotlin Bytecode will open a new window inside the IDE providing a live preview of the bytecode the compiler would create for the current .kt file. The result is the following bytecode.

public final class FileKt {// access flags 0x19
public final static foobar()V
L0
LINENUMBER 1 L0
RETURN
L1
MAXSTACK = 0
MAXLOCALS = 0@Lkotlin/Metadata;(...)
// compiled from: File.kt
}

As mentioned before, not many people will enjoy reading this kind of syntax, which is why we can also choose the option “Decompile”. After that, IDEA shows a Java representation of the bytecode from above.

As you can see, and possibly already know, a Kotlin top-level class is compiled into a final Java class with a static function. This structure reminds us of the concept that extension functions mean to replace: utility classes, i.e. a collection of static functions in a final class. In Kotlin, you can just declare those things at the top level of any file.

Let’s see a more difficult one:

Classes and Extension Functions

In the next example, we look at a class that contains a single property of type Int. In addition to that, the file also defines an extension function on that new class, defined on the top level.

What are the things we are interested in here when looking at the corresponding bytecode? First, we should have a look at what the class is compiled to because we used a primary constructor and the val keyword. Both things don’t exist in Java so it should be interesting to learn what the corresponding Java code looks like.

Well, there’s no real surprise here. The property becomes a final member and is assigned in the single constructor of that class. It’s just a lot simpler to declare in Kotlin, isn’t it?

Now, what will happen to the extension function?

The extension function itself is compiled into a static function with its receiver object, MyClass, becoming a parameter to that function.
Another thing we can observe in the example is the use of a class called Intrinsics. This one is part of the Kotlin standard library and works as a toolset to check parameters to be not null. Let’s quickly do an experiment and see what would happen if we change the original extension function’s parameter type to String? and then access length in a null-safe way. We skip the Kotlin code here and jump to the Java representation right away:

Checking value is not necessary anymore since we told the compiler that null is an acceptable thing to point to. The return expression has become more complex as we added a check for nullability and applied a default value. Sweet!

The next and final example is a bit more tricky. It’s the one with the greatest difference between Kotlin and Java code:

Kotlin Ranges

The following code defines a function that contains a simple loop to print a couple of integers to the console. There are a few interesting things in here, however: We use a range, or progression to be more specific, in order to generate a sequence of numbers we want to iterate over. Additionally, there are two infix functions involved as part of this declaration.

This one should be more interesting to look at in the bytecode. Let’s see how it turns out.

Okay, this is quite a lot of code here. What do we have here? Although the given Java code is pretty easy to read, it doesn’t look like a common way of looping over a couple of numbers. The way the compiler resolves the progression is a bit more complex than a simple for loop. This example shows that the resulting bytecode can be pretty extensive compared to the original concise code you wrote with Kotlin. Interesting learning, if you ask me!

Conclusion

Most of the time you don’t really care about what the Kotlin compiler produces behind the scenes. Nonetheless, observing what a compiler does can be really interesting and helpful. Doing so also provides an answer to my initial question of what Kotlin, and JVM languages in general, actually are. In the end, those are just abstractions to produce bytecode written in .class files just the way Java does it. The programming language itself may be arbitrary as long as the compiler produces viable bytecode.

On the flip side we also saw that, in certain situations, the compiled Java code is more verbose than one would expect. Could this impact performance? Yes indeed, it does have minor effects. Have a look at this presentation by Dmitry Jemerov if you’re interested in more "Kotlin → Java bytecode" examples that also take performance considerations into account. To be fair, this is an old resource and with every new version of the Kotlin compiler, things can change because, in the end, it just needs to translate Kotlin files into bytecode that the JVM understands. Every new JVM version is a potential chance for the Kotlin compiler to improve.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK