KOTLIN VS JAVA (DEL 2 UD AF 2)

 
Kotlin vs android - anden del af sammenligningen.

Efter introduktionen udspecificeres nogle features fra Kotlin, som jeg særligt har kunne drage nytte af efter overgangen fra Java til Kotlin.

Funktionel programmering

Både Java og Kotlin er objektorienterede programmeringsprog, men Kotlin yder bedre support til funktionel programmering. Fra Java 8 understøttes funktionel programmering, men det kræver boilerplate kode at implementere, yderligere kommer koden ikke til at fremstå så præcis og klar, som Kotlin supporterer.

Et eksempel er en simpel lambda funktion der summerer to tal. I Java skal man bruge et interface til at holde funktionen, enten en custom eller som i dette eksempelvis BiFunction.

public BiFunction<Integer, Integer, Integer> sum() {
    return (a, b) -> a + b;
}
BiFunction<Integer, Integer, Integer> sum = sum();
sum.apply(3, 5)

Koden kræver api 24 for at kunne køre på android telefoner og mangler abstrahering. Til forskel i Kotlin gemmes lambda funktionen i en variable, som efterfølgende kan bruges som et normalt metodekald.

val sum: (Int, Int) -> Int = { a, b -> a + b}
sum(3,4)

Hvorfor er denne support så vigtig at inkludere i programmeringssproget?

Higher order functions defineres ved at kunne modtage funktioner som parametre og returnere funktioner som værdier. Higher order functions er effektive da de kan abstrahere koden op til et højere niveau, hvor boilerplate koden minimeres og præcisionen af koden optimeres. Et godt eksempel er de supporterede listefunktioner.

I Java er det blevet for vane at skrive liste operationerne selv som dette eksempel, hvor opgaven lyder at finde de mest bedårende dyr af typen Pet:

data class Pet(val name: String, val cuteness: Int)
public List<Pet> cutePets(List<Pet> pets) {
    ArrayList<Pet> cuties = new ArrayList<>();
    for (Pet pet: pets) {
        if(pet.getCuteness() > 8) {
            cuties.add(pet);
        }
    }
    return cuties;
}

Samme funktion kan defineres i en linjes kode i Kotlin ved hjælp af filter, som er en ‘higher order function’, hvor gennemløbningen af listen abstraheres, og fokus i stedet er på det ‘predicate’, der validerer, om dyret skal tilføjes eller ej.

fun cutePets(pets: List<Pet>): List<Pet> = pets.filter { it.cuteness > 8 }

For at generere lambdaen it.cuteness > 8 er der brugt Kotlin sugarcoating. Den oprindelige lambda er defineret som en lambda der får en parameter af typen Pet og bruger den i valideringen:

    pets.filter( { pet : Pet -> pet.cuteness > 8 } )

Compileren ved at listen indeholder objekter af typen Pet, hvorved parametertypen kan undlades:

    pets.filter( { pet -> pet.cuteness > 8 } )

En feature I Kotlin er, at sidste parameter kan rykkes uden for parenteserne, hvis der er en lambda funktion:  

    pets.filter( ) { pet -> pet.cuteness > 8 }

Yderligere når en metode kun har en parameter, der er en lambda funktion, kan parenteserne undlades:

    pets.filter{ pet -> pet.cuteness > 8 }

Sidste sugarcoating, der er tilføjet er, at en lambda funktion, der kun har en parameter kan erstattes med ‘it’.

    pets.filter{ it.cuteness > 8 }

Ved at køre metoden, kan du sige hej til Spiky:

edited.jpg

Det er ikke altid anbefalet at udnytte sugarcoating reglerne for at nå til den korteste version af koden, da det kan gå ud over overblikket af koden, hvad er ‘it’ i denne kontekst. Det kan især blive et problem ved nested lambdas.

Som skrevet tidligere kan Java 8 også supportere ‘higher order functions’, men de gør det ikke så præcist og rent som Kotlin har formået. Yderligere er følgende kodefragment kun kompatibelt for api 24 og over. 

public List<Pet> cutePets(List<Pet> pets) {
    return pets.stream()
            .filter(pet -> pet.getCuteness() > 8)
            .collect(Collectors.toList());
}

Ved brug af Collections ‘higher order functions’ filter, map, maxBy etc, er der en advarsel. Tag kodeeksemplet, hvor opgaven lyder at finde listen af de dyr med med højeste score:

pets.filter{ it.cuteness == pets.maxBy{ pet -> pet.cuteness}?.cuteness }

Opgaven er løst og koden vil finde det mest bedårende dyr samt tilføje alle dyr der har fået samme score. Problemet er, at filter funktionen finder det mest bedårende dyr, hver gang et dyr evalueres. Performance af dette kodefragment er derfor O(n2) i stedet for O(n), der kan opnås ved istedet at gemme resultatet fra maxBy i en lokal variable.

val cutest = pets.maxBy{ it.cuteness}
pets.filter{ it.cuteness == cutest?.cuteness }

Extension methods

Udover bedre support af functional programming har Kotlin tilføjet extension methods. Det er nu muligt at tilføje metoder til klasser, der ellers ikke er tilgængelige. I eksemplet ovenfor er det en mulighed at tilføje enten en metode eller en property som er tilgængelig via List<Pet> instansen:

fun List<Pet>.cutePets(cutenessLimit: Int) : List<Pet> =
    filter{it.cuteness > cutenessLimit}
val List<Pet>.cutePets: List<Pet>
    get() {
        return filter { it.cuteness > 8 }
    }
println("Kotlin extension method ${pets.cutePets(8)}")
println("Kotlin extension property ${pets.cutePets}")

Hvilket kan give store tidsbesparelser I forhold til at skulle nedarve fra en klasse og tilføje den specifikke manglende metode i den custom klasse. Yderligere tilføjer IDE’et automatisk metoderne til code completion dropdown listen, så programmøren kan se de mulige extension metoder sammen med de ordinære.

Method Overloading

Som et alternativ til method overloading, giver Kotlin muligheden for default values. Tag eksemplet joinToString():

joinToString( separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null)

En default værdi sættes efter type deklarationen. Resultatet er at programmøren kan bruge default værdierne ved at kalde metoden uden nogle parametre, eller specificere de parametre der ønskes overskrevet.

pets.joinToString()
pets.joinToString( separator = “; “)

Dette kan yderligere fremme læsbarheden af koden, når parameternavnene er angivet:

pets.joinToString( “; “, “{ “, “ }” )
pets.joinToString( separator = "; ", prefix = "{ ", postfix = " }")

Konklusion

Kotlin leverer det, der er blevet lovet. Kotlin har fokuseret på at optimere udviklingen ved at skære boilerplate koden væk, hvilket er bemærkelsesværdig efter at have programmeret i Kotlin. De to mest berigende features er data classes, som næsten gør klasser gratis at oprette, samt den forbedrede support for funktionel programmering, der i mange tilfælde kan gøre for-loops redundante. I stedet kan programmøren fokuserer på predicates og transformationen af data. Det tager tid at vænne sig til at skrive Kotlin især den funktionelle del, men der er tid at spare når Kotlin begynder at komme ind under huden. En hjælp er eksempelvis Android Studio der kommer med optimerings foreslag, der kan føre ens kode væk fra de dårlige Java vaner, hvor programmering af boilerplate kode er hverdag og over til en mere præcis og klar Kotlin kode.


Af Lasse Sørensen

Android udvikler hos Touchlogic