(Pauline van Alst) Kotlin is de laatste jaren een veel voorkomend onderwerp op conferenties. Deze opkomende JVM taal brengt nieuwe features met zich mee die de taal onderscheiden van Java.
Kotlin is populair binnen de mobiele applicatie wereld, met name omdat het de ambitie heeft een multi-platform taal te worden. Dit betekent dat Kotlin code gedeeld en aangeroepen kan worden over verschillen platformen (Android en iOS bijvoorbeeld). Kotlin begint echter ook server-side een groter aandeel te winnen en wordt zo steeds interessanter voor Java ontwikkelaars. De vraag is nu of Kotlin echt een kandidaat is om Java te vervangen of dat Kotlin zich aanvullend aan Java gaat opstellen.
Overzicht features
Kotlin is bekend om te streven naar minder code voor dezelfde functionaliteit zonder op leesbaarheid en onderhoudbaarheid in te leveren. Taal features die dat mogelijk maken zijn bijvoorbeeld data classes, extension functions, multiline Strings en String templates.
Een data class maakt het bijvoorbeeld mogelijk om in een regel equivalent van een een plain Java object te declareren (zie listing 1). In Java 14 zal dit middels de preview feature “records” mogelijk zijn (JEP 359).
data class Article(val title: String, val author: String)
Listing 1
|
Extension functions maken het mogelijk om functionaliteit op een class te definiëren, buiten deze class om. Een zeer krachtige feature, die zorgvuldig gebruikt moet worden. In listing 2 is te zien hoe bijvoorbeeld functionaliteit op een lijst van objecten gedefinieerd is. Dit is erg handig voor codebases waar veel logica op collecties plaats vindt en voorkomt hiermee code duplicatie en fouten door uiteenlopende code.
fun List<Article>.writtenBy(author: String) = this.filter { it.author == author }
Listing 2
|
Ook het gebruik van String templates kan de leesbaarheid van de codebase verbeteren. In combinatie met een multiline String is het in Kotlin mogelijk op een leesbare manier een String op te bouwen aan de hand van variable in de code. Een voorbeeld hiervoor is te zien listing 3. Multiline strings zullen in Java 13 middels een preview feature aanwezig zijn (JEP 355).
val title = “Java en Kotlin”
val author = “The Duke” val articleJson = “”” { “title”: $title, “author” : $author } “””
Listing 3
|
Naast deze features, biedt Kotlin het ook aan om pairs te definiëren middels de keyword “to” en zijn de manipulaties op collectie niveau uitgebreider dan in Java, wat functioneel programmeren makkelijker maakt.
Collecties in Kotlin kennen ook een verschil ten opzichte van Java, er worden namelijk twee verschillende type collecties gebruikt in Kotlin: Sequence en Iterable. Het verschil zit hem met name in het processen van de collecties. Sequences maken gebruik van lazy evaluation (zoals streams in Java) en Iterables gebruiken eager evaluation. Voor performance redenen is het dan belangrijker om Sequences te gebruiken bij grotere collecties.
Andere grote feature ten opzichte van Java, is “null-safety”. Kotlin kent namelijk nullable en niet-nullable types. Een type kan nullable gemaakt worden, door deze te postfixen met een “?”. Is een variable van een niet nullable type en wordt deze met “null” geinstantieerd, zal die leiden tot een compilatie fout. Dit voorkomt vaak onnodige runtime fouten. Zie listing 4 voor een voorbeeld hiervan.
data class Article(val title: String, val author: String)
val articleWithoutAuthor = Article(“Nieuwe artikel”, null) //compileert niet
Listing 4
|
Hierboven genoemde voorbeelden van features zijn vooral aanvullend om de huidige features van Java. Echter heeft Kotlin ook echt nieuwe features te bieden.
fun main() { GlobalScope.launch { waitForIt() } runBlocking { start() } println(“Finishing”) }
suspend fun start() { println(“Staring to process”) delay(200L) }
suspend fun waitForIt() { delay(100L) println(“Waiting a bit”) }
Listing 5
|
Een groot voorbeeld sinds release 1.3 zijn de coroutines, die multithreaded programmeren mogelijk maakt. Bij Java wordt hier aan soortgelijke functionaliteit gewerkt in het kader van het project Loom.
Coroutines zijn lightweight threads die het mogelijk maken om non-blocking functionaliteit te implementeren. Door gebruik te maken van coroutine scopes, kunnen er asynchrone en non-blocking code geschreven worden. Wanneer er in een scope, een suspend function wordt aangeroepen, kan de thread vrij gegeven worden en elders hergebruikt worden. Wanneer deze functie klaar is, kan de thread weer verder gaan waar deze oorspronkelijk gebleven was. Op deze manier kunnen threads optimaal gebruikt worden binnen de applicatie. In listing 5 is te zien, hoe er non-blocking thread wordt gestart in de “launch” blok, waar een “suspend” function wordt aangeroepen. Een functie met het keyword “suspend” is een functie die de thread niet zal blokkeren zolang er geen resultaat is. Vervolgens is er een “runBlocking” blok die het mogelijk maakt om een brug te bouwen tussen de asynchrone code (delay) en de synchrone code (println statements). In dit blok wordt er gewacht totdat alles is verwerkt, voordat er verder gegaan kan worden met de rest.
De uitkomst van deze code is dan ook:
- “Staring to process”
- “Waiting a bit”
- “Finishing”
Er is te zien aan de uitkomt dat beide blokken door elkaar lopen en dat het eerste blok de rest niet ophoudt.
Daarnaast biedt Kotlin de mogelijkheid om operators te implementeren voor objecten. Zo is het mogelijk om bijvoorbeeld de “plus operatie” voor class “Money” te definiëren (zie listing 6). Dit is met name erg waardevol voor codebases waar veel in gerekend wordt. Hiermee worden rekenkundige operaties makkelijker te lezen en dus ook makkelijker te onderhouden.
data class Money(val amount: Double, val currency: String) { operator fun plus(money: Money): Money { if (this.currency != money.currency) { throw CurrencyNotTheSameException(); } return Money(this.amount + money.amount, this.currency) } }
val tenEuros = Money(10.00, “EUR”) val oneEuros = Money(1.00, “EUR”) val elevenEuros = tenEuros + oneEuros // Money(11.00, “EUR”)
|
Ook een hele fijne feature in Kotlin, is het feit dat er DSL’s gedefinieerd kunnen worden. Zo ondersteunt Spring Webflux Kotlin tot zover dat het ook een router DSL aanbiedt (zie listing 7).
Dit kan erg waardevol zijn voor het schrijven van testen. Testen kunnen hierdoor leesbaar worden voor de personen in een team die geen programmeer-ervaring hebben. Denk aan een business analist of een product owner. Dit maakt het makkelijk om de testen te laten reviewen en zo de juiste functionaliteit te garanderen.
fun router(articleService: ArticleService) = router { accept(APPLICATION_JSON).nest { “/api”.nest { “/articles”.nest { GET(“/”, articleService::all) } } } }
Listing 7
|
Kotlin: integratie met Spring
Spring wordt veel gebruikt in de Java wereld om server side applicaties de bouwen. Spring integreert doorgaans goed met Kotlin, echter is het wel belangrijk om rekening te houden met bepaalde zaken. Zo eist Spring van bepaalde beans dat ze niet final zijn. Classes zijn in Kotlin by default final. Classes die geannoteerd zijn met @Configuration, @Transactional en @Async moeten door Spring ge-extend kunnen worden. Het is dan nodig om deze classes niet-final te maken, met andere woorden “open”. Er kan er voor gekozen worden om gebruik te maken van een compiler plugin die dit compile time doet. Echter wordt het dan wel minder expliciet in de code dat deze niet final zijn.
Spring maakt ook veel gebruik van reflectie om zo met beans te kunnen werken, ook hiervoor is er een Kotlin specifieke api nodig om die met Spring mogelijk te kunnen maken (listing 8).
Daarnaast, wanneer er in de applicatie, gebruik wordt gemaakt van onder andere JPA Entiteiten in Spring, zijn er op deze entiteiten “no-args” constructors nodig. Dit spreekt Kotlin classes weer tegen die doorgaans enkel een full-args constructor definiëren. Ook hiervoor is een compile plugin nodig die voor deze entiteiten de nodige constructor aanmaakt. De boven genoemde plugins zijn in listing 9 terug te vinden.
<dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency>
Listing 8
|
Kotlin en Java: de samenwerking
Kotlin is erg sterk in de samenwerking met Java. In elk Java project is het mogelijk om vrijwel pijnloos Kotlin te integreren. Zo is het mogelijk om vanuit Kotlin, Java code aan te roepen en vice versa. Daarnaast kunnen de bekende frameworks in de Java wereld goed overweg met Kotlin. Bij sommige frameworks zijn wel wat extra trucjes nodig om het werkend te krijgen, maar deze zijn doorgaans goed gedocumenteerd.
Wanneer je ervoor kiest om over te stappen naar Kotlin, is het belangrijk om de idiomen van de taal goed te begrijpen en deze ook toe te passen. Enkel dan is de winst van de features echt merkbaar. Daarbij is Kotlin toch echt een nieuwe taal en niet persé syntatic sugar voor Java. Hierdoor is het belangrijk om te beseffen dat een team zich een nieuwe taal aan moet leren en zich daarin moet gaan specialiseren.
Conclusie
Kotlin heeft een makkelijke leercurve en als je van de Java-wereld komt, kan het soms als een opluchting voelen om in Kotlin te programmeren. Er is immers minder code nodig om eenzelfde feature te implementeren. Daarnaast zijn er language features die soms intuïtiever aanvoelen. Echter kan Kotlin pas echt een toegevoegde waarde zijn als er gebruik gemaakt wordt van de distinctieve features, die dus niet in Java te vinden zijn. Ook zijn sommige features in Kotlin, terug te vinden in de nieuwste Java versies of deze features worden beschikbaar gemaakt middels preview features. Het is belangrijk om de winst van het gebruik van Kotlin op het project goed te overwegen, voordat een team de stap zet om een nieuwe taal te leren.
Referenties:
https://wiki.openjdk.java.net/display/loom/Main
https://openjdk.java.net/jeps/355
https://openjdk.java.net/jeps/359
https://www.baeldung.com/spring-webflux-kotlin
https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html
https://kotlinlang.org/docs/reference/
https://blog.jetbrains.com/idea/2018/10/spring-and-kotlin-final-classes/