Lekce 3 - Typový systém podruhé: Datové typy v Kotlin
V předešlém cvičení, Řešené úlohy k 1.-2. lekci Kotlin, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Nyní se podíváme více zblízka na datové typy a vysvětlíme si, kdy jaký použít. Dnešní lekce bude hodně teoretická, ale o to více bude praktická ta příští. Na konci si vytvoříme pár jednoduchých ukázek.
Kotlin nám poskytuje spoustu datových typů pro ukládání různých dat. Java a další jazyky rozlišovaly kvůli optimalizaci výkonu zda je typ tzv. primitivní nebo složitý a podle toho s ním poté pracovaly. Tento mechanismus sice ušetřil nějaký výkon, ale byl neintuitivní. Kotlin žádné takové rozlišení nedělá a i jednotlivá čísla se vnitřně ukládají úplně stejně jako komplexní objekty.
Celočíselné datové typy
Podívejme se nyní na tabulku všech vestavěných celočíselných
datových typů v Kotlin. Všimněte si typu Int
, který již
známe z minula.
Datový typ | Rozsah | Velikost |
---|---|---|
Byte | -128 až 127 | 8 bitů |
Short | -32768 až 32767 | 16 bitů |
Int | -2147483648 až 2147483647 | 32 bitů |
Long | -263 to 263 - 1 | 64 bitů |
Rozsahy těchto typů si samozřejmě nemusíte pamatovat, pro celá čísla
můžete jednoduše používat vždy typ Int
.
Asi vás napadá otázka, proč máme tolik možných typů pro uložení
čísla. Odpověď je prostá, záleží na jeho velikosti. Čím větší
číslo, tím více spotřebuje paměti. Pro věk uživatele tedy zvolíme
Byte
, protože se jistě nedožije více, než 127
let. Představte si databázi milionu uživatelů nějakého systému, když
zvolíme místo Byte
typ Int
, bude zabírat 4x více
místa. Naopak když budeme mít funkci k výpočtu faktoriálu, stěží nám
bude stačit rozsah typu Int
a použijeme Long
.
Nad výběrem datového typu nemusíte moc přemýšlet a většinou se
používá jednoduše Int
. Typ řešte pouze v případě, když
jsou proměnné v nějakém poli (obecně kolekci) a je jich tedy více, potom
se vyplatí zabývat se paměťovými nároky. Tabulky sem dávám spíše pro
úplnost.
Desetinná čísla
U desetinných čísel je situace poněkud jednodušší, máme na výběr
pouze dva datové typy. Samozřejmě se liší opět v rozsahu hodnoty, dále
však ještě v přesnosti (vlastně počtu des. míst). Double
má
již dle názvu dvojnásobnou přesnost oproti Float
.
Datový typ | Rozsah | Přesnost |
---|---|---|
Float | +-1.4 * 10^−45 až +-3.4 * 1038 | 7 čísel |
Double | +-5.0 * 10^−324 až +-1.7 * 10308 | 15-16 čísel |
Vzhledem k tomu, že desetinná čísla jsou v počítači uložena ve dvojkové soustavě, dochází k určité ztrátě přesnosti. Odchylka je sice téměř zanedbatelná, nicméně když budete programovat např. finanční systém, nepoužívejte tyto dat. typy pro uchování peněz, mohlo by dojít k malým odchylkám.
Typ Double
je pro desetinná čísla výchozí,
Float
je tak nutné blíže specifikovat. Když do
Float
chceme dosadit přímo ve zdrojovém kódu, musíme použít
sufix F
.
val f: Float = 3.14f val d = 2.72
Jako desetinný separátor používáme ve zdrojovém kódu vždy tečku, nehledě na to, jaké máme v operačním systému regionální nastavení.
Další vestavěné datové typy
Podívejme se na další datové typy, které nám Kotlin nabízí:
Datový typ | Rozsah | Velikost/Přesnost |
---|---|---|
Char | U+0000 až U+ffff | 16 bitů |
Boolean | true nebo false | 8 bitů |
Char
Char
nám reprezentuje jeden znak, na rozdíl od typu
String
, který reprezentoval celý řetězec hodnot
Char
. Znaky v Kotlin píšeme do apostrofů:
val c: Char = 'A'
Char
patří v podstatě do celočíselných proměnných
(obsahuje číselný kód znaku), ale přišlo mi logičtější uvést ho
zde.
Boolean
Datový typ Boolean
nabývá dvou hodnot:
true
(pravda)false
(nepravda).
Budeme ho používat zejména tehdy, až se dostaneme k podmínkám. Do
proměnné typu Boolean
lze uložit jak přímo hodnotu
true
/false
, tak i logický výraz. Zkusme si
jednoduchý příklad:
{KOTLIN_CONSOLE}
val b = false
val vyraz = 15 > 5
println(b)
println(vyraz)
{/KOTLIN_CONSOLE}
Výstup programu:
Konzolová aplikace
false
true
Výrazy můžeme psát do závorek. To se hodí v případě, že jich máme
např. několik. Vidíme, že výraz nabývá hodnoty true
(pravda), protože 15
je opravdu větší než 5
. Od
výrazů je to jen krok k podmínkám, na ně se podíváme příště.
String
Datový typ, se kterým se setkáte na každém kroku. Reprezentuje řetězec znaků, či prostě jakýkoliv text. My si ukážeme hlavně důležité metody, které je dobré znát nebo alespoň vědět, že existují.
contains()
,
endsWith()
a startsWith()
Můžeme se jednoduše zeptat, zda řetězec začíná, končí nebo zda
obsahuje určitý podřetězec (substring). Podřetězcem myslíme část
původního řetězce. Všechny tyto metody budou jako parametr brát
samozřejmě podřetězec a vracet hodnoty typu Boolean
(true
/false
). Zatím na výstup neumíme reagovat, ale
pojďme si ho alespoň vypsat:
{KOTLIN_CONSOLE}
val s = "Krokonosohroch"
println(s.startsWith("krok"))
println(s.endsWith("hroch"))
println(s.contains("nos"))
println(s.contains("roh"))
{/KOTLIN_CONSOLE}
Výstup programu:
Konzolová aplikace
false
true
true
false
Vidíme, že vše funguje podle očekávání. První výraz samozřejmě neprošel díky tomu, že řetězec ve skutečnosti začíná velkým písmenem.
toUpperCase()
a
toLowerCase()
Rozlišování velkých a malých písmen může být někdy na obtíž.
Mnohdy se budeme potřebovat zeptat na přítomnost podřetězce tak, aby
nezáleželo na velikosti písmen. Situaci můžeme vyřešit pomocí metod
toUpperCase()
a toLowerCase()
, které vrací řetězec
ve velkých a v malých písmenech. Uveďme si reálnější příklad než je
Krokonosohroch. Budeme mít v proměnné řádek konfiguračního souboru,
který psal uživatel. Jelikož se na vstupy od uživatelů nelze spolehnout,
musíme se snažit eliminovat možné chyby, zde např. s velkými písmeny.
{KOTLIN_CONSOLE}
var konfig = "Fullscreen shaDows autosave"
konfig = konfig.toLowerCase()
println("Poběží hra ve fullscreenu?")
println(konfig.contains("fullscreen"))
println("Budou zapnuté stíny?")
println(konfig.contains("shadows"))
println("Přeje si hráč vypnout zvuk?")
println(konfig.contains("nosound"))
println("Přeje si hráč hru automaticky ukládat?")
println(konfig.contains("autosave"))
{/KOTLIN_CONSOLE}
Výstup programu:
Konzolová aplikace
Poběží hra ve fullscreenu?
true
Budou zapnuté stíny?
true
Přeje si hráč vypnout zvuk?
false
Přeje si hráč hru automaticky ukládat?
true
Vidíme, že jsme schopni zjistit přítomnost jednotlivých slov v řetězci tak, že si nejprve řetězec převedeme celý na malá písmena (nebo na velká) a potom kontrolujeme přítomnost slova jen malými (nebo velkými) písmeny. Takhle by mimochodem mohlo opravdu vypadat jednoduché zpracování nějakého konfiguračního skriptu.
replace()
Asi nejdůležitější metodou na String
je nahrazení určité
jeho části jiným textem. Jako parametry zadáme dva podřetězce, jeden co
chceme nahrazovat a druhý ten, kterým to chceme nahradit. Metoda vrátí nový
String
, ve kterém proběhlo nahrazení. Když daný podřetězec
metoda nenajde, vrátí původní řetězec. Zkusme si to:
var s = "Java je nejlepší!" s = s.replace("Java", "Kotlin") println(s)
Výstup programu:
Konzolová aplikace
Kotlin je nejlepší!
trim()
Další nástrahou mohou být mezery a obecně všechny tzv. bílé znaky,
které nejsou vidět, ale mohou nám v aplikaci způsobovat problémy. Obecně
může být dobré trimovat všechny vstupy od uživatele. Zkuste si v
následující aplikaci před číslo a za číslo zadat několik mezer,
trim()
je odstraní. Odstraňují se vždy bílé znaky kolem
řetězce, nikoli uvnitř:
{KOTLIN_CONSOLE}
println("Zadej číslo:")
val s = readLine()!!
println("Zadal jsi text: $s")
println("Text po funkci trim: " + s.trim())
val a = s.trim().toInt()
println("Převedl jsem zadaný text na číslo parsováním, zadal jsi: $a")
{/KOTLIN_CONSOLE}
Zkuste program spustit a zadat před číslo několik mezer:
Zadej číslo: 10 Zadal jsi text: 10 Text po funkci trim: 10 Převedl jsem zadaný text na číslo parsováním, zadal jsi: 10
Řetězcové šablony
Řetězcové šablony, někdy označované jako tzv. interpolace řetězců,
jsme si již zmiňovali. Pokud chceme do řetězce v Kotlin na určitá místa
vložit nějaké proměnné, zapíšeme je přímo do řetězce pomocí značek
$
.
{KOTLIN_CONSOLE}
val a = 10
val b = 20
val c = a + b
println("Když sečteme $a a $b, dostaneme $c")
{/KOTLIN_CONSOLE}
Výstup programu:
Konzolová aplikace
Když sečteme 10 a 20, dostaneme 30
Komplikovanější výrazy vkládáme ještě do složených závorek za znak
dolaru jako ${a + b}
. Například, pokud chceme sečíst dvě
čísla a ihned tento výsledek vypsat. Když se zamyslíme, jak jinak by měl
Kotlin poznat, že chceme sečíst dvě proměnné, kdybychom napsali pouze
$
před jednu proměnnou?
Takováto funkcionalita se nám často hodí při výpisu. Zkusme si rovnou i sečíst čísla přímo ve výrazu:
{KOTLIN_CONSOLE}
val a = 10
val b = 20
println("Když sečteme $a a $b, dostaneme ${a + b}")
{/KOTLIN_CONSOLE}
Toto je velmi užitečná a přehledná cesta, jak sestavovat řetězce.
Pokud chceme vypsat čísla, tak se ji nevyhneme. Pro sloučení proměnných
obsahující řetězce s jiným řetězcem můžeme použít tzv. konkatenaci
(jednoduše +
pro jejich spojení do jednoho řetězce).
val jmeno = "Bill" val prijmeni = "Gates" val celeJmeno = jmeno + " " + prijmeni
Vlastnost length
Poslední, ale nejdůležitější vlastnost (pozor, ne metoda) je
length
, tedy počet znaků. Vrací celé číslo, které
představuje počet znaků v řetězci. Za vlastnosti nepíšeme závorky,
protože nemají parametry.
{KOTLIN_CONSOLE}
println("Zadejte vaše jméno:")
val jmeno = readLine()!!
println("Délka vašeho jména je: ${jmeno.length}")
{/KOTLIN_CONSOLE}
Je toho ještě spoustu k vysvětlování a jsou další datové typy, které jsme neprobrali. Teorie ovšem bylo prozatím dost.
V následujícím kvízu, Kvíz - Proměnné a parsování v Kotlin, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.