Programmeren voor Android: Kotlin vervangt Java

Marco den Teuling
0

Inhoudsopgave

Google heeft Kotlin uitgeroepen tot de officiële tweede programmeertaal om te programmeren voor Android. Kotlin belooft veel: Java-ontwikkelaars productiever maken, fouten als NullPointerExceptions principieel uitsluiten en toch de voordelen van het enorme Java-ecosysteem kunnen gebruiken. Bovendien kun je Kotlin-code compileren naar zowel JavaScript als machinetaal.

Op Google’s I/O-conferentie van 2017 verklaarde het bedrijf Kotlin tot de officiële tweede programmeertaal voor Android – onder daverend applaus. Dat enthousiasme kwam niet zomaar uit de lucht vallen. Als modern Java-alternatief zorgt Kotlin namelijk voor compactere code, minder fouten en meer plezier bij het programmeren.

Maar de taal is niet alleen populair bij Android-apps. Ook op servers zie je het steeds vaker. Kotlin ondersteunt het zakelijk bijzonder populaire Spring-framework met speciale API’s. Ook de buildtool Gradle kan tegenwoordig met Kotlin-bestanden geconfigureerd worden. En de grootste banken ter wereld gebruiken Kotlin om het blockchainplatform Corda te ontwikkelen. Genoeg redenen om in een serie artikelenin te gaan op het gebruik van Kotlin bij programmeren voor Android of andere toepassingen.

Kotlin wordt ontwikkeld door de Tsjechische softwareproducent JetBrains. Die staat bekend om ontwikkeltools als IntelliJ IDEA en ReSharper. Het bedrijf begon aan de nieuwe programmeertaal om de productiviteit van de eigen ontwikkelaars op te voeren. Die werkten eerder met Java. Daarom moest Kotlin ook naadloos met Java kunnen samenwerken. Verder moest de taal makkelijk te leren en te controleren zijn en goed geschikt voor ondersteuning met tools. De productiviteit hadden ze ook wel omhoog kunnen krijgen met het destijds al beschikbare Scala. De echte redenen om een eigen taal te ontwerpen komen dus uit de andere doelen.

Programmeren voor Android met Kotlin - tools

Kotlin vs Java: ‘Hello world’

Vergeleken met Java blinkt Kotlin uit met onder meer compactere code, null-safety, een uniform typesysteem dat primitieve datatypen als integers en objecten combineert, properties in plaats van getters en setters, het ontbreken van checked exceptions, delegation als handig alternatief voor overerven, statische uitbreidingsfuncties en strings met ingebedde variabelen.

Meteen al bij het beroemde ‘hello world’-voorbeeld zie je de voordelen ten opzichte van Java:

fun main(args: Array<String>) {

valname = args.getOrElse(0)

{ “world” }

println(“hello $name!”)

}

De functie main hoeft niet ingebed te worden in een klasse en ook het bekende public static void is overbodig. Je kunt variabelen direct inbedden in een string door er een $ voor te zetten. Bij parameters en variabelen geef je eerst de naam (args) en dan, na een dubbele punt, het type (Array<String>). Ten eerste leest dat prettiger, aangezien de naam meestal belangrijker is dan het type. Ten tweede kun je op deze manier de typeaanduidingen bij variabelen weglaten.

Variabelen definieer je met var, onveranderlijke waarden met val, kort voor value:

vara = 1 // veranderlijk

valb = 2 // onveranderlijk

Door het weglaten van de typeaanduiding bij variabelen zou je kunnen denken dat Kotlin variabelen hun type dynamisch toewijst, maar dan heb je het mis. Kotlin is net als Java statisch getypeerd. Dankzij ingebouwde typeafleiding (type inference) is de compiler in staat om typen zelfstandig te achterhalen. Met typeaanduiding zou je de definities hierboven ook zo kunnen schrijven:

vara: Int = 1;

valb: Int = 2;

Als je wilt, kun je regeleindes versieren met een puntkomma, maar dat is niet verplicht. Sterker nog: IntelliJ IDEA ziet de puntkomma als niet-Kotlinesk en geeft je een waarschuwing.

Alles onder controle

In Kotlin is vrijwel alles een expressie en heeft dus een waarde. Dat geldt bijvoorbeeld ook voor if. Dankzij deze handige en elegante uniformiteit kun je de expressie zonder omwegen voor een toewijzing gebruiken:

valcolor =

if (stock == 0) Color.RED

else if (stock < 10) Color.YELLOW

else Color.GREEN

Voor het switch-statement van Java gebruik je in Kotlin when. Alleen is when ook een expressie en krijg je er extra mogelijkheden bij, zoals checks op type en range. Om precies te zijn staat when willekeurige expressies toe die een waarheidswaarde representeren.

valgetal = when(x) {

0 -> “niks”

1, 2 -> “een of twee”

in1..9 -> “onder de tien”

is String -> “geen getal”

else -> “iets anders”

}

Kotlins when kan wel veel meer dan een switch in Java, maar er is geen fall-through. In Java worden ook alle volgende cases uitgevoerd als een case niet expliciet wordt afgesloten met break. Fall-through staat daarom bekend als bron van fouten. De code wordt daar immers niet duidelijker van.

De foreach-loop is bijna hetzelfde als bij Java, alleen zie je terug dat Kotlin streeft naar goede leesbaarheid. In plaats van for (Item item : collection) schrijf je for (item in collection). In for-loops kunnen objecten ook gelijk aangemaakt worden met ‘destructuring declarations’:

valmyMap = mapOf(1 to “een”,

2 to “twee”)

for((key, value) in myMap) {

println(“key: $key, value: $value”)

}

Nooit meer NullPointerExceptions

In Java krijg je vaak fouten doordat velden of variabelen die geen null mogen zijn op een gegeven moment dom genoeg toch die waarde krijgen. Daar wilden de makers van Kotlin definitief mee afrekenen. Daarom zorgt de compiler ervoor dat referenties nooit de waarde null krijgen, tenzij dat expliciet toegestaan is en voor de dereferencing gecontroleerd wordt. Daarvoor zet je een vraagteken achter de typeaanduidingen:

valname: String? = null

valcity: String = null // fout!

Voordat je een ‘nullable’ referentie mag gebruiken, moet je eerst controleren of die niet null is:

vallength =

if (name != null)name.length else0

Voor deze veelvoorkomende operatie is er een verkorte schrijfwijze:

vallength: Int = name?.length ?: 0

Het eerste vraagteken is bedoeld voor veilige dereferencing (zonder exception). Dat betekent dat het resultaat van de expressie name?.length nog steeds null kan zijn. Daarom is de expressie van het type Int?. Om tot een ‘zuivere’ Int te komen heb je dus nog een voorwaarde nodig. Daarvoor wordt de van Groovy bekende Elvis-operator ?: gebruikt. Die heet overigens zo omdat hij doet denken aan een emoticon met Elvis-kuif.

Fun met functies in Kotlin

Tot zover zijn de verschillen tussen Kotlin en Java beperkt. Bij de functies is dat al anders. Die heten bij Kotlin trouwens gemakshalve altijd functies en geen methodes. Je begint ze met fun, gevolgd door de lijst parameters, het returntype en tot slot de inhoud. Functies die uit één expressie bestaan kun je – net als in de wiskunde – schrijven met een isgelijkteken achter de parameters:

fun max(a: Int, b: Int): Int =

if (a > b) a else b

Als een functie geen returnwaarde heeft, geef je ook geen returntype aan:

funsend(message: String) {

require(!message.isEmpty)

server.send(message)

}

Toch heeft de functie dan – anders dan bij Java – een regulier returntype, namelijk Unit. Dat komt overeen met bijvoorbeeld void in Java, met als belangrijk verschil dat dit geen speciaal geval is. Dat helpt weer om speciale gevallen in je eigen code te vermijden.

Bijzonder handig zijn zogeheten functieparameters met standaardwaarden. Die maken overloads en builders in veel gevallen overbodig. Bij het definiëren van een functie kan elke parameter een waarde toegewezen krijgen die automatisch gebruikt wordt, als de parameter bij het aanroepen van de functie niet expliciet ingesteld wordt:

funcalc(a: Int = 3, b: Int = 2,

c: Int = 1) = a * b + c

Deze functie zou dan bijvoorbeeld als volgt gebruikt kunnen worden:

calc() // 7

calc(b = 4) // 13

calc(c = 6, a = 5) // 16

Zoals in de laatste regel te zien is, hoef je je bij de genoemde parameters met standaardwaarde ook niet aan de precieze volgorde van de parameters te houden.

Met Kotlin zijn ook functies van een hogere orde mogelijk. Een functie kan dus bijvoorbeeld een andere functie als parameter meekrijgen en uitvoeren:

funtimes(n: Int, f: (Int) -> Unit) {

for (i in1..n) {

f(i)

}

}

De functie times krijgt bij een aanroep een lambda-expressie mee die n keer uitgevoerd wordt en zelf de actuele teller meekrijgt. Het volgende voorbeeld zet op die manier de getallen 1, 2, 3, 4 en 5 op het scherm:

times(5, { x -> println(x) }

Als er maar één parameter is, kun je de parameterlijst in de lambda-expressie (× ->) ook weglaten en in plaats daarvan it gebruiken.

times(5, { println(it) })

Maar aangezien in dit geval puur een andere functie – println – aangeroepen wordt, kun je ook simpelweg een functiereferentie gebruiken:

times(5, ::println)

Om tot een syntax te komen zoals bij ingebouwde controlestructuren als if, mag je de lambda-expressie voor de laatste functieparameter ook achter de parameterlijst zetten:

times(5) { x ->

println(x)

}

Zo zou je bijvoorbeeld heel elegant een functie kunnen gebruiken om transacties beschikbaar te maken:

transactional {

// instructies in transactie

}

Je kunt functies ook binnen andere functies definiëren om de scope van de binnenste functie zo gericht mogelijk te houden:

funa(p: Iterable<Int>): Int {

funb(x: Int) = x * x – 1

return p.map { b(x) }.sum()

}

Uitbreidingsfuncties

Je hebt vast wel eens gewenst dat je een klasse uit een vreemde bibliotheek achteraf met een functie kon uitbreiden zonder daarvoor eerst een nieuwe klasse te moeten afleiden. Als dat überhaupt mogelijk is, want overerving is vaak niet toegestaan – en daar zijn goede redenen voor. In Kotlin heb je hiervoor uitbreidingsfuncties (extensions). Zo kun je bijvoorbeeld voor de klasse Int een functie definiëren die een actie x keer uitvoert:

funInt.times(action: (Int) -> Unit)

{

for (i in1..this) {

action(i)

}

}

5.times {print(it)}

Het voorbeeld voert ‘12345’ uit.

Om van een functie een uitbreidingsfunctie te maken, hoef je alleen het uit te breiden type (hier Int) met een punt voor de functienaam te zetten. Het object van het uitgebreide type is binnen de functie beschikbaar als this. In het voorbeeld is this het getal 5 waarmee de functie aangeroepen wordt.

Slimme typeconversie

Als je met if of when een type controleert, converteert Kotlin het type automatisch. In het volgende voorbeeld heb je direct toegang tot de eigenschap length van de klasse String omdat de compiler ‘weet’ dat het na een succesvolle controle om een string moet gaan. Daar heb je geen expliciete typeconversie voor nodig:

funmeasure(something: Any) {

if (m is String) {

println(m.length)

}

}

Hetzelfde principe komt trouwens ook naar voren als je op null controleert. Dan wordt een ‘nullable’ type namelijk omgezet naar het bijbehorende basistype, dus bijvoorbeeld String? naar String.

Nog veel meer Kotlin

Klassen en overerving, lokale variabelen, operatoren – Kotlin biedt op nog veel meer gebieden wezenlijke vereenvoudigingen in vergelijking met Java. Meer daarover leggen we uit in een tweede deel van deze serie artikelen, dat is verschenen in c’t magazine 03, 2018. In het derde deel gaan we verder in op functioneel programmeren met Kotlin (c’t magazine 04, 2018).

Als je ondertussen alvast eens met Kotlin wilt experimenteren, kun je je eerste stappen zetten op try.kotlinlang.org. Deze online runtimeomgeving biedt zelfs syntaxaanvulling en kan Java-code omzetten naar Kotlin. Als je professioneel aan de slag wilt met Kotlin, is de laatste versie van IntelliJ IDEA de eerste keus. Ook de gratis Community Edition ondersteunt Kotlin. Daarvoor heb je wel JDK (versie 6 of hoger) nodig.

Programmeren voor Android met Kotlin - web

(Christian Helmbold / Herman Heringa, c’t magazine 1-2/2018)

 

 

Deel dit artikel

Lees ook

Arduino project met geheugenspel: zelfbouw-Simon

Je kunt met experimenteerboards leuk knutselen en leren programmeren, maar ook plezier hebben van het eindresultaat, zoals dit Arduino project laat zi...

Thuisnetwerk instellen voor Windows 10, zonder Thuisgroepen

Microsoft heeft de 'Thuisgroepen' verwijderd uit Windows 10. Als je die gebruikte voor het netwerk van je pc's, moet je dat op een andere manier oplos...

0 Praat mee

avatar
  Abonneer  
Laat het mij weten wanneer er