Kotlin ja JavaFX – ihanaa!

Aloin kesäloman ratoksi perehtymään hiukan Kotlin ohjelmointikieleen – kirjailin siitä jo ensivaikutelmia aiempaan blogiini. Koska en näillä näkymin pääse hetikohta käyttelemään Kotlinia työprojekteissa, päätin harrastella sillä jotain muuta ominpäin.

JavaFX on entuudestaan suuri rakkaus – harmillisen vähän käytettyä tekniikkaa sekin, mutta toimivaa. Ajattelin yhdistellä näitä ja katsoa mitä siitä syntyy, voi olla että peli vielä jossain vaiheessa. Peliohjelmointi ei ole oma vahva lajini – mutta huonommankin pelin koodailusta tulee opittua paljon. 😉

No niin, joka tapauksessa, kaikki alkaa hello worldistä. Eli miltä näyttää Kotlinilla tehty pääohjelma joka käynnistää JavaFX ikkunan?

import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.layout.Pane
import javafx.stage.Stage

class Test : Application() {

  override fun start(stage: Stage) {
    var pane = Pane()
    var scene = Scene(pane, 200.0, 200.0)
    stage.scene = scene
    stage.show()
  }

  companion object {
    @JvmStatic
    fun main(args: Array<String>) {
      launch(Test::class.java)
    }
  }

}

Mitä juuri tapahtui? 😉 Kotlinissa on tosiaan vähän yksinkertaistettu syntaksi Javaan verrattuna. Kuten kuvasta näkyy, muutama modifieri puuttuu, tyyppejä ei tarvitse toistaa, vaan ne päätellään fiksusti tarvittaessa. Funktiot määritetään fun avainsanalla. Ehkä kiintoisin osa tässä on Scalasta tuttu Companion Object. Static avainsanaa kun ei Kotlinissa ole, Companion Object on luokan ’automaattinen instanssi’. Tämän lisäksi tietysti ilmeistä, että Kotlinista käsin voi käyttää kaikkia Java kirjastoja, mukaanlukien Java 8 sisältämä JavaFX.

Miten tuon sitten voi kääntää, paketoida, ajaa? Mavenin voi opettaa prosessoimaan Kotlinia. Oma pom.xml sisältää seuraavia osia:

<dependencies>
  <dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>${kotlin.version}</version>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-test-junit</artifactId>
    <version>${kotlin.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Ylempänä on tietysti määritetty property-muuttujat kotlin.version ja junit.version (1.0.3 ja 4.12 tällä hetkellä). Kotlin käännöksiä varten tarvitaan tietysti kotlin-stdlib riippuvuus. Ilman etukäteen kirjoitettuja yksikkötestejä ei voi ihminen elää, joten siksi junit riippuvuus. Ja lopuksi vielä kotlin-test-junit riippuvuus tuomaan vähän lisämukavuutta liitokseen. Seuraavaksi veivataan build prosessi käsittelemään kotlin-koodit src/main/kotlin, ja src/test/kotlin kansioiden alta:

<build>
  <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
  <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>

  <plugins>
    <plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <configuration/>
      <executions>
        <execution>
          <id>compile</id>
          <phase>compile</phase>
          <goals>
            <goal>compile</goal>
          </goals>
       </execution>
       <execution>
         <id>test-compile</id>
         <phase>test-compile</phase>
         <goals>
           <goal>test-compile</goal>
         </goals>
       </execution>
     </executions>
    </plugin>
  </plugins>
</build>

Jep, ja näin Maven kääntää Kotlin-koodit pluginin avulla bytecodeksi, tavanomaiseen paikkaan, normaaleissa Maven vaiheissa. Lopuksi säädetään vähän paketointia.

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <archive>
      <manifest>
        <mainClass>game.Pong</mainClass>
      </manifest>
    </archive>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
  </configuration>
  <executions>
    <execution>
      <id>make-assembly</id>
      <phase>package</phase>
      <goals>
        <goal>single</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Eli, tämä Maven Assembly plugin tekee lopputuotoksesta mega-jar paketin, jossa on kaikki tarvittava. Oman maun mukaan voi tehdä fiksumman käyttäen manifest riippuvuuksia, tai Java 9 moduuleita, mutta tässä kohtaa tämä on ihan bueno, ja helpottaa levitystä ja testausta.

Vein koodia vähän pidemmällekin, otin käyttöön game loopin, animaatiota, piirtoa, mutta puidaa niitä toisella kertaa. Pistin koodit Git repositoryyn, josta löytyy välivaiheita. Nykyinen koodi ei tee vielä paljoa, mutta on hauska tutkimuspöytä sille mitä JavaFX on nykymuodossaan, ja miten Kotlin helpottaa koodin kirjoittelua.

Linkki Gittiin: https://github.com/crystoll/kotlinfx

 

 

Mainokset

Kuolema nullpointerexceptioneille: Kotlin ftw!

Lomaillessa ja sairastaessa on hyvin aikaa leikkiä jollain jolle ei ole ollut kysyntää ns oikeissa, hiellä ja tuskalla väännetyissä Wakavissa Projekteissa. Tällä kertaa tutkaan osui Kotlin, johon on ollut tarkoitus perehtyä tilaisuuden tullen.

Kotlin on tuorein lisäys JVM-perheen kieliin; Eli kuten Scala, Clojure, ja Groovy, se on ihan oma erillinen kielensä, syntaksinsa, ja filosofiansa, mutta kun se menee kääntäjän läpi, putkesta ulos tulee Java-virtuaalikoneen bytecodea – vaatii tosin minimitasoksi Java 6 myllyn.

Ajonaikaiset virheet ohjelmissa ovat nykypäivän ohjelmistokehityksessä niitä kalleimpia virheitä – koska ne löytyvät vain kattavalla testauksella, voivat vaatia hyvinkin eksoottisia tiloja ja kombinaatioita ilmentyäkseen, ja mitä myöhemmin ne löytyvät, sitä enemmän ne maksavat. Esim. käyttöönottovaiheen jälkeen löytyvä bugi pitää ensin havaita, sitten raportoida, sitten toistaa, sitten ymmärtää, sitten korjata, sitten testata korjaus, ja testata ettei korjaus rikkonut mitään muualla, jne. Tästä kertyy ihan kohtuullisesti hintaa pienille asioille. Java-kielessä on alusta alkaen ilmennyt kahta eri virhettä yli kaiken muun: ClassCastException, sekä NullPointerException. Java 5 onnistui aikanaan hävittämään edellisen lähes sukupuuttoon Generics tyypitettyjen kokoelmien myötä. Mutta NullPointer on edelleen valitettavan yleinen virhe. Ja kun se paukahtaa, se usein valuu pinossa ylöspäin, ja voi ilmentyä hyvin mielikuvituksellisin tavoin – tai voi aiheuttaa seurauksia jotka näkyvät jossain aivan muualla myöhemmin. Eli pirullisia virheitä.

Nullpointereita on jahdattu eri tavoin. Olen itse opettanut ja kirjoitellut siitä miten Java-kielellä koodatessa voi tiedostaa tämän, ja koodata fiksusti. NullPointer paholainen iskee tyypillisemmin kun metodi palauttaa kokoelman sijasta null-arvon. Eli parempi filosofia on palauttaa aina ei-null arvo, esim. tyhjä. Jos jossain oikeasti tulee vastaan niin harvinainen tapaus, että null ja tyhjä ovat eri asia ja molemmat tarpeen palauttaa, niin se poikkeus on hyvä dokumentoida ja nimetä selkeästi, jotta kutsuja osaa käsitellä arvot oikein. Scala ja Groovy hoitavat nulleja omilla tavoillaan, mutta molemmat kielet edelleen sallivat null arvoja. Java 8 esitteli uuden Optional-tyypin, josta olen myös kirjaillut, ja se tuo vähän helpotusta ongelmiin oikein käytettynä – mutta ei sekään ongelmaa poista, ja mikään ei sitä pakota käyttämään.

Joten – long story short – Kotlin kielen mainostetuin uutuuspiirre on juurikin kuolema nullpointtereille, taas uudella ja tuoreella näkökulmalla. Mutta ei mennä asioiden edelle. Kotlin on syntaksiltaan ja toiminnaltaan eniten lähellä Scala-kieltä, ja Java-koodaajillekin tuttu.

fun main(args: Array<String>) {
  print("Kuolema NULLPOINTEREILLE!")
}

Jeah, puolipisteet valinnaisia, pääohjelman voi tehdä ilman luokkaa. Itse asiassa on myös mahdollista tehdä Kotlin scriptitiedosto, joka sisältää vain pääohjelman koodin:

print("Kuolema NULLPOINTEREILLE scriptissä!")

Kotlin käännetään kotlinc-kääntäjällä, joka voi suoraan paketoida java-kirjastot mukaan. Lopputuotos voi olla .class tiedostoja, tai suoraan ajettava .jar. Kotlin voi vapaasti importoida ja kutsua Java-kirjastoja, sekä JVM sisältä että mitä hyvänsä lisäkirjastoa mitä on tehty sitten vuoden 1995.

kotlinc app.kt -include-runtime -d app.jar

Miten Kotlin sitten auttaa nullpointerien kanssa? Kotlin ei salli normaalien tyyppien asettamista null-arvoon: kääntäjä estää sen. Esim. tyyppi String vaatii aina ei-null arvon. Jos haluat asettaa null-arvon, tyypin perään tulee kysymysmerkki merkiksi, esim. String?

var name:String?
var email:String
name = null
email = null // This line does not compile at all!
print("HELLO " + name + "!")
print("Your email is " + email)

Ylläolevassa koodiesimerkissä näet molemmat käytössä. Jos halutaan siis varata oikeus käyttää null-arvoja, se pitää näkyä jo alunperin tyyppimäärityksessä. On siis varsin mahdollista käyttää null-arvoja – mutta useimmiten ne ovat vain puhdasta laiskuutta. Useimmiten kun on mielijohde palauttaa null-arvo, onkin fiksumpaa palauttaa ns tyhjä arvo, esim. tyhjä String, tyhjä List, jne, tai heittää poikkeus. Niihin harvoihin tapauksiin kun null arvo on oikeasti kätevä, se on helppo ottaa käyttöön.

Mitä muuta jännää on Kotlinissa, verrattuna Java/Scala kieliin esim? Aika paljolti paletti molempien parhaita puolia. Primitiivityypit on poistettu: Kaikki ovat oliotyyppejä. Ei ns checked exceptions poikkeuksia, kaikki ovat nykymuodin mukaan runtime exception tyyppiä, eli käsitellään kun halutaan, ei ole pakko. Nykymuodin mukaan myös kielessä on paljon älykkyyttä ja päättelyä: esim. muuttuja/vakiotyyppiä ei tarvitse määrittää jos kääntäjä sen jo osaa päätellä arvon perusteella. Erikseen löytyy val ja var avainsanat immutable/mutable käyttöä varten. Tottakai lambda expressionit pelaavat mainiosti.

Kielestä on sitten vastaavasti jätetty pois monia piirteitä, joilla Java/Scala koodaajat ampuvat itseään lahjakkaasti jalkaan, esim. static avainsanat ja osat, implicit conversions, ja rohkaisu on ratkoa ongelmia siten että koodi pysyy luettavana ja selkeänä. Toisin kuin Scala, kieli on myös rakennettu modernin Java 8 pohjalle, eli on lähempänä nykysyntaksia mm. Lambdojen ja Stream käsittelyn osalta. Itse pidän myös esim. funktioparametrien oletusarvoista, ja mahdollisuudesta asettaa parametreille arvot nimen perusteella. Tämä vähentää tarpeetonta polymorfismia – toki kohtuudella käytettynä tämäkin, muuten polymorfisuus korvautuu modaalisuudella joka taas ei ole hyvä vaihtokauppa.

2016-06-10 13.13.59

Firman Flamingo hyväksyy Kotlinin

Kotlin on tosiaan melko tuore tulokas kentällä: JetBrains niminen paja lanseerasi sen vuonna 2011, vuonna 2012 se vapautettiin Apache lisenssillä open sourceksi, ja nyt vuonna 2016 siitä on julkaistu vakaaksi laskettava versio 1.0, vuoden alusta. Sen osuus on sen verran marginaalinen ettei vielä näy esim. TIOBE indeksissä, mutta se ei toki tarkoita etteikö sitä jo kentällä käytettäisi.

Kannattaisiko siis käyttää Kotlinia seuraavaan projektiin? Omalta puoleltani annan vahvan peukutuksen. Clojure uskovaiset tuskin kokevat halua kääntyä tänne, se on edelleen oma maailmansa. Java uskovaisten kannattaisi tehdä seuraava projekti ihan millä hyvänsä modernimmalla kielellä, Java on nykymuodossaan aika rasitettu ja päällelaastaroitu sekamelska, ja Scala, Clojure, ja Kotlin ovat kaikki huimia parannuksia siihen. Scala kielen pieni haitta on se että se oli kentällä aikaisin, ja nyt sen linjaus Java 8 mukaiseksi on vähän isompi operaatio. Scalassa on myös turhankin voimallisia tapoja ampua itseään jalkaan, jotka ovat Kotlinista tahallisesti poistettuja.

Kuten Scala, myös Kotlin on hyvä alusta jossa voit toteuttaa vapaasti haluamaasi yhdistelmää funktionaalista tai olio-ohjelmointityyliä. Voit esim. aloittaa iisisti perinteisemmällä Java oliokoodilla, ja hivuttaa itseäsi kohti korkeamman tason funktioita ja immutable, arvoja. Kotlin käyttöönotto on helppoa, ja sen voi perinteiseen tapaan aloittaa vaikkapa testipuolella. Kotlin toimii myös mukavasti esim. Spring Boot+REST alustana, ja sen saumaton yhteensopivuus olemassaolevien Java-kirjastojen kanssa tuo ratkaisua mm. tietokantojen kanssa pelaamiseen.

Itse ainakin käyttäisin. Rakastuin aikanaan Scalaan, ja Kotlin on Scalalle vähän samaa kuin Scala oli Javalle. Scala on edelleen paljonkin voimallisempi työkalu, ja kypsempi myös, mutta molemmat piirteet ovat kaksiteräisiä miekkoja. Ja Java-kieltä taas kukaan ei varmaan ihan ehdottoman innoissaan halua koodata. Sen paras argumentti lienee Cobol-mainen 20 vuoden tuoma maturiteetti, se on hyvä sveitsin armeijan linkkuveitsi siellä työkalupakin pohjalla. Ehkä sen olisi kielenä jo aika kuolla pois. Mutta toki maailmalta löytyy 10 miljoonaa enemmän tai vähemmän aktiivista koodaajaa jotka sitä ymmärtävät. Digitalisoinnin tarpeiden taas laajentuessa eksponentiaalisesti sekin on hyve.

 

Tuolta lisää:

https://kotlinlang.org/