Categories
Mjukvaruhantverk Teknik

Är Kotlin framtidens Java-dialekt?

Vad är poängen med att använda Kotlin och när ska det användas? Ola Rende har gjort research.

Ovan: Flottkatedralen i Kronstadt, belägen på ön Kotlin utanför St Petersburg. (Alex 'Florstein' Fedorov [CC BY-SA 4.0 (//creativecommons.org/licenses/by-sa/4.0) or FAL], via Wikimedia Commons)
Ovan: Flottkatedralen i Kronstadt, belägen på ön Kotlin utanför St Petersburg. (Alex ‘Florstein’ Fedorov [CC BY-SA 4.0 (//creativecommons.org/licenses/by-sa/4.0) or FAL], via Wikimedia Commons)

Det senaste decenniet har en mängd olika språk byggda på Javas virtuella maskin skapats och publicerats. Bland dem finns kända språk som Clojure, Scala och Groovy men också mindre kända språk som Xtend, Ceylon, Frege, JRuby och Jython. Vissa av språken är implementationer av tidigare språk på JVM:en, medan andra är nya språk specifikt framtagna för plattformen. De skiljer sig från varandra i såväl syntax som designfilosofier, och drivs i många fall av skaparnas egna agenda och affärsmotivering. Tittar vi på Xtend så är det framtagen av Eclipse Foundation, som även ligger bakom Eclipse, den vitt spridda IDE:n för Javautvecklare, medan Ceylon är framtaget av Red Hat i syfte att konkurrera med ärkerivalen Oracles Java-implementation.

I en annan del av Javavärlden finns företaget Jetbrains

De har utmärkt sig främst genom sin mycket framgångsrika IDE kallad Intellij IDEA, men har även gjort IDE:er för en mängd andra språk, varav de flesta är orelaterade till Java och JVM:en. En sak är dock säker: deras långa erfarenhet av att utveckla verktyg för programmerare har gett dom en närmast oslagbar inblick i hur utvecklare skriver kod. Utrustade med denna kunskap beslutade sig bolaget till 2011 för att utveckla ett eget språk. Då teamet som utvecklar språket huvudsakligen är baserade i St Petersburg så valde man att döpa språket till Kotlin, efter Kotlin-ön utanför staden.

Men vad är då poängen med att använda Kotlin?

Kort sagt: det är en Java-dialekt som förbättrar i närmast varje aspekt av Java, utan att någonstans bryta kompatibiliteten med Javas klassbibliotek. Den beskrivning som kanske är närmast till hands är att Kotlin är för Java vad C++ var för C: det lägger till ny funktionalitet, syntax och paradigmer samtidigt som det tillåter fullständig interoperabilitet med existerande Javakod.

Det är dock inte bara nya koncept och idéer som lagts på ovanför det gamla bekanta Java. Kotlins designers har även tittat noggrant på de mest utskällda delarna av Java och introducerat olika lösningar på dem.

Är du fortfarande inte övertygad att Kotlin är värt att titta närmare på? Låt oss då ta en titt på ett antal exempel, från enklare till mer avancerade användningsfall:

import java.util.Random;
 
public class JavaMain {
	public static void main(String[] args) {
    	int number = 108;
 
    	final String text = "Your lucky number is ";
 
    	System.out.println(text + number);
 
    	String typeOfNumber;
    	if (new Random().nextInt(109) == 108) {
        	typeOfNumber = "lucky";
    	} else {
        	typeOfNumber = "not lucky";
    	}
    	System.out.println("The generated number was " + typeOfNumber);
	}
}

Ovanstående något naiva Javakod kan i Kotlin skrivas enligt följande:

import java.util.Random
 
fun main(args: Array) {
	var number: Int = 108
 
	val text: String = "Your lucky number is "
 
	println(text + number)
 
	val typeOfNumber: String
	if (Random().nextInt(109) == 108) {
    		typeOfNumber = "lucky"
	} else {
    		typeOfNumber = "not lucky"
	}
	println("The generated number was " + typeOfNumber)
}

Koden ovan har många likheter med vanlig Javakod, men ett par saker står ut:
Vi behöver inte omgärda Kotlinkod i klasser, utan kan använda funktioner som de yttersta byggstenarna i våra program.
Semikolon är frivilliga
Variabler skrivs med namnet följt av datatypen. Dessutom prefixas dem av var (för muterbara variabler) eller val (för konstanta variabler).
Det finns inget new nyckelord, utan klasser instansieras genom ett vanligt funktionsanrop till konstruktor-funktionen.
Vi anropar dessutom en klass från Javas standardbibliotek (java.util.Random) direkt ifrån vår Kotlin-kod. Mer kod än så behövs inte för att anropa Java från Kotlin.

Det finns dock en mängd ändringar vi kan göra för att vår Kotlinkod ska bli mer idiomatisk:

import java.util.Random
 
fun main(args: Array) {
	val number = 108
 
	val text = "Your lucky number is "
 
	println("$text $number")
 
	val typeOfNumber = if (Random().nextInt(109) == 108) "lucky" else "not lucky"
	println("The generated number was $typeOfNumber")
}

Här kan vi se ytterligare ett antal av nyheterna i Kotlin:
Datatypen för variabler som deklareras och instansieras på samma rad kan utelämnas varefter kompilatorn väljer den mest lämpade datatypen.
Variabler kan interpoleras i strängar på samma sätt som i Javascript och andra dynamiska språk.
If-satser fungerar precis som den ternära operatorn i Java genom att de alltid returnerar ett värde.

Ett vanlig problem i Java är kod som ser ut så här:

CustomerFactory.makeCustomer(true, true, 500, false, 0.3f);

Vad betyder alla boolska värden? Och vad är det för siffror inblandade i metodanropet? Ett sätt att mildra problemet i Java är att skapa variabler för parametrarna:

boolean isVip = true;
boolean isActive= true;
int initialBalance = 500;
boolean isLegacyCustomer= false;
float discount = 0.3f;
CustomerFactory.makeCustomer(isVip, isActive, initialBalance, isLegacyCustomer, discount);

Kotlin har däremot lyckats kringgå detta problem helt, med hjälp av namngivna parametrar:

CustomerFactory.makeCustomer(isVip = true, isActive = true, initialBalance = 500, isLegacyCustomer = false, discount = 0.3f)

Det går dessutom att ytterligare minska boilerplate-koden genom att använda default-värden för funktionsparametrar:

fun makeCustomer(isVip: Boolean = true, isActive: Boolean = true, initialBalance: Int = 500, isLegacyCustomer: Boolean = false, discount: Float = 0.3f): Customer { … }

Ett annat vanligt användningsfall i Java är att göra rena databärande klasser utan logik. Om man dock måste ha fält, konstruktorer, toString, equals och hashCode så tenderar dessa klasser att ändå växa sig långa. Kotlin löser detta genom sina dataklasser:

data class User(val name: String, val age: Int)

Denna kodrad skapar en hel klass med två fält, konstruktorer, toString, equals och hashCode.

Ett annat återkommande problem i Java är att det ofta känns som det saknas funktionalitet i standardbiblioteket. I det populära tredjepartsbiblioteket från Google, Guava, finner vi massvis med statiska hjälpmetoder för existerande Java-klasser som ibland känns så självklara att man förvånas över att de inte fanns med i Java från början.
Kotlins lösning på detta är “extension functions”:

fun java.util.ArrayList.reverse(): java.util.ArrayList {
	return this.reversed() as java.util.ArrayList
}
 
fun main(args: Array) {
	var myList = (1..4).toList() as java.util.ArrayList
	myList = myList.reverse()
	myList.forEach({ t -> println(t) })
}

I main-funktionen här ovan får vi en lista med heltal från Arrays.asList som vi lagrar i myList-variabeln. Sedan anropar vi reverse-metoden på denna. Men… i Javas API finns det ingen reverse-metod på denna klass, så hur går detta till? Det som gör det möjligt är funktionen ovanför main-funktionen, i vilken vi lägger till en ny metod på List-klassen kallad reverse. Inuti funktionskroppen anropar vi sedan en metod i Kotlin som vänder listan bak-och-fram och returnerar den.

Kotlin har även dragit sitt strå till stacken när det gäller att effektivisera lamba-uttrycken som gjorde debut i Java 8. Att arbeta med en lista kan i Java 8 typiskt se ut som följande:

persons.stream()
            .filter(p -> p.age >= 18)
            .sorted(Comparator.comparing(p -> p.name))
            .map(p -> p.email)
            .forEach(System.out::println);

I Kotlin kan detta destilleras ned till följande:

persons
    	.filter { it.age >= 18 }
    	.sortedBy { it.name }
    	.map { it.email }
    	.forEach { print(it) }

Några skillnader här är:
I Kotlin kan parenteserna i funktionsanrop utelämnas.
Funktionerna tar här som enda parameter en funktionskropp, som behandlas som en lambda.
Om lambdafunktionen endast har en parameter så deklareras den implicit som en parameter med namnet “it”.
Jämförelser i Kotlin använder värde framför referens, vilket gör att sortedBy inte behöver specificera att det är strängens innehåll snarare än dess minnesadress som ska jämföras, vilket hade krävts i Java.

När är det lämpligt att använda Kotlin?

Eftersom Kotlin är byggt på Javas virtuella maskin så drar det nytta av de optimeringar som gjorts på denna, vilket gör att det kan mäta sig med de andra JVM-språken i snabbhet. Faktumet att språket använder en virtuell maskin gör det dock olämpligt för resurssnåla miljöer som inbäddade system. Bäst lämpat är språket för backend-programmering, där dess förmåga att kopplas ihop med existerande javakod är en stor vinst, och för Android-appar, där den existerande Javaversionen är föråldrad och ibland omständig och långrandig att skriva.

I nästa artikel!

Kotlin erbjuder dock ännu fler nyheter än vad jag visat ovan och i nästa artikel kommer jag ge exempel på bland annat hur man arbetar med trådar med en bråkdel av koden som krävs i Java, visa hur funktioner har tagits till nästa nivå samt ge ett exempel på hur två av Go:s killer features, goroutines och channels, kan utföras i Kotlin.

Leave a Reply

Your email address will not be published.