Programmering

Hvorfor Kotlin? Otte funktioner, der kunne overbevise Java-udviklere om at skifte

Officielt frigivet i 2016 har Kotlin tiltrukket sig stor opmærksomhed de seneste år, især siden Google annoncerede sin støtte til Kotlin som et alternativ til Java på Android-platforme. Med den for nylig annoncerede beslutning om at gøre Kotlin til det foretrukne sprog til Android undrer du dig måske over, om det er tid til at begynde at lære et nyt programmeringssprog. Hvis det er tilfældet, kan denne artikel hjælpe dig med at beslutte.

Kotlins frigivelseshistorik

Kotlin blev annonceret i 2011, men den første stabile udgivelse, version 1.0, dukkede først op i 2016. Sproget er gratis og open source, udviklet af JetBrains med Andrey Breslav, der fungerer som dets førende sprogdesigner. Kotlin 1.3.40 blev frigivet i juni 2019.

Om Kotlin

Kotlin er et moderne, statisk skrevet programmeringssprog, der indeholder både objektorienterede og funktionelle programmeringskonstruktioner. Det er målrettet mod flere platforme, herunder JVM, og er fuldt interoperabelt med Java. På mange måder er Kotlin, hvordan Java kan se ud, hvis det blev designet i dag. I denne artikel introducerer jeg otte funktioner i Kotlin, som jeg tror, ​​at Java-udviklere vil være glade for at opdage.

  1. Ren, kompakt syntaks
  2. Enkelt type system (næsten)
  3. Null sikkerhed
  4. Funktioner og funktionel programmering
  5. Dataklasser
  6. Udvidelser
  7. Operatør overbelastning
  8. Objekter på øverste niveau og Singleton-mønsteret

Hej Verden! Kotlin versus Java

Liste 1 viser det obligatoriske "Hej verden!" funktion skrevet i Kotlin.

Liste 1. "Hej verden!" i Kotlin

 sjov main () {println ("Hej verden!")} 

Så simpelt som det er, afslører dette eksempel vigtige forskelle fra Java.

  1. vigtigste er en topfunktion; det vil sige, Kotlin-funktioner behøver ikke at være indlejret i en klasse.
  2. Der er ingen offentlig statisk modifikatorer. Mens Kotlin har synlighedsmodifikatorer, er standard offentlig og kan udelades. Kotlin støtter heller ikke statisk modifikator, men det er ikke nødvendigt i dette tilfælde, fordi vigtigste er en topfunktion.
  3. Siden Kotlin 1.3 er parameteren array-of-strings for vigtigste er ikke påkrævet og kan udelades, hvis den ikke bruges. Om nødvendigt ville det blive erklæret som argumenterer: Array.
  4. Der er ikke angivet nogen returtype for funktionen. Hvor Java bruger ugyldig, Bruger Kotlin Enhed, og hvis returtypen for en funktion er Enhed, det kan udelades.
  5. Der er ingen semikoloner i denne funktion. I Kotlin er semikoloner valgfri, og derfor er linjeskift betydelige.

Det er en oversigt, men der er meget mere at lære om, hvordan Kotlin adskiller sig fra Java og i mange tilfælde forbedrer det.

1. Renere, mere kompakt syntaks

Java kritiseres ofte for at være for detaljeret, men noget ordlighedsgrad kan være din ven, især hvis det gør kildekoden mere forståelig. Udfordringen i sprogdesign er at reducere bredden og samtidig bevare klarheden, og jeg tror, ​​Kotlin går langt mod at imødekomme denne udfordring.

Som du så i liste 1, kræver Kotlin ikke semikolon, og det tillader udeladelse af returtypen for Enhed funktioner. Lad os overveje et par andre funktioner, der hjælper med at gøre Kotlin til et renere, mere kompakt alternativ til Java.

Skriv slutning

I Kotlin kan du erklære en variabel som var x: Int = 5, eller du kan bruge den kortere, men lige så klare version var x = 5. (Mens Java nu understøtter var erklæringer, denne funktion dukkede først op på Java 10, længe efter at funktionen var vist i Kotlin.)

Kotlin har også val erklæringer for skrivebeskyttede variabler, som er analoge med Java-variabler, der er erklæret som endelig, hvilket betyder, at variablen ikke kan tildeles igen. Liste 2 giver et eksempel.

Notering 2. Skrivebeskyttede variabler i Kotlin

 val x = 5 ... x = 6 // FEJL: KOMPILERER IKKE 

Egenskaber versus felter

Hvor Java har felter, har Kotlin egenskaber. Egenskaber erklæres og tilgås på en måde svarende til offentlige felter i Java, men Kotlin leverer standardimplementeringer af accessor / mutatorfunktioner for egenskaber; det vil sige Kotlin giver få() funktioner til val egenskaber og begge dele få() og sæt() funktioner til var ejendomme. Tilpassede versioner af få() og sæt() kan implementeres, når det er nødvendigt.

De fleste ejendomme i Kotlin vil have baggrundsfelter, men det er muligt at definere en beregnet ejendom, som i det væsentlige er en få() funktion uden et understøtningsfelt. For eksempel kan en klasse, der repræsenterer en person, have en ejendom til fødselsdato og en beregnet ejendom til alder.

Standard versus eksplicit import

Java importerer implicit klasser defineret i pakke java.lang, men alle andre klasser skal eksplicit importeres. Som et resultat starter mange Java-kildefiler med at importere samlingsklasser fra java.util, I / O-klasser fra java.io, og så videre. Som standard importerer Kotlin implicit kotlin. *, som er omtrent analogt med Java-import java.lang. *, men Kotlin importerer også kotlin.io. *, kotlin.collections. *og klasser fra flere andre pakker. På grund af dette kræver Kotlin-kildefiler normalt færre eksplicit import end Java-kildefiler, især for klasser, der bruger samlinger og / eller standard I / O.

Intet kald til 'nyt' for konstruktører

I Kotlin, nøgleordet ny er ikke nødvendigt for at oprette et nyt objekt. For at ringe til en konstruktør skal du bare bruge klassens navn med parenteser. Java-koden

 Student s = ny student (...); // eller var s = ny studerende (...); 

kunne skrives som følger i Kotlin:

 var s = Student (...) 

Strengskabeloner

Strenge kan indeholde skabelonudtryk, som er udtryk, der evalueres med resultater indsat i strengen. Et skabelonudtryk starter med et dollartegn ($) og består af enten et simpelt navn eller et vilkårligt udtryk i krøllede seler. Strengskabeloner kan forkorte strengudtryk ved at reducere behovet for eksplicit streng sammenkædning. Som et eksempel følgende Java-kode

 println ("Navn:" + navn + ", Afdeling:" + dept); 

kunne erstattes af den kortere, men ækvivalente Kotlin-kode.

 println ("Navn: $ name, Department: $ dept") 

Udvider og implementerer

Java-programmører ved, at en klasse kan forlænge en anden klasse og implementere en eller flere grænseflader. I Kotlin er der ingen syntaktisk forskel mellem disse to lignende begreber; Kotlin bruger et kolon til begge. For eksempel Java-koden

 offentlig klasse Student udvider Personredskaber Sammenlignelig 

ville blive skrevet mere simpelt i Kotlin som følger:

 klasse Studerende: Person, sammenlignelig 

Ingen kontrollerede undtagelser

Kotlin understøtter undtagelser på samme måde som Java med en stor forskel - Kotlin har ikke kontrollerede undtagelser. Mens de var velmenende, er Java's kontrollerede undtagelser blevet kritiseret bredt. Du kan stadig kaste og fangst undtagelser, men Kotlin-kompilatoren tvinger dig ikke til at fange nogen af ​​dem.

Destrukturer

Tænke på destrukturer som en enkel måde at bryde et objekt op i dets bestanddele. En destruktureringserklæring opretter flere variabler på én gang. Listing 3 nedenfor giver et par eksempler. I det første eksempel antages den variabel studerende er en instans af klasse Studerende, som er defineret i liste 12 nedenfor. Det andet eksempel er taget direkte fra Kotlin-dokumentationen.

Liste 3. Eksempler på destruktion

 val (_, lName, fName) = studerende // uddrag for- og efternavn fra elevobjekt // understregning betyder, at vi ikke har brug for student.id til ((nøgle, værdi) på kort) {// gør noget med nøglen og værdien} 

'if' udsagn og udtryk

I Kotlin, hvis kan bruges til kontrolflow som med Java, men det kan også bruges som et udtryk. Java's kryptiske ternære operatør (?:) erstattes af det klarere, men noget længere hvis udtryk. For eksempel Java-koden

 dobbelt max = x> = y? x: y 

ville blive skrevet i Kotlin som følger:

val max = if (x> = y) så x else y 

Kotlin er lidt mere detaljeret end Java i dette tilfælde, men syntaksen er uden tvivl mere læsbar.

'når' erstatter 'switch'

Min mindst foretrukne kontrolstruktur på C-lignende sprog er kontakt udmelding. Kotlin erstatter kontakt erklæring med en hvornår udmelding. Fortegnelse 4 er taget direkte fra Kotlin-dokumentationen. Læg mærke til det pause udsagn er ikke påkrævet, og du kan nemt inkludere værdiområder.

Notering 4. En 'når' erklæring i Kotlin

 når (x) {i 1..10 -> udskriv ("x er i området") i validNumbers -> print ("x er valid")! i 10..20 -> print ("x er uden for området ") andet -> udskriv (" intet af ovenstående ")} 

Prøv at omskrive Listing 4 som en traditionel C / Java kontakt erklæring, og du får en idé om, hvor meget bedre vi har det med Kotlins hvornår udmelding. Også ligner hvis, hvornår kan bruges som et udtryk. I så fald bliver værdien af ​​den tilfredse gren værdien af ​​det samlede udtryk.

Skift udtryk i Java

Java 12 introducerede switch-udtryk. Svarende til Kotlins hvornår, Kræver Java's switch-udtryk ikke pause udsagn, og de kan bruges som udsagn eller udtryk. Se "Loop, switch eller tag en pause? Beslut og gentag med udsagn" for mere om switch-udtryk i Java.

2. System af en enkelt type (næsten)

Java har to separate typesystemer, primitive typer og referencetyper (aka objekter). Der er mange grunde til, at Java inkluderer to separate typesystemer. Faktisk er det ikke sandt. Som beskrevet i min artikel Et tilfælde til opretholdelse af primitiver i Java, er der virkelig kun en grund til primitive typer - ydeevne. I lighed med Scala har Kotlin kun ét typesystem, idet der i det væsentlige ikke skelnes mellem primitive typer og referencetyper i Kotlin. Kotlin bruger primitive typer, når det er muligt, men bruger objekter, hvis det er nødvendigt.

Så hvorfor advarslen om "næsten"? Fordi Kotlin også har specialiserede klasser til at repræsentere arrays af primitive typer uden autoboxing overhead: IntArray, DoubleArray, og så videre. På JVM, DoubleArray er implementeret som dobbelt[]. Bruger DoubleArray virkelig gøre en forskel? Lad os se.

Benchmark 1: Matrixmultiplikation

I forbindelse med sagen for Java-primitiver viste jeg flere benchmark-resultater, der sammenlignede Java-primitiver, Java-wrapper-klasser og lignende kode på andre sprog. En af benchmarks var simpel matrixmultiplikation. For at sammenligne Kotlin-ydeevne med Java oprettede jeg to matrixmultiplikationsimplementeringer til Kotlin, en med Array og en bruger Array. Liste 5 viser implementeringen af ​​Kotlin ved hjælp af Array.

Notering 5. Matrixmultiplikation i Kotlin

 sjov multiplicere (a: Array, b: Array): Array {if (! checkArgs (a, b)) throw Undtagelse ("Matricer er ikke kompatible til multiplikation") val nRows = a.størrelse val nCols = b [0]. størrelse val resultat = Array (nRows, {_ -> DoubleArray (nCols, {_ -> 0.0})} for (rowNum i 0 indtil nRows) {for (colNum i 0 indtil nCols) {var sum = 0.0 for (i i 0 indtil en [0] .størrelse) sum + = a [rowNum] [i] * b [i] [colNum] result [rowNum] [colNum] = sum}} returnere resultat} 

Derefter sammenlignede jeg ydeevnen for de to Kotlin-versioner med Java dobbelt og Java med Dobbelt, kører alle fire benchmarks på min nuværende bærbare computer. Da der er en lille smule "støj" ved kørsel af hver benchmark, kørte jeg alle versioner tre gange og gennemsnitede resultaterne, som er opsummeret i tabel 1.

Tabel 1. Runtime-ydeevne for matrixmultiplikationsbenchmark

Tidsindstillede resultater (i sekunder)
Java

(dobbelt)

Java

(Dobbelt)

Kotlin

(DoubleArray)

Kotlin

(Array)

7.3029.836.8115.82

Jeg blev lidt overrasket over disse resultater, og jeg tegner to takeaways. Først Kotlin ydeevne ved hjælp af DoubleArray er klart bedre end Kotlin-ydeevne ved hjælp af Array, hvilket er klart bedre end Java ved hjælp af indpakningsklassen Dobbelt. Og for det andet, Kotlin-ydeevne ved hjælp af DoubleArray kan sammenlignes med - og i dette eksempel lidt bedre end - Java-ydeevne ved hjælp af den primitive type dobbelt.

Det er klart, at Kotlin har gjort et stort stykke arbejde med at optimere behovet for separate systemsystemer - med undtagelse af behovet for at bruge klasser som DoubleArray i stedet for Array.

Benchmark 2: SciMark 2.0

Min artikel om primitiver inkluderede også et andet, mere videnskabeligt benchmark kendt som SciMark 2.0, som er et Java-benchmark for videnskabelig og numerisk databehandling tilgængelig fra National Institute of Standards and Technology (NIST). SciMark-benchmarket måler ydeevnen for flere beregningsrutiner og rapporterer en sammensat score omtrentligt Mflops (millioner af flydende punktoperationer pr. sekund). Således er større tal bedre for dette benchmark.

Ved hjælp af IntelliJ IDEA konverterede jeg Java-versionen af ​​SciMark-benchmarket til Kotlin. IntelliJ IDEA konverteres automatisk dobbelt[] og int [] i Java til DoubleArray og IntArray i Kotlin. Jeg sammenlignede derefter Java-versionen ved hjælp af primitiver med Kotlin-versionen ved hjælp af DoubleArray og IntArray. Som før kørte jeg begge versioner tre gange og gennemsnitede resultaterne, som er opsummeret i tabel 2. Igen viser tabellen nogenlunde sammenlignelige resultater.

Tabel 2. Runtime-ydeevne for SciMark-benchmarket

Ydeevne (i Mflops)
JavaKotlin
1818.221815.78
$config[zx-auto] not found$config[zx-overlay] not found