Bisher hast du Kotlin-Code geschrieben und einfach laufen lassen, ohne dir Gedanken darüber zu machen, was dazwischen passiert. Für den Rest dieses Kurses ändert sich das: Du schaust dem Kotlin-Compiler bei der Arbeit zu.
kotlinc übersetzt deine .kt-Dateien nicht in Maschinencode, sondern in JVM-Bytecode —
gespeichert in .class-Dateien, im exakt gleichen Format, das auch javac für Java-Code erzeugt.
Das ist der Grund, warum Kotlin und Java so nahtlos zusammenarbeiten: Am Ende landet beider Code
als Bytecode auf derselben virtuellen Maschine, die JVM unterscheidet nicht, aus welcher Sprache er
stammt.
Eine .class-Datei enthält unter anderem einen Konstanten-Pool (Strings, Zahlen, Referenzen
auf Methoden und Felder) und für jede Methode eine Liste kleiner, stapelbasierter Instruktionen —
"lade diese Konstante", "rufe jene Methode auf", "gib zurück". Du musst diese Instruktionen nicht
im Detail lesen können, um guten Kotlin-Code zu schreiben. Aber zu wissen, dass sie existieren,
erklärt einiges, das in den nächsten Lektionen wichtig wird: warum die JVM selbst kein Konzept von
Null-Safety kennt (Kotlin muss die entsprechenden Prüfungen als zusätzlichen Bytecode einfügen),
und warum manche Kotlin-Features spürbar mehr oder weniger Bytecode erzeugen als andere.
Eine Besonderheit direkt zu Beginn: Die JVM kennt nur Methoden, die zu einer Klasse gehören — keine
"freien" Funktionen. Kotlin erlaubt dir aber, Funktionen direkt auf Dateiebene zu deklarieren, ohne
umschließende Klasse. Der Compiler löst das, indem er alle Top-Level-Funktionen und -Properties
einer Datei in einer synthetischen Klasse bündelt, benannt nach der Datei plus dem Suffix Kt —
aus Utils.kt wird UtilsKt. Mit @file:JvmName("...") kannst du diesen Namen selbst festlegen,
zum Beispiel wenn Java-Code die generierte Klasse unter einem sprechenderen Namen ansprechen soll.
Der zweite Baustein, der die JVM von einem reinen Bytecode-Interpreter zu einer schnellen Laufzeitumgebung macht, ist der JIT-Compiler (Just-in-Time): Die JVM interpretiert Bytecode zunächst Zeile für Zeile — das startet sofort, ist aber langsam. Methoden, die häufig durchlaufen werden ("heißer" Code), übersetzt die JVM zur Laufzeit zusätzlich in nativen Maschinencode für die konkrete CPU, was danach deutlich schneller läuft. Dieser Kompromiss aus sofortigem Start und nachträglicher Optimierung nur dort, wo es sich lohnt, ist einer der Gründe, warum die JVM trotz Bytecode-Zwischenschritt konkurrenzfähige Performance erreicht.
Weiterlesen: Java-to-Kotlin-Interop
Lies den Code genau durch, bevor du ihn ausführst.
// Datei: Utils.kt
package de.firma.tools
fun quadrat(x: Int): Int = x * x
class Rechner {
fun verdopple(x: Int): Int = x * 2
}
Wie viele .class-Dateien erzeugt der Kotlin-Compiler aus dieser einen Utils.kt, und wie heißen sie?
In AppConfig.kt liegen nur Top-Level-Deklarationen. Java-Code müsste sie aktuell über AppConfigKt.ladeKonfiguration() aufrufen — die generierte Klasse heißt schließlich nach der Datei plus "Kt". Sorge mit einer Datei-Annotation dafür, dass die generierte Klasse stattdessen AppConfig heißt, ohne die Datei umzubenennen oder den Code in eine echte Klasse zu verschieben.
Bevor du einen Hint bekommst: Was hast du schon probiert, und woran hängt es genau?
1. Warum kann Java-Code überhaupt Kotlin-Top-Level-Funktionen aufrufen, obwohl die JVM keine "freien" Funktionen außerhalb von Klassen kennt?
2. Was macht der JIT-Compiler (Just-in-Time), wenn die JVM eine Methode als "heiß" erkennt (oft ausgeführt)?