Eine neue(re) Programmiersprache für die JVM

Nach der “Entdeckung” von Webstorm stöberte ich noch ein wenig auf der Jetbrains-Site umher und stiess auf eine weitere Perle: Kotlin. Wieso habe ich davon bisher nie gehört? Ich hatte ja seit etwa zwei Jahren eine Faszination für Scala entwickelt. Der streng funktionale Stil und das elegante Handling von Collections machen ihren Reiz aus. Allerdings: Wirklich gut Scala zu schreiben ist schwierig. Wegen diverser implicits und dem hemmungslosen Überladen von Operatoren schon in der Standard-Library ist es ohne Debugger manchmal schwer vorherzusagen, was ein bestimmter Ausdruck genau bewirken wird. Und: Scala ermöglich bzw. fördert es sogar, Code zu schreiben, den man nach kurzer Zeit selber nicht mehr versteht. Meine Liebe zu Scala blieb daher weitgehend unerwidert, und jedesmal, wenn ich wieder einen Anlauf amchte, verbrachte ich mehr Zeit mit dem Austüfteln geeigneter Sprachkonstrukte, als mit dem eigentlichen Algorithmus.

Kotlin scheint hier in die Bresche zu springen: Übernimmt das Beste, das Eleganteste von Scala, ohne sich aber so weit von Java zu entfernen, dass es richtig schwierig würde.

Ein paar Beispiele:

Variablendeklaration und -Initialisierung

Java:

    Map<String, String> map=new HashMap<String, String>();
    map.put("myOpinion","boring");
    map.put("yourOpinion","?");

Kotlin:

    var map=mapOf("myOpinion" to "simple","yourOpinion" to "?")
  1. Der Kotlin-Compiler braucht Typinformationen nur auf einer Seite der Deklaration. Er findet selber heraus, dass auf der anderen Seite des Gleichheitszeichens wahrscheinlich derselbe Datentyp stehen muss. Der Java-Compiler schnallt das auch in der achten Version noch nicht. Der Java-Compiler weiss auch nicht, dass “myOpinion” ein String ist, man muss es ihm sagen. Kotlin kommt selber darauf.

  2. Der Kotlin-Compiler kann Collections bei der Deklaration auch gleich initialisieren.

  3. Der Kotlin-Compiler findet selber heraus, wo ein Statement zu Ende ist. Er braucht kein Semikolon dafür. (Man darf es aber setzen)

Collections

Kotlin:

     val list=listOf(1,2,3,4,5,6,7,8,9,10)
     val even=list.filter{ x-> x%2==0 }
     val dynlist=(1..100).map {i -> (3*i)-1}

Java: Die analogen Java-Funktionen seien als Übung dem Leser überlassen.

Data classes

Oft definiert man Klassen, die nur den Zweck haben, Daten zu halten. Kotlin macht das einfach

    data class User(val name: String, val age: Int)
    val hans=User("Hans Meier", 42)

Mehrzeilige Strings mit Interpolation

     val langerString= """
	    lorem ipsem solatur predicisse
	    narratum efluat inlastum ibidum
	    useremos Hans annos ${hans.age} habet.
	    ispum equat bibicisse ulatum 
	    maximum dolendum esse!
	    Gaudeamus! Igitur!! 
	    """

if als Funktion

    val fritz=User("Fritz Müller",58)
    val oldest= if(fritz.age>hans.age) fritz else hans

Match nicht nur mit Numbers

    fun wasIstDas(XY: Any){
        when(XY){
          1 -> return "eins"
          is Int -> return "Eine Integer Zahl"
          is User -> return "hey, das ist der ${XY.age} jährige ${XY.name}!"
          "hallo" -> return "ein Gruss"
          !is Int -> return "Hm, jedenfalls kein Integer"
          else -> return " Keine Ahnung was das ist"
      }
    }

Konstante “ich weiss noch nicht”

     public class MongoLog: Verticle() {
          val cfg: JsonObject by Delegates.lazy { container.config() }
          
          override fun start() : Unit {
              vertx.eventBus().registerHandler(cfg.getString("address"), LogHandler())
               ...
          }
              ...
     }

cfg kann erst nach start() gesetzt werden, da vorher container null ist. Natürlich kann man sich behelfen, indem man sie als Variable (var) statt als Konstante (val) deklariert und dann in der Funktion start() setzt. Aber das ist unelegant, weil es semantisch eben eine Konstante ist. Kotlin erlaubt es uns, sie lazy zu initialisieren: Es wird ihr erst dann ein Wert zugewiesen, wenn sie das erste Mal ausgelesen wird (in registerHandler(cfg.getString())). Von da an bleibt sie konstant.

Null-Sicherheit

    var name = package?.getMessage()?.body?.getString("Name")

Bedeutung: Wenn irgendein Glied der Kette null ist, wird name == null sein (aber es wird keine Exception geworfen). Und der Compiler “weiss” jetzt, dass name==null sein könnte. Wenn man danach z.B. versucht:

    var len=name.length()

zu schreiben, wird es einen Compiler-Fehler geben (name könnte null sein). Man muss aktiv angeben, dass man sich dessen bewusst ist:

     var len=name?.length()

Womit len dann die Eigenschaft “könnte null sein” erbt. Oder man gibt an, dass man gewillt ist, eine NPE in Kauf zu nehmen:

    val len=name!!.length()

Dann wird len vom Null-Makel frei sein, aber man sollte die eventuell auftretende NPE irgendwo fangen.

Natürlich kann man es immer noch wie “früher” machen, und auf null testen:

    if(name==null){
        len=-1
    }else{
        len=name.length()
    }

Der Compiler ist schlau genug um zu merken, dass name im else-Zweig nicht mehr null sein kann und besteht darum hier nicht mehr auf einer “könnte null sein”-Deklaration.

Zu guter Letzt gibt es noch den “Elvis-Operator”, der eine Verkürzung der if(x==null) Syntax ist:

    len = name?.length() ?: -1

Bedeutung: Wenn das erste Argument null ist, wird das zweite ausgewertet.

Fazit: Mit diesen Konstrukten wird eine ganze Klasse von Fehlern, der beste Feind des Java-Programmierers, die NullPointerException sehr viel seltener werden.

Class cast Sicherheit

Der zweitbeste Feind des Java Programmierers nach der NPE ist wohl die ClassCastException. Auch hier hat Kotlin eine relativ einfache Lösung:

    val x: Int? = y as? Int

Bedeutung: Wenn y nach Int gecastet werden kann, wird x diesen Wert erhalten. Wenn ein Cast nicht möglich ist, wird x null sein (Daher ist x als nullable (Int?) deklariert)

Und das Beste kommt zum Schluss

Man kann Kotlin Programme nach javaScript statt nach Bytecode kompilieren. Damit hat man eine einfache Web-Entwicklung gratis dazu (Okay, soweit ich es sehe fehlt die Interaktion mit vorhandenen js-Libraries, und damit ein guter Teil der Mächtigkeit von JavaScript, aber für einfachere Programme sollte es trotzdem reichen)