Podstawowe typy Kotlina, ich literały i operacje
To jest rozdział z książki Kotlin Essentials.
Każdy język potrzebuje wygodnego sposobu reprezentowania podstawowych rodzajów wartości, takich jak liczby czy znaki. Wszystkie języki muszą mieć wbudowane typy i literały. Typy są używane do reprezentowania pewnych rodzajów wartości. Przykłady typów to Int
, Boolean
czy String
. Literały to wbudowane notacje, które są używane do tworzenia instancji tych typów. Przykłady literałów to literał stringa, czyli po prostu tekst w cudzysłowie, czy literał całkowitoliczbowy, czyli zwykła liczba całkowita.
W tym rozdziale poznamy podstawowe typy języka Kotlin i ich literały:
- liczby (
Int
,Long
,Double
,Float
,Short
,Byte
), - wartości logiczne (
Boolean
), - znaki (
Char
), - stringi(
String
).
W Kotlinie istnieją także tablice, które zostaną omówione w rozdziale Kolekcje.
W Kotlinie wszystkie wartości są traktowane jako obiekty (nie ma typów prymitywnych), więc wszystkie mają metody, a ich typy mogą być używane jako argumenty typów generycznych (to zostanie omówione w rozdziale Typy generyczne). Typy reprezentujące liczby, wartości logiczne i znaki mogą być zoptymalizowane przez kompilator Kotlina i używane jako typy prymitywne, ale ta optymalizacja nie ma wpływu na to, jak wygląda nasz kod, dlatego nie musisz nawet o tym myśleć.
Zacznijmy jeden po drugim omawiać podstawowe typy i literały w języku Kotlin.
Liczby
W Kotlinie istnieje szereg typów służących do reprezentowania liczb. Mogą one być podzielone na te reprezentujące liczby całkowite (bez części dziesiętnej) oraz te reprezentujące liczby zmiennoprzecinkowe (z częścią dziesiętną). W tych grupach różnica polega na liczbie bitów używanych do reprezentowania tych liczb, co determinuje możliwy rozmiar liczby i precyzję.
Do reprezentacji liczb całkowitych, używamy Int
, Long
, Byte
i Short
.
Typ | Rozmiar (bity) | Wartość minimalna | Wartość maksymalna |
---|---|---|---|
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | ||
Long | 64 |
Natomiast do reprezentacji liczb zmiennoprzecinkowych, używamy Float
i Double
.
Typ | Rozmiar (bity) | Bity znaczące | Bity wykładnika | Cyfry dziesiętne |
---|---|---|---|---|
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
Zwykła liczba bez kropki dziesiętnej jest interpretowana jako Int
. Zwykła liczba z kropką dziesiętną jest interpretowana jako Double
.
Możesz utworzyć Long
, dodając sufiks L
po liczbie. Long
jest również używany dla literałów liczbowych, które są zbyt duże dla Int
.
Podobnie, możesz utworzyć Float
, kończąc liczbę sufiksem F
lub f
.
Nie ma sufiksu do tworzenia typów Byte
ani Short
. Jednak liczba wyraźnie określona jako jeden z tych typów stworzy instancję tego typu. To samo dotyczy Long
.
To nie jest konwersja! Kotlin nie obsługuje niejawnej konwersji typów, więc nie można użyć Byte
ani Long
, gdzie oczekiwany jest Int
.
Jeśli musimy przekształcić jedną liczbę na inny typ, używamy funkcji konwersji, takich jak toInt
lub toLong
.
Podkreślenia w liczbach
W literałach liczbowych możemy użyć podkreślenia _
pomiędzy cyframi. Ten znak jest ignorowany, ale używamy go do formatowania długich liczb dla lepszej czytelności.
Inne systemy liczbowe
Aby zdefiniować liczbę w systemie szesnastkowym, zacznij od 0x
. Aby zdefiniować liczbę w systemie binarnym, zacznij od 0b
. System ósemkowy nie jest obsługiwany.
Number
i funkcje konwersji
Wszystkie podstawowe typy reprezentujące liczby należą do podtypów klasy Number
.
Klasa abstrakcyjna Number
określa funkcje przekształcające z bieżącej liczby na dowolny inny podstawowy typ reprezentujący liczbę.
Oznacza to, że dla każdej podstawowej liczby można przekształcić ją na inną podstawową liczbę za pomocą funkcji to{new type}
. Takie funkcje są znane jako funkcje konwersji.
Operacje na liczbach
Kotlin obsługuje podstawowe operacje matematyczne na liczbach:
- dodawanie (
+
), - odejmowanie (
-
), - mnożenie (
*
), - dzielenie (
/
).
Zauważ, że poprawny wynik
1.4 / 2.5
powinien wynosić0.56
, a nie0.5599999999999999
. Ten problem zostanie rozwiązany wkrótce.
Gdy dzielimy Int
przez Int
, wynik jest również Int
, więc część dziesiętna jest tracona.
Rozwiązaniem jest najpierw przekształcenie liczby całkowitej na reprezentację zmiennoprzecinkową, a następnie jej podzielenie.
Istnieje również operator reszty z dzielenia1 %
:
Kotlin obsługuje również operacje modyfikujące zmienną var
:
+=
, gdziea += b
jest równoznaczne za = a + b
,-=
, gdziea -= b
jest równoznaczne za = a - b
,*=
, gdziea *= b
jest równoznaczne za = a * b
,/=
, gdziea /= b
jest równoznaczne za = a / b
,%=
, gdziea %= b
jest równoznaczne za = a % b
,- post-inkrementacja i pre-inkrementacja
++
, które zwiększają wartość zmiennej o1
, - post-dekrementacja i pre-dekrementacja
--
, które zmniejszają wartość zmiennej o1
.
Operacje na bitach
Kotlin obsługuje również operacje na bitach za pomocą następujących metod, które można wywoływać za pomocą notacji infiksowej (czyli między dwiema wartościami):
and
zachowuje tylko bity, które mają1
w tych samych pozycjach binarnych w obu liczbach.or
zachowuje tylko bity, które mają1
w tych samych pozycjach binarnych w jednej lub obu liczbach.xor
zachowuje tylko bity, które mają dokładnie jedno1
w tych samych pozycjach binarnych w obu liczbach.shl
przesuwa wartość po lewej stronie w lewo o prawą ilość bitów.shr
przesuwa wartość po lewej stronie w prawo o prawą ilość bitów.
BigDecimal
i BigInteger
Wszystkie podstawowe typy w Kotlinie mają ograniczony rozmiar i precyzję, co może prowadzić do nieprecyzyjnych lub błędnych wyników w specyficznych sytuacjach.
Zrozumienie dlaczego wyniki są takie, a nie inne wychodzi poza zakres tej książki, ale dla zainteresowanych spieszę z wyjaśnieniem. W pierwszym przypadku wynik jest nieprecyzyjny, ponieważ liczby zmiennoprzecinkowe są reprezentowane w postaci binarnej, a nie dziesiętnej. W drugim przypadku wynik jest niepoprawny, ponieważ liczba całkowita jest reprezentowana przez 32 bity, a więc 2147483647 to największa możliwa liczba, a dodanie 1 powoduje jej przepełnienie i zmianę bitu reprezentującego znak na przeciwny.
To standardowy kompromis w programowaniu, z którym w większości języków musimy się pogodzić. Jednak są przypadki, gdy potrzebujemy mieć doskonałą precyzję i nieograniczony rozmiar liczby. Na JVM, dla nieograniczonego rozmiaru liczby powinniśmy użyć BigInteger
, który reprezentuje liczbę bez części dziesiętnej. Dla nieograniczonego rozmiaru i precyzji powinniśmy użyć BigDecimal
, który reprezentuje liczbę mającą część dziesiętną. Obiekty obu typów można utworzyć za pomocą konstruktorów2, funkcji fabrycznych (takich jak valueOf
) lub konwersji z podstawowych typów reprezentujących liczby (metody toBigDecimal
i toBigInteger
). Przykłady poniżej.
BigDecimal
i BigInteger
obsługują również podstawowe operatory matematyczne:
Na platformach innych niż Kotlin/JVM do reprezentowania liczb o nieograniczonym rozmiarze i precyzji potrzebne są zewnętrzne biblioteki.
Wartości logiczne
Innym podstawowym typem jest Boolean
, który ma dwie możliwe wartości: true
i false
.
Używamy wartości logicznych do wyrażania odpowiedzi tak/nie, na przykład:
- Czy użytkownik jest administratorem?
- Czy użytkownik zaakceptował politykę plików cookie?
- Czy dwie liczby są identyczne?
W praktyce wartości logiczne są często wynikiem pewnego rodzaju porównania.
Porównania
Wartość Boolean
często jest wynikiem porównania dwóch równości. W Kotlinie sprawdzamy czy obiekty są równe, używając podwójnego znaku równości ==
. Aby sprawdzić, czy dwa obiekty nie są równe, używamy znaku nierówności !=
.
Liczby i wszystkie obiekty, które można porównać (tzn. implementują interfejs Comparable
), można również porównać za pomocą znaków >
, <
, >=
i <=
.
Operacje logiczne
W Kotlinie mamy trzy podstawowe operatory logiczne:
- and
&&
, który zwracatrue
, gdy wartości po obu jego stronach sątrue
; w przeciwnym razie zwracafalse
. - or
||
, który zwracatrue
, gdy wartość po którejkolwiek ze swoich stron jesttrue
; w przeciwnym razie zwracafalse
. - not
!
, który zamieniatrue
nafalse
ifalse
natrue
.
Kotlin nie obsługuje żadnego rodzaju automatycznej konwersji na Boolean
(ani żaden inny typ), więc operatorów logicznych należy używać tylko z obiektami typu Boolean
.
Znaki
Aby reprezentować pojedynczy znak, używamy typu Char
. Znak określamy za pomocą apostrofów.
Każdy znak jest reprezentowany jako liczba Unicode. Aby dowiedzieć się, jaka jest wartość Unicode danego znaku, użyj właściwości code
.
Kotlin akceptuje znaki Unicode. Aby opisać je za pomocą ich kodu, zaczynamy od \u
, a następnie musimy użyć formatu szesnastkowego (podobnie jak w Javie).
Stringi
Stringi to po prostu sekwencje znaków tworzące tekst. W Kotlinie tworzymy stringa, używając cudzysłowów "
lub potrójnych cudzysłowów """
.
String otoczony pojedynczymi cudzysłowami wymaga tekstu w jednym wierszu. Jeśli chcemy zdefiniować znak nowej linii, musimy użyć specjalnego znaku \n
. To nie jedyna rzecz, która potrzebuje (lub może potrzebować) ukośnika wstecznego do wyrażenia w ciągu znaków.
Sekwencja ucieczki | Znaczenie |
---|---|
\t | Tabulator |
\b | Cofnięcie |
\r | Powrót karetki |
\f | Przewijanie formularza |
\n | Nowa linia |
\' | Pojedynczy cudzysłów |
\" | Cudzysłów |
\\ | Ukośnik wsteczny |
\$ | Dolar |
Ciągi znaków w potrójnych cudzysłowach mogą być wielowierszowe; w tych ciągach można bezpośrednio używać znaków specjalnych, a formy poprzedzone ukośnikiem wstecznym nie działają.
Aby lepiej sformatować ciągi znaków w potrójnych cudzysłowach, używamy funkcji trimIndent
, która ignoruje stałą liczbę spacji dla każdej linii.
Ciągi znaków mogą zawierać wyrażenia szablonowe, czyli odniesienia do zmiennych, których wartości zostaną podstawione do stringa. Wyrażenie szablonowe zaczyna się od znaku dolara ($
) i składa się albo z nazwy zmiennej (takiej jak "tekst to $text"
), albo z wyrażenia w nawiasach klamrowych (takiego jak "1 + 2 = ${1 + 2}"
).
Jeśli potrzebujesz użyć specjalnego znaku wewnątrz ciągu znaków otoczonych potrójnymi cudzysłowami, najłatwiejszym sposobem jest zdefiniowanie go za pomocą zwykłego ciągu znaków i dołączenie go przy użyciu składni szablonu.
W ciągach znaków Kotlina używamy Unicode, możemy więc zdefiniować znak Unicode, używając liczby zaczynającej się od \u
, a następnie podając kod znaku Unicode w składni szesnastkowej.
Jeśli pomiędzy dwoma stringami postawimy znak +
, wynikiem będzie string, zawierający kolejno zawartość tamtych dwóch. Możemy też do stringa dodać dowolną wartość, która w wyniku tej operacji zostanie przekształcona na stringa.
Podsumowanie
W tym rozdziale poznaliśmy podstawowe typy używane w języku Kotlin oraz literały używane do ich tworzenia:
- Liczby reprezentowane przez typy
Int
,Long
,Double
,Float
,Short
iByte
są tworzone za pomocą samych wartości liczbowych z możliwością dodania sufiksów dla dostosowania typu. Możemy definiować liczby ujemne oraz części dziesiętne. Możemy również używać podkreślników dla lepszej czytelności liczb. - Wartości logiczne
true
ifalse
są reprezentowane przez typBoolean
. - Znaki, które są reprezentowane przez typ
Char
. Wartość znaku definiujemy za pomocą pojedynczych cudzysłowów. - Ciągi znaków, które służą do reprezentowania tekstu, są reprezentowane przez typ
String
. Każdy ciąg to tylko seria znaków. Ciągi znaków definiujemy wewnątrz podwójnych cudzysłowów.
Mamy więc podstawy do korzystania z Kotlina. Przejdźmy do bardziej skomplikowanych struktur sterujących, które określają, jak zachowuje się nasz kod.
W języku angielskim istnieje rozdzielenie na operację "modulo" oraz "remainder", gdzie różnica między nimi jest w wyniku dla liczb ujemnych. Tutaj poprzez resztę z dzielenia rozumiemy operację określaną w języku angielskim jako "remainder". Tak więc reszta z dzielenia -5 przez 4 to -1, ponieważ -5 = 4 * (-1) + (-1). Wynik -5 modulo 4 to 3, ponieważ -5 = 4 * (-2) + 3. Reszta z dzielenia zawsze ma taki sam znak jak liczba dzielona. Wynik modulo zawsze ma taki sam znak jak liczba, przez którą dzielimy.
Konstruktory zostaną omówione w rozdziale Klasy.