Programmering

Hvorfor Kotlin? Åtte funksjoner som kan overbevise Java-utviklere om å bytte

Offisielt utgitt i 2016, har Kotlin vakt mye oppmerksomhet de siste årene, spesielt siden Google kunngjorde sin støtte til Kotlin som et alternativ til Java på Android-plattformer. Med den nylig annonserte beslutningen om å gjøre Kotlin til det foretrukne språket for Android, lurer du kanskje på om det er på tide å begynne å lære et nytt programmeringsspråk. Hvis det er tilfelle, kan denne artikkelen hjelpe deg med å bestemme deg.

Kotlins utgivelseshistorikk

Kotlin ble kunngjort i 2011, men den første stabile utgivelsen, versjon 1.0, dukket ikke opp før i 2016. Språket er gratis og åpen kildekode, utviklet av JetBrains med Andrey Breslav som hovedsprogdesigner. Kotlin 1.3.40 ble utgitt i juni 2019.

Om Kotlin

Kotlin er et moderne, statisk skrevet programmeringsspråk som har både objektorienterte og funksjonelle programmeringskonstruksjoner. Den retter seg mot flere plattformer, inkludert JVM, og er fullt kompatibelt med Java. På mange måter er Kotlin hvordan Java kan se ut hvis den ble designet i dag. I denne artikkelen introduserer jeg åtte funksjoner i Kotlin som jeg tror Java-utviklere vil være glade for å oppdage.

  1. Ren, kompakt syntaks
  2. Enkelt type system (nesten)
  3. Null sikkerhet
  4. Funksjoner og funksjonell programmering
  5. Dataklasser
  6. Utvidelser
  7. Overbelastning av operatør
  8. Objekter på toppnivå og Singleton-mønsteret

Hei Verden! Kotlin versus Java

Oppføring 1 viser obligatorisk "Hei, verden!" funksjon skrevet i Kotlin.

Oppføring 1. "Hei, verden!" i Kotlin

 morsom hoved () {println ("Hei, verden!")} 

Så enkelt som det er, avslører dette eksemplet viktige forskjeller fra Java.

  1. hoved- er en toppfunksjon; det vil si at Kotlin-funksjoner ikke trenger å være nestet i en klasse.
  2. Det er ingen offentlig statisk modifikatorer. Mens Kotlin har synlighetsmodifikatorer, er standardverdien offentlig og kan utelates. Kotlin støtter heller ikke statisk modifikator, men det er ikke nødvendig i dette tilfellet fordi hoved- er en toppfunksjon.
  3. Siden Kotlin 1.3, parameteren array-of-strings for hoved- er ikke nødvendig og kan utelates hvis den ikke brukes. Om nødvendig vil det bli erklært som argumenterer: Array.
  4. Ingen returtype er spesifisert for funksjonen. Hvor Java bruker tomrom, Bruker Kotlin Enhet, og hvis returtypen til en funksjon er Enhet, kan det utelates.
  5. Det er ingen semikolon i denne funksjonen. I Kotlin er semikolon valgfri, og derfor er linjeskift betydelige.

Det er en oversikt, men det er mye mer å lære om hvordan Kotlin skiller seg fra Java og i mange tilfeller forbedrer det.

1. Renere, mer kompakt syntaks

Java blir ofte kritisert for å være for utførlig, men noe utholdenhet kan være din venn, spesielt hvis det gjør kildekoden mer forståelig. Utfordringen i språkdesign er å redusere detaljrikdommen og samtidig beholde klarheten, og jeg tror Kotlin går langt med å møte denne utfordringen.

Som du så i oppføring 1, krever ikke Kotlin semikolon, og det tillater utelatelse av returtypen for Enhet funksjoner. La oss se på noen få andre funksjoner som gjør Kotlin til et renere, mer kompakt alternativ til Java.

Skriv inn slutning

I Kotlin kan du erklære en variabel som var x: Int = 5, eller du kan bruke den kortere, men like klare versjonen var x = 5. (Mens Java nå støtter var erklæringer, den funksjonen dukket ikke opp før Java 10, lenge etter at funksjonen hadde dukket opp i Kotlin.)

Kotlin har også val erklæringer for skrivebeskyttede variabler, som er analoge med Java-variabler som er erklært som endelig, som betyr at variabelen ikke kan tildeles på nytt. Oppføring 2 gir et eksempel.

Oppføring 2. Skrivebeskyttede variabler i Kotlin

 val x = 5 ... x = 6 // FEIL: SKAL IKKE KOMPILERES 

Egenskaper kontra felt

Der Java har felt, har Kotlin egenskaper. Egenskaper blir deklarert og tilgang til på en måte som ligner på offentlige felt i Java, men Kotlin tilbyr standardimplementeringer av accessor / mutator-funksjoner for egenskaper; det vil si Kotlin gir få() funksjoner for val egenskaper og begge deler få() og sett() funksjoner for var eiendommer. Tilpassede versjoner av få() og sett() kan implementeres når det er nødvendig.

De fleste eiendommer i Kotlin vil ha støttefelt, men det er mulig å definere en beregnet eiendom, som egentlig er en få() funksjon uten støttefelt. For eksempel kan en klasse som representerer en person ha en eiendom for fødselsdato og en beregnet eiendom for alder.

Standard kontra eksplisitt import

Java importerer implisitt klasser definert i pakke java.lang, men alle andre klasser må importeres eksplisitt. Som et resultat starter mange Java-kildefiler med å importere samlingsklasser fra java.util, I / O-klasser fra java.io, og så videre. Som standard importerer Kotlin implisitt 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å grunn av dette krever Kotlin-kildefiler normalt færre eksplisitt import enn Java-kildefiler, spesielt for klasser som bruker samlinger og / eller standard I / O.

Ingen samtaler til "nye" for konstruktører

I Kotlin, nøkkelordet ny er ikke nødvendig for å lage et nytt objekt. For å ringe en konstruktør, bruk bare kursnavnet med parentes. Java-koden

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

kunne skrives som følger i Kotlin:

 var s = Student (...) 

Strengmaler

Strenger kan inneholde maluttrykk, som er uttrykk som evalueres med resultater satt inn i strengen. Et maluttrykk starter med et dollartegn ($) og består av enten et enkelt navn eller et vilkårlig uttrykk i krøllete bukseseler. Strengmaler kan forkorte strenguttrykk ved å redusere behovet for eksplisitt strengkombinasjon. Som et eksempel, følgende Java-kode

 println ("Navn:" + navn + ", Avdeling:" + avd.); 

kan erstattes av den kortere, men likeverdige Kotlin-koden.

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

Forlenger og implementerer

Java-programmerere vet at en klasse kan forlenge en annen klasse og implementere ett eller flere grensesnitt. I Kotlin er det ingen syntaktisk forskjell mellom disse to like begrepene; Kotlin bruker et kolon for begge. For eksempel Java-koden

 offentlig klasse Student utvider Personredskaper Sammenlignelig 

ville blitt skrevet enklere i Kotlin som følger:

 klasse Student: Person, sammenlignbar 

Ingen avmerkede unntak

Kotlin støtter unntak på en måte som ligner Java med en stor forskjell - Kotlin har ikke sjekket unntak. Mens de var velmenende, har Java's sjekket unntak blitt mye kritisert. Du kan fortsatt kaste og å fange unntak, men Kotlin-kompilatoren tvinger deg ikke til å fange noen av dem.

Destrukturer

Tenker på destrukturer som en enkel måte å bryte opp et objekt i dets bestanddeler. En destruktureringserklæring oppretter flere variabler samtidig. Listing 3 nedenfor gir et par eksempler. For det første eksemplet, anta variabelen student er en forekomst av klasse Student, som er definert i liste 12 nedenfor. Det andre eksemplet er hentet direkte fra Kotlin-dokumentasjonen.

Oppføring 3. Destruktureringseksempler

 val (_, lName, fName) = student // trekke ut for- og etternavn fra studentobjekt // understreking betyr at vi ikke trenger student.id for ((nøkkel, verdi) i kart) {// gjør noe med nøkkelen og verdien} 

'if' uttalelser og uttrykk

I Kotlin, hvis kan brukes til kontrollflyt som med Java, men det kan også brukes som et uttrykk. Java's kryptiske ternære operatør (?:) erstattes av det klarere, men noe lengre hvis uttrykk. For eksempel Java-koden

 dobbelt maks = x> = y? x: y 

ville bli skrevet i Kotlin som følger:

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

Kotlin er litt mer ordentlig enn Java i dette tilfellet, men syntaksen er uten tvil mer lesbar.

'når' erstatter 'bytte'

Min minst favorittkontrollstruktur i C-lignende språk er bytte om uttalelse. Kotlin erstatter bytte om uttalelse med en når uttalelse. Oppføring 4 er hentet rett fra Kotlin-dokumentasjonen. Legg merke til det gå i stykker uttalelser er ikke påkrevd, og du kan enkelt inkludere verdiområder.

Oppføring 4. En 'når' uttalelse i Kotlin

 når (x) {i 1..10 -> skriv ut ("x er i området") i validNumbers -> print ("x er valid")! i 10..20 -> print ("x er utenfor området ") annet -> skriv ut (" ingen av de ovennevnte ")} 

Prøv å skrive om oppføring 4 som en tradisjonell C / Java bytte om uttalelse, og du vil få en ide om hvor mye det er bedre med Kotlin når uttalelse. Også, lik som hvis, når kan brukes som et uttrykk. I så fall blir verdien av den fornøyde grenen verdien av det generelle uttrykket.

Bytt uttrykk i Java

Java 12 introduserte bryteruttrykk. I likhet med Kotlins når, Krever ikke Java's bryteruttrykk gå i stykker uttalelser, og de kan brukes som utsagn eller uttrykk. Se "Sløyfe, bytte eller ta en pause? Bestemme og gjenta med utsagn" for mer om bytteuttrykk i Java.

2. Enkelt type system (nesten)

Java har to separate typesystemer, primitive typer og referansetyper (aka objekter). Det er mange grunner til at Java inkluderer to separate typesystemer. Det stemmer faktisk ikke. Som skissert i artikkelen En sak for å beholde primitiver i Java, er det egentlig bare en grunn til primitive typer - ytelse. I likhet med Scala har Kotlin bare ett typesystem, ved at det i hovedsak ikke skilles mellom primitive typer og referansetyper i Kotlin. Kotlin bruker primitive typer når det er mulig, men vil bruke objekter om nødvendig.

Så hvorfor forbehold om "nesten"? Fordi Kotlin også har spesialiserte klasser for å representere matriser av primitive typer uten autoboksing overhead: IntArray, DoubleArray, og så videre. På JVM, DoubleArray er implementert som dobbelt[]. Bruker DoubleArray virkelig gjøre en forskjell? La oss se.

Referanse 1: Matriksmultiplikasjon

Da jeg la saken til grunn for Java-primitiver, viste jeg flere referanseresultater som sammenlignet Java-primitiver, Java-wrapper-klasser og lignende kode på andre språk. En av målene var enkel matrisemultiplikasjon. For å sammenligne Kotlin-ytelse med Java, opprettet jeg to matrisemultiplikasjonsimplementasjoner for Kotlin, en med Array og en bruker Array. Oppføring 5 viser Kotlin-implementeringen ved hjelp av Array.

Oppføring 5. Matrisemultiplikasjon i Kotlin

 moro multiplisere (a: Array, b: Array): Array {if (! checkArgs (a, b)) throw Unntak ("Matriser er ikke kompatible for multiplikasjon") val nRows = a.størrelse val nCols = b [0]. størrelse val resultat = Array (nRows, {_ -> DoubleArray (nCols, {_ -> 0.0})} for (rowNum i 0 til nRows) {for (colNum i 0 til nCols) {var sum = 0.0 for (i i 0 til en [0] .størrelse) sum + = a [rowNum] [i] * b [i] [colNum] result [rowNum] [colNum] = sum}} return result} 

Deretter sammenlignet jeg ytelsen til de to Kotlin-versjonene med Java dobbelt og Java med Dobbelt, kjører alle fire referanseverdiene på den nåværende bærbare datamaskinen min. Siden det er en liten mengde "støy" i å kjøre hver referanseindeks, kjørte jeg alle versjonene tre ganger og gjennomsnittet resultatene, som er oppsummert i tabell 1.

Tabell 1. Kjøretidsytelse for referanseindeks for matriksmultiplikasjon

Tidsbestemte resultater (i sekunder)
Java

(dobbelt)

Java

(Dobbelt)

Kotlin

(DoubleArray)

Kotlin

(Array)

7.3029.836.8115.82

Jeg ble litt overrasket over disse resultatene, og jeg tegner to takeaways. Først Kotlin ytelse ved hjelp av DoubleArray er klart overlegen Kotlin-ytelsen ved hjelp av Array, som er klart bedre enn Java ved hjelp av wrapper-klassen Dobbelt. Og for det andre, Kotlin-ytelse med DoubleArray er sammenlignbar med - og i dette eksemplet litt bedre enn - Java-ytelse ved hjelp av den primitive typen dobbelt.

Det er tydelig at Kotlin har gjort en god jobb med å optimalisere behovet for separate typesystemer - med unntak av behovet for å bruke klasser som DoubleArray i stedet for Array.

Referanse 2: SciMark 2.0

Artikkelen min om primitiver inkluderte også en andre, mer vitenskapelig referanseindeks kjent som SciMark 2.0, som er en Java-referanse for vitenskapelig og numerisk databehandling tilgjengelig fra National Institute of Standards and Technology (NIST). SciMark-referansen måler ytelsen til flere beregningsrutiner og rapporterer en sammensatt score omtrent Mflops (millioner av flytende punktoperasjoner per sekund). Dermed er større tall bedre for denne referanseindeksen.

Ved hjelp av IntelliJ IDEA konverterte jeg Java-versjonen av SciMark-referansen til Kotlin. IntelliJ IDEA konverteres automatisk dobbelt[] og int [] i Java til DoubleArray og IntArray i Kotlin. Jeg sammenlignet deretter Java-versjonen ved hjelp av primitiver med Kotlin-versjonen ved hjelp av DoubleArray og IntArray. Som før kjørte jeg begge versjonene tre ganger og gjennomsnittet resultatene, som er oppsummert i tabell 2. Tabellen viser nok en gang omtrent sammenlignbare resultater.

Tabell 2. Runtime-ytelse for SciMark-referansen

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