Programování

Proč Kotlin? Osm funkcí, které by mohly přesvědčit vývojáře Javy k přechodu

Oficiálně vydán v roce 2016, Kotlin v posledních letech přitahoval velkou pozornost, zejména proto, že Google oznámil podporu Kotlin jako alternativu k Javě na platformách Android. S nedávno oznámeným rozhodnutím učinit Kotlin preferovaným jazykem pro Android si možná budete klást otázku, jestli je čas začít se učit nový programovací jazyk. Pokud je to váš případ, může vám tento článek pomoci rozhodnout se.

Historie vydání Kotlina

Kotlin byl oznámen v roce 2011, ale první stabilní vydání, verze 1.0, se objevilo až v roce 2016. Tento jazyk je bezplatný a otevřený, vyvinutý společností JetBrains a jejím hlavním vedoucím návrhářem jazyků je Andrey Breslav. Kotlin 1.3.40 byl vydán v červnu 2019.

O Kotlinovi

Kotlin je moderní, staticky napsaný programovací jazyk, který obsahuje jak objektově orientované, tak funkční programovací konstrukce. Zaměřuje se na několik platforem, včetně JVM, a je plně interoperabilní s Javou. V mnoha ohledech je Kotlin tím, jak by Java mohla vypadat, kdyby byla navržena dnes. V tomto článku představuji osm funkcí Kotlin, které, jak věřím, vývojáři Java budou nadšeni objevovat.

  1. Čistá a kompaktní syntaxe
  2. Systém jednoho typu (téměř)
  3. Nulová bezpečnost
  4. Funkce a funkční programování
  5. Datové třídy
  6. Rozšíření
  7. Přetížení obsluhy
  8. Objekty nejvyšší úrovně a vzor Singleton

Ahoj světe! Kotlin versus Java

Výpis 1 zobrazuje povinné „Ahoj, světe!“ funkce napsaná v Kotlin.

Výpis 1. „Ahoj, svět!“ v Kotlinu

 fun main () {println ("Hello, world!")} 

Jakkoli je tento příklad jednoduchý, odhaluje klíčové rozdíly od Javy.

  1. hlavní je funkce nejvyšší úrovně; to znamená, že funkce Kotlin nemusí být vnořeny do třídy.
  2. Nejsou k dispozici žádné veřejná statika modifikátory. Zatímco Kotlin má modifikátory viditelnosti, výchozí je veřejnost a lze jej vynechat. Kotlin také nepodporuje statický modifikátor, ale v tomto případě to není nutné, protože hlavní je funkce nejvyšší úrovně.
  3. Od verze Kotlin 1.3 je parametr pole řetězců pro hlavní není vyžadováno a pokud není použito, může být vynecháno. V případě potřeby by to bylo deklarováno jako args: Pole.
  4. Pro funkci není zadán žádný návratový typ. Kde Java používá prázdnota, Kotlin používá Jednotka, a pokud je návratový typ funkce Jednotka, může být vynechán.
  5. V této funkci nejsou žádné středníky. V Kotlinu jsou středníky volitelné, a proto jsou zalomení řádků významné.

To je přehled, ale je toho mnohem víc, co se dozvíte o tom, jak se Kotlin liší od Javy a v mnoha případech ji vylepšuje.

1. Čistší a kompaktnější syntaxe

Java je často kritizována za to, že je příliš podrobná, ale některá výřečnost může být vaším přítelem, zvláště pokud je zdrojový kód srozumitelnější. Výzvou v jazykovém designu je snížit výřečnost při zachování jasnosti a myslím si, že Kotlin jde dlouhou cestou ke splnění této výzvy.

Jak jste viděli v seznamu 1, Kotlin nevyžaduje středníky a umožňuje vynechat návratový typ pro Jednotka funkce. Zvažme několik dalších funkcí, díky nimž je Kotlin čistší a kompaktnější alternativou k Javě.

Odvození typu

V Kotlin můžete deklarovat proměnnou jako var x: Int = 5, nebo můžete použít kratší, ale stejně jasnou verzi var x = 5. (Zatímco Java nyní podporuje var tato funkce se objevila až v Javě 10, dlouho poté, co se funkce objevila v Kotlin.)

Kotlin také má val deklarace pro proměnné jen pro čtení, které jsou analogické s proměnnými Javy, které byly deklarovány jako finále, což znamená, že proměnnou nelze přiřadit. Výpis 2 uvádí příklad.

Výpis 2. Proměnné jen pro čtení v Kotlin

 val x = 5 ... x = 6 // CHYBA: NEZPRACUJE se 

Vlastnosti versus pole

Tam, kde má Java pole, má Kotlin vlastnosti. Vlastnosti jsou deklarovány a je k nim přistupováno podobným způsobem jako k veřejným polím v Javě, ale Kotlin poskytuje výchozí implementace funkcí accessor / mutator pro vlastnosti; to znamená, že Kotlin poskytuje dostat() funkce pro val vlastnosti a obojí dostat() a soubor() funkce pro var vlastnosti. Přizpůsobené verze dostat() a soubor() lze v případě potřeby implementovat.

Většina vlastností v Kotlin bude mít záložní pole, ale je možné definovat a vypočítaná vlastnost, což je v zásadě a dostat() funkce bez doprovodného pole. Například třída představující osobu může mít vlastnost pro datum narození a vypočítaná vlastnost pro stáří.

Výchozí a explicitní importy

Java implicitně importuje třídy definované v balíčku java.lang, ale všechny ostatní třídy musí být explicitně importovány. Ve výsledku mnoho zdrojových souborů Java začíná importem tříd kolekce z java.util, I / O třídy z java.io, a tak dále. Ve výchozím nastavení Kotlin implicitně importuje kotlin. *, což je zhruba analogické s importem Java java.lang. *, ale Kotlin také dováží kotlin.io. *, kotlin.collections. *a třídy z několika dalších balíčků. Z tohoto důvodu zdrojové soubory Kotlin obvykle vyžadují méně explicitních importů než zdrojové soubory Java, zejména pro třídy, které používají kolekce nebo standardní I / O.

Žádné volání „nového“ pro konstruktéry

V Kotlin, klíčové slovo Nový není nutné k vytvoření nového objektu. Chcete-li volat konstruktor, stačí použít název třídy se závorkami. Kód Java

 Student s = nový Student (...); // nebo var s = new Student (...); 

lze v Kotlinu napsat takto:

 var s = Student (...) 

Řetězcové šablony

Řetězce mohou obsahovat výrazy šablony, což jsou výrazy, které jsou vyhodnoceny s výsledky vloženými do řetězce. Výraz šablony začíná znakem dolaru ($) a skládá se buď z jednoduchého názvu, nebo z libovolného výrazu ve složených závorkách. Řetězcové šablony mohou zkrátit řetězcové výrazy snížením potřeby explicitního zřetězení řetězců. Jako příklad následující kód Java

 println ("Jméno:" + jméno + ", Oddělení:" + odd.); 

lze nahradit kratším, ale ekvivalentním Kotlinovým kódem.

 println ("Jméno: $ name, Oddělení: $ odd") 

Rozšiřuje a implementuje

Programátoři Java vědí, že třída může rozšířit další třída a nářadí jedno nebo více rozhraní. V Kotlinu neexistuje žádný syntaktický rozdíl mezi těmito dvěma podobnými koncepty; Kotlin používá dvojtečku pro oba. Například kód Java

 veřejná třída Student rozšiřuje Person implementuje Srovnatelné 

by bylo napsáno jednodušeji v Kotlin takto:

 třída Student: Osoba, srovnatelná 

Žádné zaškrtnuté výjimky

Kotlin podporuje výjimky podobným způsobem jako Java s jedním velkým rozdílem - Kotlin nemá zaškrtnuté výjimky. I když byly dobře zamýšleny, kontrolované výjimky Java byly široce kritizovány. Stále můžeš házet a chytit výjimky, ale kompilátor Kotlin vás nenutí chytit žádnou z nich.

Destrukturalizace

Myslet na ničení jako jednoduchý způsob rozbití objektu na jeho jednotlivé části. Deklarace destrukce vytvoří více proměnných najednou. Výpis 3 níže poskytuje několik příkladů. U prvního příkladu předpokládejme tuto proměnnou student je instance třídy Student, který je definován v seznamu 12 níže. Druhý příklad je převzat přímo z dokumentace Kotlin.

Výpis 3. Příklady restrukturalizace

 val (_, lName, fName) = student // extrahovat jméno a příjmení ze studentského objektu // podtržítko znamená, že nepotřebujeme student.id pro ((klíč, hodnota) v mapě) {// něco udělat s klíčem a hodnota} 

výroky a výrazy „pokud“

V Kotlin, -li lze použít pro tok řízení jako v Javě, ale lze jej také použít jako výraz. Tajemný ternární operátor Java (?:) je nahrazen jasnějším, ale o něco delším -li výraz. Například kód Java

 dvojnásobek max = x> = y? x: y 

bude napsáno v Kotlin takto:

val max = if (x> = y), pak x else y 

Kotlin je v tomto případě o něco podrobnější než Java, ale syntaxe je pravděpodobně čitelnější.

'když' nahradí 'spínač'

Moje nejméně oblíbená kontrolní struktura v jazycích podobných C je přepínač prohlášení. Kotlin nahrazuje přepínač prohlášení s a když prohlášení. Výpis 4 je převzat přímo z dokumentace Kotlin. Všimněte si toho přestávka příkazy nejsou povinné a můžete snadno zahrnout rozsahy hodnot.

Výpis 4. Prohlášení „když“ v Kotlinu

 when (x) {in 1..10 -> print ("x is in the range") in validNumbers -> print ("x is valid")! in 10..20 -> print ("x is outside the range ") else -> print (" nic z výše uvedeného ")} 

Zkuste přepsat výpis 4 jako tradiční C / Java přepínač prohlášení a získáte představu o tom, jak je nám u Kotlinových lépe když prohlášení. Podobně jako -li, když lze použít jako výraz. V takovém případě se hodnota spokojené větve stane hodnotou celkového výrazu.

Přepínání výrazů v Javě

Java 12 představila výrazy přepínačů. Podobně jako Kotlin když„Přepínací výrazy Java nevyžadují přestávka výroky a lze je použít jako výroky nebo výrazy. Další informace o výrazech přepínačů v Javě najdete v části „Smyčka, přepnutí nebo pauza? Rozhodování a iterace s příkazy“.

2. Systém jednoho typu (téměř)

Java má dva samostatné systémy typů, primitivní typy a referenční typy (neboli objekty). Existuje mnoho důvodů, proč Java obsahuje dva samostatné typy systémů. Ve skutečnosti to není pravda. Jak je uvedeno v mém článku Případ pro udržování primitiv v Javě, pro primitivní typy existuje opravdu jen jeden důvod - výkon. Podobně jako Scala, Kotlin má pouze jeden typový systém v tom, že v zásadě neexistuje žádný rozdíl mezi primitivními typy a referenčními typy v Kotlin. Kotlin používá primitivní typy, pokud je to možné, ale v případě potřeby použije objekty.

Proč tedy námitka „téměř“? Protože Kotlin má také specializované třídy, které představují pole primitivních typů bez režie autoboxingu: IntArray, DoubleArray, a tak dále. Na JVM DoubleArray je implementován jako dvojnásobek[]. Používá DoubleArray opravdu změnit? Uvidíme.

Benchmark 1: Násobení matic

Při vytváření případu pro primitivy Java jsem ukázal několik srovnávacích výsledků porovnávajících primitivy Java, třídy obálky Java a podobný kód v jiných jazycích. Jedním z měřítek bylo jednoduché násobení matic. Pro porovnání výkonu Kotlin s Javou jsem vytvořil dvě implementace násobení matic pro Kotlin, jednu s použitím Pole a jeden pomocí Pole. Výpis 5 ukazuje implementaci Kotlin pomocí Pole.

Výpis 5. Násobení matic v Kotlin

 fun multiply (a: Array, b: Array): Array {if (! checkArgs (a, b)) throw Exception ("Matrix are not compatible for multiplication") val nRows = a.size val nCols = b [0]. size val result = Array (nRows, {_ -> DoubleArray (nCols, {_ -> 0.0})}) for (rowNum in 0 until nRows) {for (colNum in 0 until nCols) {var sum = 0.0 for (i v 0 do a [0] .size) sum + = a [rowNum] [i] * b [i] [colNum] výsledek [rowNum] [colNum] = sum}} návratový výsledek} 

Dále jsem porovnal výkon dvou verzí Kotlin s výkonem Java dvojnásobek a Java s Dvojnásobek, běží všechny čtyři měřítka na mém aktuálním notebooku. Vzhledem k tomu, že při spouštění každého benchmarku existuje malé množství „šumu“, spustil jsem všechny verze třikrát a zprůměroval jsem výsledky, které jsou shrnuty v tabulce 1.

Tabulka 1. Runtime výkon maticového násobení

Časované výsledky (v sekundách)
Jáva

(dvojnásobek)

Jáva

(Dvojnásobek)

Kotlin

(DoubleArray)

Kotlin

(Pole)

7.3029.836.8115.82

Tyto výsledky mě poněkud překvapily a nakreslil jsem dvě jídla. Nejprve výkon Kotlin pomocí DoubleArray je jasně lepší než výkon Kotlin Pole, což je zjevně lepší než u Javy pomocí třídy wrapper Dvojnásobek. A za druhé, výkon Kotlin pomocí DoubleArray je srovnatelný - a v tomto příkladě o něco lepší než - výkon Java pomocí primitivního typu dvojnásobek.

Je zřejmé, že Kotlin odvedl skvělou práci při optimalizaci potřeby samostatných typů systémů - s výjimkou nutnosti používat třídy jako DoubleArray namísto Pole.

Benchmark 2: SciMark 2.0

Můj článek o primitivech také zahrnoval druhý, více vědecký benchmark známý jako SciMark 2.0, což je Java benchmark pro vědecké a numerické výpočty dostupný od National Institute of Standards and Technology (NIST). Benchmark SciMark měří výkon několika výpočetních rutin a přibližně uvádí složené skóre Mflops (miliony operací s plovoucí desetinnou čárkou za sekundu). Větší čísla jsou tedy pro tuto referenční hodnotu lepší.

S pomocí IntelliJ IDEA jsem převedl Java verzi benchmarku SciMark na Kotlin. IntelliJ IDEA se automaticky převede dvojnásobek[] a int [] v Javě do DoubleArray a IntArray v Kotlinu. Pak jsem porovnal verzi Java pomocí primitiv s verzí Kotlin pomocí DoubleArray a IntArray. Stejně jako dříve jsem obě verze běžel třikrát a zprůměroval jsem výsledky, které jsou shrnuty v tabulce 2. Tabulka opět ukazuje zhruba srovnatelné výsledky.

Tabulka 2. Výkon za běhu benchmarku SciMark

Výkon (v Mflops)
JávaKotlin
1818.221815.78