Dane oryginału

Copyright © 2020 by Mindview LLC. Title of English-language original: Atomic Kotlin by Bruce Eckel and Svetlana Isakova, ISBN 978-0-9818725-4-4. Polish-language edition copyright © 2022 by Polish Scientific Publishers PWN Wydawnictwo Naukowe PWN Spółka Akcyjna. All rights reserved.

Przekład: Marek Włodarz na zlecenie WITKOM Witold Sikorski

Projekt okładki polskiego wydania: INT-MEDIA Dawid Mazur

Wydawca: Wioleta Szczygielska-Dybciak, Dorota Siudowska-Mieszkowska

Redaktor prowadzący: Monika Zabrocka-Kutera

Redaktor: Martyna Grębocka

Korekta: Bożena Hałuszczyńska

Koordynator produkcji: Anna Bączkowska

Skład wersji elektronicznej na zlecenie Wydawnictwo Naukowe PWN S.A.: Michał Latusek

Konsultacja merytoryczna:

Jarosław Michalik

Zastrzeżonych nazw firm i produktów użyto w książce wyłącznie w celu identyfikacji.

Książka, którą nabyłeś, jest dziełem twórcy i wydawcy. Prosimy, abyś przestrzegał praw,

jakie im przysługują. Jej zawartość możesz udostępnić nieodpłatnie osobom bliskim

lub osobiście znanym. Ale nie publikuj jej w internecie. Jeśli cytujesz jej fragmenty,

nie zmieniaj ich treści i koniecznie zaznacz, czyje to dzieło. A kopiując jej część, rób to

jedynie na użytek osobisty.

Szanujmy cudzą własność i prawo

Więcej na www.legalnakultura.pl

Polska Izba Książki

Copyright © for the Polish edition by Wydawnictwo Naukowe PWN SA

Warszawa 2022

ISBN 978-83-01-22305-2

eBook został przygotowany na podstawie wydania papierowego z 2022r. (Wydanie I)

Warszawa 2022

Wydawnictwo Naukowe PWN SA

02-460 Warszawa, ul. Gottlieba Daimlera 2

tel. 22 69 54 321, faks 22 69 54 288

infolinia 801 33 33 88

e-mail: pwn@pwn.com.pl, reklama@pwn.pl
www.pwn.pl

Fragment

Copyright

Część I
Podstawy programowania

Wprowadzenie

Dlaczego Kotlin?

Hello, world!

var oraz val

Typy danych

Funkcje

Wyrażenia if

Szablony łańcuchów

Typy numeryczne

Wartości logiczne (Boolean)

Powtarzanie za pomocą while

Pętle i przedziały

Słowo kluczowe in

Wyrażenia i instrukcje

Podsumowanie 1

Część II
Wprowadzenie do obiektów

Wszędzie obiekty

Tworzenie klas

Właściwości

Konstruktory

Ograniczanie widoczności

Testowanie

Wyjątki

Listy

Zmienne listy argumentów

Zbiory

Mapy

Akcesory właściwości

Podsumowanie 2

Część III
Użyteczność

Funkcje rozszerzające

Argumenty nazwane i domyślne

Przeciążanie

Wyrażenia when

Typy wyliczeniowe

Klasy danych

Deklaracje destrukturyzacyjne

Typy nullowalne

Bezpieczne wywołania i operator warunkowy

Asercje nie-null

Rozszerzenia typów nullowalnych

Wprowadzenie do typów generycznych

Właściwości rozszerzające

break i continue

Część IV
Programowanie funkcyjne

Wyrażenia lambda

Ważność wyrażeń lambda

Operacje na kolekcjach

Referencje do elementów członkowskich

Funkcje wyższego poziomu

Manipulowanie listami

Budowanie map

Sekwencje

Funkcje lokalne

Zwijanie list

Rekurencja

Część V
Programowanie obiektowe

Interfejsy

Złożone konstruktory

Konstruktory dodatkowe

Dziedziczenie

Inicjowanie klasy bazowej

Klasy abstrakcyjne

Rzutowanie w górę

Polimorfizm

Kompozycja

Dziedziczenie i rozszerzenia

Delegacja klasy

Rzutowanie w dół

Klasy zapieczętowane

Sprawdzanie typów

Klasy zagnieżdżone

Obiekty

Klasy wewnętrzne

Obiekty towarzyszące

Część VI
Zapobieganie awarii

Obsługa wyjątków

Instrukcje sprawdzające

Typ Nothing

Czyszczenie zasobów

Rejestrowanie

Testy jednostkowe

Część VII
Narzędzia produktywności

Rozszerzające wyrażenia lambda

Funkcje zakresu

Tworzenie typów generycznych

Przeciążanie operatorów

Używanie operatorów

Delegowanie właściwości

Narzędzia delegowania właściwości

Leniwe inicjowanie

Inicjowanie późne

Dodatki

Dodatek A: AtomicTest

Dodatek B: Współdziałanie z Javą

Przypisy

Pozostałe rozdziały dostępne w pełnej wersji e-booka.

Copyright

Atomowy Kotlin

By Bruce Eckel, President, MindView, LLC, and Svetlana Isakova, JetBrains sro.

Copyright © 2021, MindView LLC.

eBook ISBN 978-0-9818725-4-4

Print Book ISBN 978-0-9818725-5-1

Numer ISBN eBooka obejmuje dystrybucje Stepik oraz Leanpub, obie są dostępne poprzez stronę AtomicKotlin.com.

Prosimy o zakup tej książki poprzez stronę www.AtomicKotlin.com, aby wspierać jej ciągłe utrzymywanie i aktualizacje.

Wszelkie prawa zastrzeżone. Wydrukowano w Stanach Zjednoczonych Ameryki. Publikacja ta jest chroniona prawem autorskim i wymagane jest uzyskanie zezwolenia wydawcy przed reprodukowaniem dowolnego rodzaju, zapisaniem w systemie umożliwiającym odtwarzanie albo transmisją w dowolnej formie i za pośrednictwem dowolnego nośnika, elektroniczną, mechaniczną, poprzez fotokopiowanie, nagrywanie lub podobną. Informacje dotyczące uzyskiwania zezwoleń dostępne są na stronie AtomicKotlin.com.

Utworzono w Crested Butte, Colorado, USA i w Monachium, Niemcy. Tekst wydrukowano w Stanach Zjednoczonych

eBook: wersja 1.0, grudzień 2020
Pierwszy druk: styczeń 2021

Projekt okładki Daniel Will-Harris, www.Will-Harris.com[1]

Wiele oznaczeń wykorzystywanych przez producentów i sprzedawców do odróżniania ich produktów jest zadeklarowanych jako znaki towarowe. Ilekroć takie oznaczenia pojawiają się w tej książce, gdy wydawca ma świadomość zastrzeżenia znaku, nazwy takie pisane są wielką literą albo samymi wielkimi literami.

Znak towarowy Kotlin należy do Kotlin Foundation[2]. Java jest znakiem towarowym albo zarejestrowanym znakiem towarowym należącym do Oracle, Inc. w Stanach Zjednoczonych i innych krajach. Windows jest zarejestrowanym znakiem towarowym należącym do Microsoft Corporation w Stanach Zjednoczonych i innych krajach. Wszystkie pozostałe nazwy produktów i firm wspomniane w tej publikacji są własnością odpowiednich właścicieli.

Autorzy i wydawca podjęli starania w celu przygotowania tej książki, jednak nikomu nie udziela się jawnej lub dorozumianej rękojmi lub gwarancji. Autorzy i wydawca nie są w żadnym wypadku odpowiedzialni za jakiekolwiek szkody będące następstwem korzystania z informacji zawartych w niniejszej publikacji.

Zapraszamy na stronę www.AtomicKotlin.com.

Kod źródłowy

Cały kod źródłowy przedstawiony w książce jest dostępny jako darmowe oprogramowanie chronione prawem autorskim, dystrybuowane za pośrednictwem serwisu Github[3], by zagwarantować czytelnikowi, że dysponuje najbardziej aktualną wersją. To oficjalna lokalizacja dystrybucji kodu, którego można używać podczas wykładów i w innych sytuacjach edukacyjnych, jeśli poda się tę książkę jako źródło.

Podstawowym celem zastrzeżenia praw autorskich jest zagwarantowanie właściwego cytowania źródła kodu oraz powstrzymanie przed ponowną publikacją kodu bez zezwolenia. (Jeżeli książka zostanie wskazana jako źródło, używanie przykładów z niej w większości mediów nie będzie stanowić problemu).

W każdym pliku kodu źródłowego można znaleźć odsyłacz do poniższej uwagi o prawach autorskich:

// Copyright.txt

This computer source code is Copyright ©2021 MindView LLC.
All Rights Reserved.

Permission to use, copy, modify, and distribute this computer source code (Source Code) and its documentation without fee and without a written agreement for the purposes set forth below is hereby granted, provided that the above copyright notice, this paragraph and the following five numbered paragraphs appear in all copies.

Permission is granted to compile the Source Code and to include the compiled code, in executable format only, in personal and commercial software programs.

Permission is granted to use the Source Code without modification in classroom situations, including in presentation materials, provided that the book "Atomic Kotlin" is cited as the origin.

Permission to incorporate the Source Code into printed media may be obtained by contacting:

MindView LLC, PO Box 969, Crested Butte, CO 81224 MindViewInc@gmail.com

The Source Code and documentation are copyrighted by MindView LLC. The Source code is provided without express or implied warranty of any kind, including any implied warranty of merchantability, fitness for a particular purpose or non-infringement. MindView LLC does not

warrant that the operation of any program that includes the Source Code will be uninterrupted or error-free. MindView LLC makes no representation about the suitability of the Source Code or of any software that includes the Source Code for any purpose. The entire risk as to the quality and performance of any program that includes the Source Code is with the user of the Source Code. The user understands that the Source Code was developed for research and instructional purposes and is advised not to rely exclusively for any reason on the Source Code or any program that includes the Source Code. Should the Source

Code or any resulting software prove defective, the user assumes the cost of all necessary servicing, repair, or correction.

IN NO EVENT SHALL MINDVIEW LLC, OR ITS PUBLISHER BE LIABLE TO ANY PARTY UNDER ANY LEGAL THEORY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS, OR FOR PERSONAL INJURIES, ARISING OUT OF THE USE OF THIS SOURCE CODE AND ITS DOCUMENTATION, OR ARISING OUT OF THE INABILITY TO USE ANY RESULTING PROGRAM, EVEN IF MINDVIEW LLC, OR ITS PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. MINDVIEW LLC SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOURCE CODE AND DOCUMENTATION PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, WITHOUT ANY ACCOMPANYING SERVICES FROM MINDVIEW LLC, AND MINDVIEW LLC HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

Please note that MindView LLC maintains a Web site which is the sole distribution point for electronic copies of the Source Code, where it is freely available under the terms stated above:

https://github.com/BruceEckel/AtomicKotlinExamples

If you think you've found an error in the Source Code, please submit a correction at: https://github.com/BruceEckel/AtomicKotlinExamples/issues

Kodu tego można używać w swoich projektach oraz w zajęciach edukacyjnych (w tym w materiałach prezentacyjnych), o ile zostanie zachowana uwaga o prawach autorskich obecna w każdym pliku źródłowym.

Część I
Podstawy programowania

W programowaniu było coś niebywale kuszącego
– Vint Cerf

Ta część przeznaczona jest dla tych osób, które uczą się programowania. Ci spośród czytelników, którzy mają doświadczenie w programowaniu, mogą przejść do punktów „Podsumowanie 1” oraz „Podsumowanie 2”.

Wprowadzenie

Książka ta jest przeznaczona zarówno dla zaangażowanych nowicjuszy, jak i dla doświadczonych programistów.

Przyjmujemy, że czytelnik jest nowicjuszem, jeśli nie ma wcześniejszego doświadczenia w programowaniu, ale powinien być „zaangażowany”, gdyż dostarczymy jedynie tyle wiedzy, ile potrzeba, aby mógł działać na tym polu samodzielnie. Po ukończeniu otrzyma solidne podstawy w programowaniu, a także w znajomości języka Kotlin.

Osoby, które są doświadczonymi programistami, powinny przejść do punktów „Podsumowanie 1” oraz „Podsumowanie 2” i kontynuować lekturę od tamtego miejsca.

Słowo „atomowy” w tytule książki odnosi się do atomów jako najmniejszych, niepodzielnych jednostek. W tej książce staramy się wprowadzać po jednej koncepcji w każdym rozdziale, zatem nie mogą być one podzielone na mniejsze fragmenty – dlatego nazywamy je atomami.

Koncepcje

Wszystkie języki programowania składają się z funkcjonalności. Używamy tych funkcjonalności, aby uzyskać pożądany rezultat. Kotlin jest efektywnym językiem – nie tylko zawiera bogaty zestaw funkcjonalności, ale dzięki niemu można je wyrazić na wiele sposobów.

Gdybyśmy zbyt szybko przekazali całą wiedzę czytelnikowi, mógłby odejść z przekonaniem, że Kotlin jest „zbyt skomplikowany”.

W tej książce staramy się uczyć tego języka w sposób dokładny i przemyślany, stosując się do następujących zasad:

1. Drobne kroki i małe zwycięstwa. Odrzucamy tyranię rozdziałów. Zamiast tego prezentujemy każdy, nawet niewielki krok jako atomową koncepcję lub po prostu atom, wyglądający jak mały rozdział. W każdym atomie staramy się przedstawić tylko jedną koncepcję. Typowy atom zawiera jeden lub więcej niewielkich, możliwych do uruchomienia fragmentów kodu oraz tworzonych przez nie wyników.

2. Bez odnośników w przód. O ile to tylko możliwe, unikamy zwrotów w rodzaju „Te funkcje zostaną wyjaśnione w późniejszym atomie”.

3. Bez odnośników do innych języków programowania. Będziemy to robić jedynie wtedy, gdy jest to niezbędne. Analogia do funkcjonalności dostępnej w języku, którego się nie rozumie, nie jest pomocna.

4. Pokazywanie, a nie opowiadanie. Zamiast słownego omawiania funkcjonalności, preferujemy używanie przykładów i wyników, czyli zamieniamy teorię w praktykę – lepiej jest gołym okiem zobaczyć np. funkcjonalność w kodzie.

5. Ćwiczenia przed teorią. Staramy się najpierw pokazywać mechanikę języka, a dopiero potem wyjaśniać, dlaczego te funkcjonalności istnieją. Jest to podejście odwrotne do „tradycyjnego” nauczania, ale często wydaje się działać lepiej.

Gdy czytelnik już pozna funkcjonalności języka, wypracuje sobie jego rozumienie. Zazwyczaj łatwiejsze jest przyswojenie pojedynczej strony kodu Kotlina niż zrozumienie równoważnego mu kodu w innym języku.

Gdzie jest indeks?

Ta książka została napisana w języku znaczników Markdown i następnie złamana za pomocą Leanpub. Niestety, ani Markdown, ani Leanpub nie obsługują indeksowania. Jednak dzięki utworzeniu możliwie małych rozdziałów (atomów), z których każdy zawiera pojedyncze zagadnienie, spis treści odgrywa rolę czegoś w rodzaju indeksu. Dodatkowo wersja eBook tej książki umożliwia elektroniczne przeszukiwanie treści.

Odsyłacze

Odsyłacz do innego atomu w książce wygląda tak: „Wprowadzenie”; w tym przypadku odsyłacz, kieruje do bieżącego atomu. W różnych formatach eBooków tworzy on hiperłącze do odpowiedniego atomu.

Formatowanie

W tej książce:

• Kursywa służy do wprowadzenia nowego pojęcia lub koncepcji, a czasem do wyróżnienia jakiegoś pomysłu.

• Czcionka o stałej szerokości oznacza słowa kluczowe programu, identyfikatory oraz nazwy plików. Przykłady kodu również używają tej czcionki, a w jej wersjach eBook także różnych kolorów wyróżniających składnię.

• W tekście opisowym nazwę funkcji zawsze uzupełniamy pustymi nawiasami, tak jak func(). Pomaga to czytelnikom rozpoznać, że patrzą na funkcję.

• Aby ułatwić czytanie eBooków na wszystkich urządzeniach i pozwolić użytkownikom zwiększyć rozmiar czcionki, szerokość listingów kodu ograniczyliśmy do 47 znaków. Czasami wymaga to kompromisów, ale wydaje się, że uzyskany rezultat jest tego wart. Aby otrzymać taką szerokość, usunęliśmy wiele spacji, które inaczej byłyby obecne w wielu stylach formatowania – w szczególności używamy wcięć o wielkości dwóch spacji, a nie standardowych czterech.

Przykładowa książka

W witrynie AtomicKotlin.com[4] udostępniamy przykładowy fragment książki. Ta próbka obejmuje w całości dwie pierwsze części oraz kilka późniejszych atomów. Dzięki temu czytelnik może wypróbować książkę i zdecydować, czy jest dla niego odpowiednia.

Kompletna książka jest oferowana do zakupu, zarówno w formie drukowanej, jak i elektronicznej. Jeśli komuś podoba się to, co znalazł w bezpłatnej próbce, może wesprzeć nas i pomóc w dalszej pracy, płacąc za to, czego używa. Mamy nadzieję, że książka okaże się pomocna i bardzo dziękujemy za sponsorowanie naszej pracy.

W erze Internetu nie wydaje się możliwe kontrolowanie każdego elementu informacji. Elektroniczną wersję tej książki zapewne można znaleźć w wielu miejscach. Jeśli Twoja sytuacja materialna nie pozwala Ci na zakup książki już teraz i pobierzesz ją z jednego z tych miejsc, prosimy, abyś chociaż mógł podzielić się swoją wiedzą z kimś innym, np. poprzez naukę języka.

Ćwiczenia i rozwiązania

Większości atomów książki Atomowy Kotlin towarzyszy kilka niewielkich ćwiczeń. Dla poprawy opanowania materiału zalecamy rozwiązywanie tych ćwiczeń bezpośrednio po lekturze atomu. Większość z nich jest automatycznie rejestrowana przez zintegrowane środowisko programistyczne (IDE) JetBrains IntelliJ IDEA, zatem można łatwo śledzić swoje postępy i uzyskać wskazówki w razie trudności.

Pokazane dalej hiperłącza można znaleźć na stronie http://AtomicKotlin.com/exercises/[5].

Aby móc rozwiązywać ćwiczenia, należy zainstalować IntelliJ IDEA wraz z wtyczką Edu Tools, zgodnie z poniższymi tutorialami:

1. Instalowanie IntelliJ IDEA oraz wtyczki EduTools[6].

2. Otwarcie kursu Atomic Kotlin i rozwiązywanie ćwiczeń[7].

W ramach kursu można znaleźć rozwiązania wszystkich ćwiczeń. Jeśli ktoś nie będzie w stanie wykonać któregoś z nich, może tam poszukać wskazówek bądź spróbować zerknąć do rozwiązania. Nadal jednak zalecamy samodzielność.

Jeśli pojawią się jakieś problemy przy konfigurowaniu i uruchamianiu kursu, należy przeczytać dokument The Troubleshooting Guide[8]. Jeżeli to nie rozwiąże problemu, prosimy o kontakt z zespołem wsparcia, zgodnie z informacjami zawartymi w przewodniku.

W przypadku odkrycia błędu w zawartości kursu (na przykład gdy test do zadania zwraca niewłaściwy wynik), prosimy użyć naszego mechanizmu śledzenia problemów, aby zgłosić go za pośrednictwem wstępnie wypełnionego formularza[9]. Warto zauważyć, że konieczne będzie zalogowanie się w YouTrack. Doceniamy czas, jaki poświęcają nasi czytelnicy, aby pomóc w ulepszeniu kursu.

Seminaria

Informacje na temat seminariów na żywo i innych narzędzi edukacyjnych można znaleźć w witrynie AtomicKotlin.com.

Konferencje

Bruce organizuje konferencje Open-Spaces, takie jak Winter Tech Forum[10]. Warto dołączyć do listy mailingowej na stronie AtomicKotlin.com, aby otrzymywać bieżące informacje o naszych działaniach i miejscach, w których prowadzimy wykłady.

Wspieranie nas

To był wielki projekt. Stworzenie tej książki i towarzyszących jej materiałów pomocniczych zajęło wiele czasu i wymagało sporego wysiłku. Jeśli komuś spodobała się nasza praca i chciałby znaleźć więcej podobnych rzeczy, prosimy o okazanie wsparcia:

• Blogowanie, tweetowanie itp. oraz opowiadanie przyjaciołom. Takie wysiłki marketingowe działają u samych podstaw, zatem cokolwiek zrobisz, będzie pomocne.

• Zakup książki w wersji eBook lub drukowanej w witrynie AtomicKotlin.com.

• Sprawdzenie AtomicKotlin.com pod kątem innych pomocniczych produktów i zdarzeń.

O nas

Bruce Eckel jest autorem wielokrotnie nagradzanych książek Thinking in Java oraz Think ing in C++, a także wielu innych książek na temat programowania komputerów, w tym Atomic Scala[11]. Przedstawił setki prezentacji i wykładów na całym świecie, a także organizuje alternatywne konferencje i wydarzenia, takie jak Winter Tech Forum[12] i szkolenia dla programistów. Bruce uzyskał BS z fizyki stosowanej oraz MS z inżynierii komputerowej. Jego blog jest dostępny pod adresem www.BruceEckel.com[13], zaś jego firma MindView LLC zajmuje się konsultacjami, szkoleniami i konferencjami[14].

Svetlana Isakova zaczynała jako członek zespołu tworzącego kompilator Kotlina, a obecnie jest propagatorem JetBrains wśród programistów. Uczy języka Kotlin i występuje na konferencjach na całym świecie, a ponadto jest współautorką książki Kotlin in Action.

Podziękowania

• Dla zespołu projektowego Kotlina i innych współautorów.

• Dla programistów Leanpub, dzięki którym opublikowanie tej książki było dużo łatwiejsze.

Dedykacje

Ku pamięci mojego ukochanego ojca – E. Wayne’a Eckela (1.04.1924 – 23.11.2016). Jako pierwszy pokazałeś mi świat maszyn, narzędzi i projektowania.

Ku pamięci mojego ojca – Sergeya Lvovicha Isakova, który odszedł tak wcześnie i którego stale nam brak.

O okładce

Daniel Will-Harris[15] zaprojektował okładkę, bazując na logu Kotlina.

Dlaczego Kotlin?

Przedstawimy teraz przegląd historycznego rozwoju języków programowania, aby czytelnik mógł się zorientować, gdzie Kotlin się znajduje i kto może chcieć się go nauczyć. Ten atom wprowadza kilka zagadnień, które mogą się początkowo wydawać zbyt skomplikowane dla nowicjuszy. W takiej sytuacji czytelnik może spokojnie pominąć ten atom i powrócić do niego później, gdy przeczyta więcej rozdziałów.

Programy muszą być pisane tak, aby ludzie mogli je czytać,
a tylko czasami powstają dla maszyn, aby mogły je wykonywać.
– Harold Abelson, Struktura i interpretacja
programów komputerowych

Projektowanie języków programowania przeszło długą drogę ewolucji, od zapewniania wymagań maszyny, po obsługiwanie potrzeb programisty.

Język programowania jest tworzony przez projektanta języka i implementowany jako jeden lub więcej programów, które pełnią funkcje narzędzi umożliwiających posługiwanie się tym językiem. Osoba implementująca te narzędzia to zazwyczaj projektant języka, przynajmniej początkowo.

Wczesne języki skupiały się na ograniczeniach sprzętowych. W miarę jak komputery stawały się coraz silniejsze, nowsze języki przesuwały się w stronę bardziej wyrafinowanego programowania, z naciskiem na niezawodność. Te języki wybierają funkcjonalności na podstawie psychologii programowania.

Każdy język programowania jest efektem zbioru eksperymentów. Historycznie rzecz biorąc, projekt języka programowania był efektem domysłów i przypuszczeń na temat tego, co może zapewnić większą produktywność programistów. Niektóre z tych eksperymentów zakończyły się niepowodzeniem, inne były średnio udane, a niektóre odniosły wielki sukces.

W każdym nowym języku uczymy się poprzez eksperymenty. Niektóre języki są nakierowane na rozwiązywanie problemów, które z kolei okazują się być raczej incydentalne niż kluczowe, albo też zmiany środowiska (szybsze procesory, mniejsza cena pamięci, nowe rozumienie programowania i języków) sprawiają, że problemy te okazują się mniej ważne bądź wręcz nieistotne. Jeśli te idee staną się przestarzałe, a język nie będzie ewoluował, jego używanie zaniknie.

Pierwsi programiści pracowali bezpośrednio z liczbami reprezentującymi instrukcje maszynowe. Takie podejście powodowało liczne błędy, zatem powstał język asemblera, zastępujący liczby mnemonicznymi kodami operacji – słowami, które programistom łatwiej było zapamiętać i czytać, niezależnie od innych pomocnych narzędzi. Nadal jednak była zależność jeden do jednego pomiędzy instrukcjami języka asemblera a instrukcjami maszynowymi, a programiści musieli napisać każdy wiersz kodu asemblera. Dodatkowo każdy procesor komputerowy używał własnego, odmiennego języka asemblera.

Tworzenie programów w asemblerze jest zbyt kosztowne. Języki wyższego poziomu pomagają rozwiązać ten problem, tworząc dodatkowe poziomy abstrakcji ponad niskopoziomowymi językami asemblera.

Kompilatory i interpretery

Kotlin jest językiem kompilowanym, a nie interpretowanym. Instrukcje języka interpretowanego są wykonywane bezpośrednio przez oddzielny program nazywany interpreterem. W przeciwieństwie do tego kod źródłowy języka kompilowanego jest przekształcany na inną reprezentację, która jest uruchamiana jako samodzielny program albo bezpośrednio w sprzętowym procesorze, albo w maszynie wirtualnej, która emuluje procesor:

Takie języki jak C, C++, Go i Rust są kompilowane do kodu maszynowego, który działa bezpośrednio w sprzętowym procesorze (central processing unit – CPU). Języki takie jak Java i Kotlin są kompilowane do kodu bajtowego (bytecode), który jest formatem warstwy pośredniej i nie jest uruchamiany wprost na procesorze sprzętowym, ale w maszynie wirtualnej, która jest programem wykonującym instrukcje kodu bajtowego. Wersja JVM języka Kotlin działa w Java Virtual Machine (JVM).

Ważną korzyścią stosowania maszyny wirtualnej jest przenośność. Ten sam kod bajtowy może działać na każdym komputerze, który dysponuje odpowiednią maszyną wirtualną. Maszyny wirtualne mogą być optymalizowane pod kątem konkretnego sprzętu i w celu rozwiązywania problemów z prędkością działania. JVM zawiera w sobie efekty wielu lat takich optymalizacji i jest implementowana na bardzo wielu platformach sprzętowych.

W czasie kompilacji kod jest sprawdzany przez kompilator w celu identyfikowania błędów czasu kompilacji. (IntelliJ IDEA i inne środowiska programistyczne wyróżniają takie błędy podczas wprowadzania kodu, dzięki czemu można szybko wykrywać i poprawiać dowolne problemy). Jeśli nie pojawią się błędy czasu kompilacji, kod źródłowy zostanie skompilowany do kodu bajtowego.

Błędu czasu wykonania (runtime error) nie można wykryć podczas kompilacji, zatem ujawnia się on dopiero po uruchomieniu programu. Typowo błędy czasu wykonania są trudniejsze do wykrycia, a ich naprawa bardziej kosztowna. Statycznie typowane języki, takie jak Kotlin, pozwalają na wykrywanie w czasie kompilacji tak wielu błędów, jak to możliwe, podczas gdy języki dynamiczne wykonują testy bezpieczeństwa dopiero podczas uruchamiania (niektóre dynamiczne języki nie mogą sobie pozwolić na wykonywanie ich w takiej ilości.

Języki, które wywarły wpływ na Kotlin

Idee i funkcjonalności występujące w Kotlinie wywodzą się z wielu języków, a na nie miały wpływ jeszcze wcześniejsze. Przydatne jest poznanie historii języków programowania, aby uzyskać lepszą perspektywę tego, jak dotarliśmy do języka Kotlin. Języki opisane w tym punkcie zostały wybrane ze względu na wpływ, jaki miały na języki następujące po nich. Wszystkie z nich ostatecznie inspirowały projektantów Kotlina, czasami jako przykłady czegoś, czego nie należy robić.

FORTRAN: FORmula TRANslation (1957)

Zaprojektowany pod kątem zastosowań naukowych i inżynierskich – Fortran – miał na celu ułatwienie kodowana równań. Dopracowane i przetestowane biblioteki Fortranu są nadal w użyciu, ale zazwyczaj zostały „opakowane”, aby możliwe było ich wywoływanie z innych języków.

LISP: LISt Processor (1958)

Zamiast być specyficznym dla zastosowania, LISP ucieleśniał podstawowe koncepcje programowania; był pierwszym językiem informatyków i pierwszym językiem funkcyjnym (programowanie funkcyjne będziemy poznawać w tej książce). Ceną za tę wszechstronność i elastyczność była wydajność: LISP był zbyt kosztowny, aby go używać na wczesnych maszynach i dopiero w ostatnich dziesięcioleciach komputery stały się dostatecznie szybkie, aby zapewnić ponowne odrodzenie się tego języka. Na przykład edytor GNU Emacs jest w całości napisany w LISP i można go rozszerzać przy użyciu LISP.

ALGOL: ALGOrithmic Language (1958)

Jest zdecydowanie najbardziej wpływowy spośród języków pochodzących z lat 50. XX w., gdyż wprowadzono w nim składnię, która przetrwała do dziś w wielu późniejszych językach. Przykładowo język C i jego pochodne są językami „podobnymi do ALGOLa”.

COBOL: COmmon Business-Oriented Language (1959)

Zaprojektowany do przetwarzania danych biznesowych, finansowych i administracyjnych. Ma składnię przypominającą zwykły język angielski i został pomyślany jako samodokumentujący się i łatwy w odczytywaniu. Choć te zamiary w ogólności się nie powiodły – COBOL jest znany z błędów powodowanych przez źle umieszczoną kropkę – Department Obrony Stanów Zjednoczonych wymusił jego szerokie stosowanie na komputerach klasy mainframe i dziś nadal istnieją działające (i wymagające utrzymywania) systemy napisane w tym języku.

BASIC: Beginners’ All-purpose Symbolic Instruction Code (1964)

BASIC był jedną z pierwszych prób sprawienia, by programowanie stało się dostępne dla wszystkich. Choć ten cel został osiągnięty, jego funkcjonalność i składnia są bardzo ograniczone, zatem był jedynie częściowo pomocny dla osób, które potrzebowały nauczyć się bardziej wyrafinowanych języków. Zasadniczo jest on interpretowany, co oznacza, że trzeba uruchamiać oryginalny kod programu. Mimo to wiele użytecznych programów zostało napisanych w języku BASIC[16], w szczególności język skryptowy dla produktów Microsoft Office. BASIC można nawet traktować jako pierwszy „otwarty” język programowania, gdyż różni ludzie stworzyli jego liczne odmiany.

Simula 67, oryginalny język obiektowy (1967)

Symulacja typowo obejmuje wiele „obiektów” oddziałujących na siebie nawzajem. Różne obiekty mają różne charakterystyki i zachowania. Języki, które istniały w tym czasie, były skrajnie niewygodne do wykorzystania w symulacjach, zatem opracowano język Simula (kolejny język „podobny do ALGOLa”) w celu zapewnienia bezpośredniego wsparcia dla tworzenia obiektów symulacji. Okazało się, że te pomysły są również przydatne w programowaniu ogólnego stosowania i taka jest geneza języków obiektowych (Object-Oriented – OO).

Pascal (1970)

Pascal pozwolił zwiększyć szybkość kompilacji poprzez narzucenie na język takich ograniczeń, aby możliwe było jego zaimplementowanie jako kompilatora jednoprzebiegowego. Język wymuszał na programiście używanie określonej struktury kodu i narzucał trochę dziwne i mniej zrozumiale ograniczenia na organizację programu. W miarę jak procesory stawały się szybsze, pamięć tańsza, a technologia kompilatorów lepsza, wpływ tych ograniczeń stawał się zbyt kosztowny.

Jedna z implementacji Pascala, Turbo Pascal firmy Borland, początkowo działała na komputerach systemu CP/M, po czym została przeniesiona na wczesne wersje MS-DOS (prekursora systemu Windows), później ewoluując do postaci języka Delphi dla Windows. Dzięki umieszczaniu wszystkiego w pamięci Turbo Pascal kompilował kod z ogromną prędkością, nawet na bardzo słabych maszynach, poprawiając komfort pracy programistów. Jego twórca, Anders Hejlsberg, później dołączył do pracy nad projektowaniem języków C# oraz TypeScript.

Niklaus Wirth, wynalazca Pascala, stworzył jeszcze kolejne języki: Modula, Modula-2 oraz Oberon. Zgodnie z nazwą Modula koncentruje się na dzieleniu programów na moduły, co pozwala na lepsze uporządkowanie i szybszą kompilację. Większość nowoczesnych języków wspiera oddzielne kompilowanie i jakąś formę systemu modułów.

C (1972)

Pomimo rosnącej liczby języków wysokiego poziomu programiści nadal pisali programy w języku asemblera. Często jest to nazywane programowaniem systemowym, gdyż jest wykonywane na poziomie samego systemu operacyjnego, ale do tej kategorii należy również tworzenie systemów wbudowanych dla dedykowanych urządzeń fizycznych. Jest to nie tylko żmudne i kosztowne (Bruce rozpoczął swoją karierę od napisania języka asemblera dla systemów wbudowanych), ale również nie jest przenośne – język asemblera może działać tylko na tym procesorze, dla którego został napisany. Język C został zaprojektowany jako „język asemblera wyższego poziomu”, który jest nadal tak dostatecznie blisko sprzętu, że rzadko zachodzi potrzeba pisania kodu asemblera. Co ważniejsze jednak, program napisany w C działa na dowolnym procesorze, dla którego istnieje kompilator C. Język C oddzielił program od procesora, co rozwiązywało wielki i kosztowny problem. W rezultacie programiści, którzy dotychczas pracowali w asemblerze, mogli być znacznie bardziej produktywni, pracując w C. Ten język okazał się tak skuteczny, że najnowsze języki (przede wszystkim Go oraz Rust) nadal podejmują próby zastąpienia go w programowaniu systemowym.

Smalltalk (1972)

Zaprojektowany od początku jako w pełni obiektowy, Smalltalk stanowił znaczący krok w przód programowania obiektowego i teorii języków, będąc platformą dla eksperymentów i demonstrowania błyskawicznego tworzenia aplikacji. Został jednak utworzony w czasach, gdy języki nadal były czyjąś własnością i cena startowa systemu Smalltalk mogła być liczona w tysiącach. Był to język interpretowany, zatem do uruchamiania programów potrzebne było środowisko Smalltalk. Jego otwarte implementacje nie pojawiły się aż do chwili, gdy świat programistyczny nie ruszył dalej. Programiści Smalltalk wnieśli znaczącą wiedzę, z której skorzystały późniejsze języki obiektowe, takie jak C++ i Java.

C++: Lepsze C z obiektami (1983)

Bjarne Stroustrup stworzył język C++, gdyż potrzebował lepszego C i chciał uzyskać wsparcie dla konstrukcji obiektowych, które poznał przy posługiwaniu się językiem Simula-67. Bruce był członkiem komitetu standaryzacyjnego C++ przez pierwszych osiem lat jego istnienia i napisał trzy książki na temat tego języka, w tym Thinking in C++[17] (Helion, 2002).

Fundamentalną zasadą C++ była wsteczna kompatybilność z C, tak by kod napisany w C mógł zostać skompilowany w C++ zasadniczo bez żadnych zmian. Zapewniło to łatwą ścieżkę migracji – programiści mogli nadal programować w C, uzyskiwać korzyści związane z C++ i powoli eksperymentować z funkcjonalnościami C++, nadal zachowując produktywność. Większość krytyki dotyczącej C++ można w istocie sprowadzić do wymogu wstecznej zgodności z C.

Jednym z głównych problemów C było zagadnienie zarządzania pamięcią. Programista musiał najpierw uzyskać przydział pamięci, potem wykonać operację używającą tej pamięci, a następnie zwolnić tę pamięć. Pominięcie (zapomnienie)ostatniego etapu nazywane jest wyciekiem pamięci i może prowadzić do zużycia całej jej dostępnej, a następnie awarii procesu. Wstępna wersja C++ wprowadziła pewne innowacje w tym zakresie, wraz z konstruktorami zapewniającymi właściwe zainicjowanie. W późniejszych wersjach języka pojawiły się znaczące usprawnienia w dziedzinie zarządzania pamięcią.

Python: przyjazny i elastyczny (1990)

Projektant języka Python, Guido Van Rossum, stworzył to narzędzie, opierając się na inspirującej koncepcji „programowania dla każdego”. Jego opiekuńcze podejście do społeczności Pythona zapewniło jej opinię najbardziej przyjaznej i pomocnej społeczności w świecie programistycznym. Python był jednym z pierwszych języków o otwartych źródłach, co doprowadziło do jego implementacji, praktycznie na każdej istniejącej platformie, w tym na systemach wbudowanych i w uczeniu maszynowym. Dynamizm i łatwość użycia języka sprawia, że jest to idealny wybór dla automatyzowania niewielkich, powtarzalnych zadań, ale jego funkcjonalności pozwalają również na tworzenie obszernych, złożonych programów.

Python jest prawdziwie „oddolnym” językiem; nigdy nie był promowany przez żadną firmę, a jego fani prezentują postawę polegającą na tym, że nigdy nie wymuszają używania języka, ale po prostu pomagają nauczyć się go każdemu, kto tego zechce. Język stale się rozwija, a w ostatnich latach jego popularność ogromnie wzrosła.

Być może Python jest pierwszym językiem głównego nurtu, który łączy w sobie programowanie funkcyjne z obiektowym. Wyprzedził język Java, jeśli chodzi o automatyczne zarządzanie pamięcią przy użyciu mechanizmów sprzątających (garbage collection) – w typowych sytuacjach nigdy nie ma potrzeby samodzielnego alokowania ani zwalniania pamięci – oraz zdolność uruchamiania programów na wielu platformach.

Haskell: czysto funkcyjne programowanie (1990)

Inspirowany językiem Miranda (1985), językiem należącym do konkretnej firmy, Haskell powstał jako otwarty standard dla badań programowania czysto funkcyjnego, choć był również używany do utworzenia konkretnych produktów. Składnia i pomysły pochodzące z Haskella miały wpływ na wiele późniejszych języków, w tym na Kotlina.

Java: maszyny wirtualne i sprzątanie pamięci (1995)

James Gosling i jego zespół otrzymali zadanie napisania kodu dla dekodera telewizyjnego. Zdecydowali, że nie użyją języka C++ i zamiast utworzenia dekodera, stworzyli język Java. Firma Sun Microsystems zapewniła niespotykane wsparcie marketingowe wolnemu językowi (co w tym czasie było nadal nowatorską koncepcją) w dążeniu do uzyskania dominacji w rozwijającym się świecie Internetu.

To niewielkie okno czasowe dominowania w Internecie wywierało wielką presję na projektowanie języka Java, co doprowadziło do znaczącej liczby niedociągnięć (książka Thinking in Java wyjaśnia te wady, dzięki czemu czytelnicy są przygotowani na radzenie sobie z nimi). Brian Goetz z firmy Oracle, obecnie główny projektant Javy, dokonał znaczących i zaskakujących usprawnień w języku, pomimo odziedziczonych ograniczeń. Choć Java osiągnęła znaczący sukces, jednym z ważnych celów projektowych języka Kotlin jest wyeliminowanie wad języka Java, aby zapewnić większą produktywność programistów.

Sukces Javy wynikał z dwóch innowacyjnych cech: maszyny wirtualnej oraz odśmiecania pamięci (garbage collection). Były one dostępne w innych językach – dla przykładu LISP, Smalltalk oraz Python dysponują mechanizmem sprzątania pamięci, zaś UCSD Pascal działa w maszynie wirtualnej – ale nigdy nie były uważane za praktyczne podejście dla języków głównego nurtu. Java zmieniła to i właśnie dzięki temu programiści stali się znacząco bardziej produktywni.

Maszyna wirtualna jest warstwą pośrednią pomiędzy językiem a sprzętem. Język nie musi generować kodu maszynowego dla konkretnego procesora, jedynie wygenerować język pośredni (kod bajtowy), który działa w maszynie wirtualnej. Wymaga ona dodatkowej mocy obliczeniowej i przed pojawieniem się Javy uważano, że jest niepraktyczna. Java Virtual Machine (JVM) pozwoliła zrealizować slogan reklamowy Javy „napisz raz, uruchamiaj wszędzie”. Dodatkowo można łatwo tworzyć kod w innych językach, wybierając JVM jako środowisko docelowe; przykłady obejmują Groovy – podobny do Javy język skryptowy, oraz Clojure – odmianę języka LISP.

Odśmiecanie pamięci rozwiązuje problem dotyczący sytuacji, gdy zapomnimy o zwolnieniu pamięci, lub takich, gdy trudno jest ustalić, czy dany fragment pamięci jest jeszcze używany, czy nie. Wiele projektów zostało znacząco opóźnionych lub wręcz anulowanych z powodu wycieków pamięci. Choć mechanizmy sprzątania pamięci występują w niektórych wcześniejszych językach, uważano, że tworzą one nieakceptowalne, nadmiarowe obciążenie, dopóki Java nie zademonstrowała praktyczności tego podejścia.

JavaScript: Java tylko w nazwie (1995)

Oryginalne przeglądarki internetowe po prostu kopiowały i wyświetlały strony z serwera. W miarę rozpowszechniania przeglądarek stawały się one nową platformą programistyczną, która potrzebowała wsparcia własnego języka. Tym językiem chciała być Java, ale była zbyt nieporęczna dla tego zadania. JavaScript powstał jako LiveScript, wbudowany w NetScape Navigator, jedną z pierwszych przeglądarek Web. Zmiana nazwy na JavaScript była jedynie chwytem marketingowym ze strony NetScape, gdyż język ten wykazuje tylko nieznaczne podobieństwo do Javy.

W miarę rozwoju sieci Web JavaScript stał się niebywale ważnym językiem. Jednak zachowanie JavaScriptu było tak nieprzewidywalne, że Douglas Crockford napisał książkę pod ironicznym tytułem JavaScript – mocne strony (Helion, 2011, tyt. oryg. JavaScript, the Good Parts[18]), w której demonstrował wszystkie problemy związane z tym językiem, tak by programiści mogli ich unikać. Późniejsze ulepszenia dokonane przez komitet ECMAScript sprawiły, że JavaScript stał się niezrozumiały dla oryginalnego programisty JavaScript. Obecnie jest uznawany za stabilny i dojrzały język.

Web assembly – WASM zostały wyprowadzone z JavaScript jako rodzaj kodu bajtowego dla przeglądarek Web. Często działają znacznie szybciej niż JavaScript i można je generować z innych języków. W chwili pisania tych słów zespół Kotlina pracuje nad dodaniem WASM jako miejsca docelowego.

C#: Java dla .NET (2000)

C# został zaprojektowany w celu zapewnienia pewnych ważnych możliwości języka Java na platformie .NET (Windows), zarazem uwalniając projektantów od ograniczenia związanego z trzymaniem się języka Java. Rezultat tych starań zawiera liczne ulepszenia względem języka Java. Dla przykładu w C# wypracowano koncepcję funkcji rozszerzających (extension functions), które są intensywnie używane w Kotlinie. C# stał się również znacząco bardziej funkcyjny niż Java. Wiele cech C# bezpośrednio wpłynęło na projekt języka Kotlin.

Scala: Skalowalność (2003)

Martin Odersky utworzył język Scala z myślą o uruchamianiu na maszynie wirtualnej Java: aby wykorzystać pracę już wykonaną nad JVM, zapewnić interakcję z programami napisanymi w Javie, a być może w nadziei zastąpienia Javy. Jako badacz, Odersky i jego zespół używali języka Scala jako platformy eksperymentowania z funkcjonalnościami języków, szczególnie tymi, które nie zostały włączone do Javy.

Eksperymenty te były bardzo pouczające i wiele z nich znalazło swoje odzwierciedlenie w Kotlinie, zazwyczaj w zmodyfikowanej formie. Na przykład zdolność do przedefiniowania operatorów, takich jak +, do wykorzystania w specjalnych przypadkach, nosi nazwę przeciążania operatorów. Ta możliwość została włączona w C++, ale nie w Javę. Scala dodała przeciążanie operatorów, ale również pozwala tworzyć nowe operatory poprzez kombinację dowolnej sekwencji znaków. To jednak często tworzy mylący, nieczytelny kod. Kotlin zawiera ograniczone możliwości przeciążania operatorów, ale przeciążać możemy tylko te operatory, które już istnieją.

Scala jest ponadto hybrydą obiektowo-funkcyjną, podobnie jak Python, ale ze skupieniem na czyste funkcje i ścisłe obiekty. Pomogło to twórcom Kotlina w dokonaniu wyboru, że ten język również ma być hybrydą obiektowo-funkcyjną.

Podobnie jak Scala, Kotlin działa na JVM, ale zapewnia znacznie łatwiejszą interakcję z programami napisanymi w Javie, niż robi to Scala. Dodatkowo Kotlin może generować kod dla JavaScript w Android OS oraz kod natywny dla innych platform.

Książka Atomowy Kotlin wyewoluowała z koncepcji i materiału zawartego w Atomic Scala[19].

Groovy: Dynamiczny język JVM (2007)

Języki dynamiczne należą do pociągających, gdyż są bardziej interaktywne i zwięzłe od języków statycznych. Podejmowano wiele prób uzyskania bardziej dynamicznych sposobów programowania na platformie JVM, w tym Jython (Python) oraz Clojure (dialekt języka Lisp). Groovy był pierwszym z tych języków, który zdobył szeroką akceptację.

Na pierwszy rzut oka Groovy wydaje się wyczyszczoną wersją Javy, zapewniającą przyjemniejsze wrażenia programistyczne. Większość kodu w języku Java będzie działać w Groovy bez żadnych zmian, zatem programiści Javy mogą szybko uzyskać produktywność, dopiero później ucząc się bardziej zaawansowanych funkcjonalności, które zapewniają znaczące korzyści względem Javy.

Występujące w języku Kotlin operatory ?. oraz ?:, które zajmują się problemem pustki (ang. emptiness)[20], po raz pierwszy pojawiły się w języku Groovy.

Istnieje wiele cech Groovy, które można zauważyć w Kotlinie. Niektóre z tych funkcjonalności występują również w innych językach, co zapewne było dodatkowym przyczynkiem dla dołączenia ich do Kotlina.

Dlaczego Kotlin? (wprowadzony w roku 2011, wersja 1.0: 2016)

Podobnie jak C++, początkowo miał być „lepszym C”, Kotlin wstępnie był nakierowany na zostanie „lepszą Javą”. Od tego momentu stopniowo wyewoluował daleko poza ten cel. Pragmatycznie wybiera tylko najbardziej udane i pomocne cechy innych języków programowania – gdy te funkcjonalności zostały już przetestowane w praktyce i wykazały swoją szczególną wartość.

Tym samym ktoś, kto przychodzi z innego języka programowania, może rozpoznać w Kotlinie pewne cechy tamtego języka. Tak właśnie powinno być: Kotlin maksymalizuje produktywność dzięki wykorzystaniu sprawdzonych koncepcji.

Czytelność

Czytelność jest głównym celem projektu języka. Składnia Kotlina jest zwięzła – w większości scenariuszy nie jest wymagany żaden ceremoniał, ale nadal można w nim wyrazić złożone idee.

Narzędzia

Kotlin wywodzi się z JetBrains, firmy, która specjalizuje się w tworzeniu narzędzi dla programistów. Oferuje najwyższej klasy wsparcie narzędzi, a wiele cech języka zostało zaprojektowanych z myślą o narzędziach.

Wiele paradygmatów

Kotlin wspiera wiele paradygmatów programowania, które zostaną przedstawione w tej książce:

• Programowanie imperatywne

• Programowanie funkcyjne

• Programowanie obiektowe

Wieloplatformowość

Kod źródłowy w języku Kotlin można skompilować na różne platformy docelowe:

• JVM. Kod źródłowy kompiluje się do kodu bajtowego JVM (pliki .class), który może zostać uruchomiony na dowolnej maszynie wirtualnej Java (JVM).

• Android. System Android zawiera własny mechanizm wykonawczy o nazwie ART[21] (jego poprzednik nosił nazwę Dalvik). Kod źródłowy w języku Kotlin można skompilować do postaci Dalvik Executable Format (pliki .dex).

• JavaScript, co umożliwia działanie wewnątrz przeglądarki.

• Natywne pliki binarne dzięki generowaniu kodu maszynowego dla określonych platform i procesorów.

Książka ta skupia się na samym języku, wykorzystując JVM jako jedyną platformę docelową. Gdy już poznamy język, będziemy mogli stosować Kotlin w różnych aplikacjach i platformach docelowych.

Dwie cechy Kotlina

W tym atomie nie zakładamy, że czytelnik jest programistą, przez co trudniejsze jest wyjaśnienie większości cech, dzięki którym język Kotlin ma przewagę nad alternatywami. Istnieją jednak dwa zagadnienia, które mają wielki wpływ i które można wyjaśnić w tak wczesnym momencie: współdziałanie z Javą oraz problem wskazywania „braku wartości”.

Współdziałanie z Javą bez wysiłku

Aby być „lepszym C”, język C++ musi być wstecznie kompatybilny ze składnią języka C, jednak Kotlin nie potrzebuje wstecznej kompatybilności ze składnią Javy – musi jedynie współdziałać z JVM. Pozwoliło to projektantom Kotlina stworzyć znacznie czystszą i bardziej efektywną składnię, bez wizualnego szumu i komplikacji, które zaciemniają kod Javy.

Aby Kotlin był „lepszą Java”, wrażenia z posługiwania się nim muszą być przyjemne i pozbawione zgrzytów, zatem Kotlin umożliwia bezwysiłkową integrację z istniejącymi projektami tworzonymi w Javie. Można napisać drobny fragment funkcjonalności w Kotlinie i wsunąć go w środek istniejącego kodu Javy. Kod ten nie będzie nawet wiedzieć, że znajduje się tu kawałek napisany w Kotlinie – będzie to wyglądać, jak kolejny fragment kodu Javy.

Firmy często badają nowy język, budując samodzielny program w tym języku. W idealnym wariancie taki program jest przydatny, ale nie jest kluczowy, zatem jeśli projekt ten zawiedzie, można go zakończyć przy minimalnych stratach. Jednak nie każda firma chce poświęcać zasoby na tego rodzaju eksperymentowanie. Dzięki temu, że Kotlin pozwala na gładką integrację z istniejącym systemem napisanym w Javie (i korzysta z testów dla tego systemu), wypróbowanie Kotlina w celu przekonania się, czy jest to dobry wybór, jest bardzo tanie lub wręcz darmowe.

Dodatkowo JetBrains, firma, która tworzy Kotlin, udostępnia IntelliJ IDEA w wersji „Community” (bezpłatnej), która zawiera wsparcie zarówno języka Java, jak i Kotlin, wraz z możliwością łatwej integracji obu języków. Dostępne jest nawet narzędzie przyjmujące kod Java i dokonujące przepisania go (prawie) na Kotlin.

Interoperacyjność z językiem Java omówiona jest w dodatku B.

Reprezentowanie pustki

Szczególnie korzystną funkcjonalnością języka Kotlin jest jego rozwiązanie dla szczególnie wymagającego problemu programistycznego.

Co można zrobić, jeśli ktoś poda nam słownik i każe wyszukać w nim słowo, które nie istnieje? Możemy zapewnić uzyskanie wyników, tworząc definicje dla nieznanych słów. Bardziej użyteczne podejście to po prostu stwierdzenie „Nie istnieje definicja dla tego słowa”. Demonstruje to znaczący problem programistyczny: Jak możemy oznaczyć „brak wartości” dla fragmentu pamięci, która nie została zainicjowana, albo dla wyniku jakiejś operacji?

Pojęcie referencji null (null reference) zostało wynalezione na potrzeby języka ALGOL przez Tony’ego Hoare’a, który później nazwał je „pomyłką za miliard dolarów”. Po pierwsze, było to zbyt proste – niekiedy nie wystarcza nam informacja, że pokój jest pusty; możemy potrzebować się dowiedzieć na przykład, dlaczego tak jest. To prowadzi do kolejnego problemu: implementacji. Ze względu na wydajność była to typowo specjalna wartość, która mieściła się w niewielkiej ilości pamięci, ale w czym miałaby być lepsza od pamięci już alokowanej dla tej informacji?

Oryginalny język C nie inicjuje automatycznie pamięci, co powodowało liczne problemy. C++ usprawnił sytuację, wypełniając nowo alokowaną pamięć samymi zerami. Tym samym, jeśli wartość numeryczna nie została zainicjowana, była po prostu równa zero. Nie wydaje się to takie złe, ale pozwalało niezainicjowanym wartościom po cichu wślizgnąć się poprzez szczeliny (nowsze kompilatory C i C++ często zgłoszą ostrzeżenia o takich sytuacjach). Co gorsza, jeśli tym fragmentem pamięci był wskaźnik – używany do oznaczania („wskazywania”) innego miejsca w pamięci – wskaźnik null kierowałby nas do lokalizacji zero w pamięci, co niemal na pewno nie jest tym, co chcemy otrzymać.

Język Java blokuje dostęp do niezainicjowanych wartości, raportując takie błędy w trakcie działania programu. Choć pozwala to odkryć niezainicjowane wartości, nie rozwiązuje to problemu, gdyż jedynym sposobem weryfikacji, że nasz program nie ulegnie awarii, jest jego uruchomienie. W kodzie Javy mogą być całe roje takich błędów i programiści marnują mnóstwo czasu na ich wyszukiwanie.

Kotlin rozwiązuje problem, powstrzymując operacje mogące prowadzić do błędów null w czasie kompilacji, zanim program będzie mógł zostać uruchomiony. Jest to cecha języka najbardziej doceniana przez programistów Javy, którzy przechodzą do Kotlina. Ta jedna funkcjonalność może zminimalizować lub wyeliminować błędy null w Javie.

Bogactwo korzyści

Te dwie cechy, które byliśmy w stanie wyjaśnić w tym miejscu (bez wymagania większej wiedzy programistycznej), stanowią wielką różnicę, niezależnie od tego, czy ktoś jest już programistą Javy, czy nie. Jeśli Kotlin jest naszym pierwszym językiem i trafimy do projektu, który wymaga udziału większej liczby programistów, znacznie łatwiej będzie zwerbować do tego projektu jednego z wielu istniejących programistów Javy.

Kotlin ma jeszcze wiele innych zalet, których nie możemy wyjaśnić, dopóki czytelnik nie nauczy się więcej o programowaniu. Temu właśnie służy reszta tej książki.

Języki są często wybierane z powodu emocji, a nie rozumowania… Staram się uczynić Kotlin językiem, który jest kochany z konkretnego powodu.
– Andrey Breslav, wiodący projektant języka Kotlin

Hello, world!

Podstawową składnię języków programowania najczęściej demonstruje się za pomocą programu „Hello, world!”

Program ten opracujemy w kilku krokach, aby czytelnik zrozumiał jego poszczególne części. Na początek zbadajmy pusty program, który w ogóle nic nie robi:

// HelloWorld/EmptyProgram.kt

fun main() {

// Tu będzie kod programu...

}

Przykład rozpoczyna się komentarzem, który jest tekstem objaśniającym, ignorowanym przez Kotlin. // (dwa ukośniki) zaczynają komentarz, który biegnie do końca bieżącego wiersza:

// Jednowierszowy komentarz

Kotlin ignoruje // i wszystko, co następuje po tych znakach aż do końca wiersza. W kolejnym wierszu znowu zaczyna zwracać uwagę.

Pierwszy wiersz każdego przykładu zawartego w tej książce jest komentarzem rozpoczynającym się nazwą podkatalogu, zawierającego plik kodu źródłowego (w tym przypadku jest to HelloWorld), po którym następuje nazwa pliku: EmptyProgram.kt. Nazwa podkatalogu z przykładami dla każdego atomu pasuje do nazwy tego atomu.

Słowa kluczowe to słowa zarezerwowane przez język i mające specjalne znaczenie. Jednym z nich jest słowo fun, które jest skrótem od function. Funkcja (function) jest fragmentem kodu, który można wykonać poprzez użycie nazwy tej funkcji (w tej książce poświęcimy funkcjom naprawdę dużo czasu). Nazwa funkcji następuje po słowie kluczowym fun, zatem w tym przypadku jest to main() (w tekście opisowym nazwy funkcji zawsze będziemy uzupełniać parą nawiasów).

main() w rzeczywistości jest specjalną nazwą funkcji; sygnalizuje „punkt wejściowy” programu w języku Kotlin. Program w Kotlinie może zawierać wiele funkcji o wielu różnych nazwach, ale to main() jest tą funkcją, która jest wywoływana automatycznie przy uruchamianiu programu.

Lista parametrów znajduje się za nazwą funkcji i jest ujęta w nawiasy. W tym przypadku nie przekazujemy niczego do funkcji main(), zatem lista parametrów jest pusta.

Ciało funkcji następuje po liście parametrów. Rozpoczyna się otwierającym nawiasem klamrowym ({) i kończy zamykającym nawiasem klamrowym (}). Ciało funkcji zawiera instrukcje i wyrażenia. Instrukcja powoduje wystąpienie jakiegoś efektu, zaś wyrażenie zwraca wynik.

EmptyProgram.kt nie zawiera w ciele funkcji żadnych instrukcji ani wyrażeń, jedynie komentarz.

Spowodujemy teraz, że program wyświetli tekst „Hello, world!”, dodając wiersz do ciała funkcji main():

// HelloWorld/HelloWorld.kt

fun main() {

println("Hello, world!")

}

/* Output:

Hello, world!

*/

Wiersz wyświetlający powitanie rozpoczyna się println(). Podobnie jak main(), println() jest funkcją. Wiersz ten wywołuje funkcję, która wykonuje swoje ciało. Podajemy nazwę funkcji, uzupełnioną nawiasami zawierającymi jeden lub więcej parametrów. W tej książce, ilekroć odwołujemy się do funkcji w tekście opisowym, dodajemy nawiasy po nazwie, dla przypomnienia, że chodzi nam o funkcję. W tym przypadku napiszemy println().

println() przyjmuje pojedynczy parametr, którym jest łańcuch (typ String). Parametr typu String definiujemy, umieszczając znaki tekstu pomiędzy znakami cudzysłowu.

println() przenosi kursor do następnego wiersza po wyświetleniu parametru, zatem następne dane wyjściowe programu pojawią się w kolejnym wierszu. Moglibyśmy zamiast tego użyć funkcji print(), która pozostawia kursor w tym samym wierszu.

W odróżnieniu od niektórych innych języków, w Kotlinie nie ma potrzeby sygnalizowania końca wyrażenia za pomocą średnika. Jest to konieczne jedynie wtedy, gdy umieszczamy więcej niż jedno wyrażenie w pojedynczym wierszu (czego nie zalecamy).

W niektórych przykładach w tej książce będziemy pokazywać na końcu listingu, wewnątrz komentarza wielowierszowego, wyjście (output) programu/funkcji. Komentarz wielowierszowy rozpoczyna się od sekwencji /* (ukośnik, po którym następuje gwiazdka) i biegnie dalej – włącznie z podziałami wierszy (które nazywamy nowymi liniami – newlines) – aż do wystąpienia sekwencji */ (gwiazdka uzupełniona przez ukośnik), która kończy komentarz:

/* Wielowierszowy komentarz

Nie zwraca uwagi

na podziały wiersza */

Możliwe jest dodanie kodu w tym samym wierszu po */ zamykających komentarz, ale może to być mylące, zatem zazwyczaj się tego nie robi.

Komentarze dodają informacje, które nie są oczywiste (nie wynikają wprost z czytanego kodu). Jeśli jedynie powtarzają to, co mówi sam kod, stają się irytujące i ludzie zaczynają je ignorować. Przy zmianach w kodzie programiści często zapominają o aktualizowaniu komentarzy, zatem dobrą praktyką jest rozważne i oszczędne stosowanie komentarzy, głównie dla wyróżniania trudniejszych aspektów naszego kodu.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

var oraz val

Gdy identyfikator przechowuje jakieś dane, musimy zdecydować, czy mogą one zostać przypisane ponownie.

Identyfikatory pozwalają odwoływać się do elementów w naszym programie. Najbardziej podstawową decyzją dotyczącą identyfikatora danych jest to, czy jego zawartość może ulec zmianie w trakcie działania programu, czy też może ona zostać przypisana tylko raz. Zachowanie to jest kontrolowane poprzez dwa słowa kluczowe:

• var, skrót od variable (zmienna), co oznacza, że możemy ponownie przypisać zawartość identyfikatora.

• val, skrót od value (wartość), co oznacza, że identyfikator można tylko zainicjować; nie będzie można go ponownie przypisać.

Zmienne (var) definiujemy w następujący sposób:

var identifier = initialization

Po słowie kluczowym var następuje identyfikator, znak równości i wartość początkowa (inicjująca). Identyfikator musi rozpoczynać się literą lub znakiem podkreślenia, po czym mogą nastąpić litery, cyfry i znaki podkreślenia. Rozróżniana jest wielkość liter (tak więc thisvalue oraz thisValue są różnymi identyfikatorami).

Oto kilka przykładów definicji var:

// VarAndVal/Vars.kt

fun main() {

var whole = 11 // [1]

var fractional = 1.4 // [2]

var words = "Twas Brillig" // [3]

println(whole)

println(fractional)

println(words)

}

/* Output:

11

1.4

Twas Brillig

*/

W tej książce będziemy niekiedy oznaczać wiersze wykomentowanymi numerami w nawiasach kwadratowych, dzięki czemu łatwiej odwoływać się do nich w tekście, jak poniżej:

• [1] Tworzymy zmienną o nazwie whole i zapisujemy w niej liczbę 11.

• [2] Zapisujemy „liczbę ułamkową” 1.4 w zmiennej fractional.

• [3] Zapisujemy jakiś tekst (String) w zmiennej words.

Zauważmy, że funkcja println() może przyjąć, jako argument, dowolną oraz pojedynczą wartość.

Jak sugeruje określenie „zmienna”, zmienne var mogą ulegać zmianie. Innymi słowy, możemy zmienić dane przechowywane w zmiennej. Mówimy, że var jest modyfikowalna (mutowalna):

// VarAndVal/AVarIsMutable.kt

fun main() {

var sum = 1

sum = sum + 2

sum += 3

println(sum)

}

/* Output:

6

*/

Przypisanie sum = sum + 2 używa bieżącej wartości zmiennej sum, dodaje do niej dwa i przypisuje wynik z powrotem do zmiennej sum.

Przypisanie sum += 3 oznacza to samo co sum = sum + 3. Operator += przyjmuje poprzednią wartość przechowywaną w sum i zwiększa ją o 3, po czym przypisuje nowy wynik z powrotem do zmiennej sum.

Zmienianie wartości przechowywanych w zmiennych var jest użyteczną metodą wyrażania zmian. Jednak w miarę wzrostu złożoności programu nasz kod będzie bardziej przejrzysty, bezpieczniejszy i łatwiejszy do zrozumienia, jeśli pewne wartości reprezentowane przez nasze identyfikatory nie będą mogły się zmieniać – innymi słowy, nie będzie możliwe ich ponowne przypisanie. Niezmienny (stały) identyfikator specyfikujemy za pomocą słowa kluczowego val, zamiast var. Wartość val może zostać przypisana tylko raz, w czasie tworzenia:

val identifier = initialization

Słowo kluczowe val pochodzi od value (wartość), sygnalizując coś, czego nie można modyfikować – jest niezmienne. Powinniśmy używać val zamiast var, ilekroć jest to możliwe. Przykład Vars.kt z początku tego atomu można przepisać, używając stałych val:

// VarAndVal/Vals.kt

fun main() {

val whole = 11 // [1]

// whole = 15 // Błąd // [1]

val fractional = 1.4 // [2]

val words = "Twas Brillig" // [3]

println(whole)

println(fractional)

println(words)

}

/* Output:

11

1.4

Twas Brillig

*/

• [1] Gdy już zainicjujemy identyfikator val, nie można go przypisać ponownie. Jeśli spróbujemy ponownie przypisać identyfikatorowi whole inną liczbę, Kotlin odmówi, informując, że „Val cannot be reassigned” (identyfikatorów val nie można ponownie przypisywać).

Wybieranie opisowych nazw identyfikatorów sprawia, że łatwiej można zrozumieć nasz kod, i często zmniejsza potrzebę stosowania komentarzy. W pliku Vals.kt nie mamy pojęcia, co reprezentuje identyfikator whole. Jeśli nasz program przechowuje liczbę 11, aby określić czas, gdy udajemy się na kawę, będzie to znacznie bardziej zrozumiałe dla innych, jeśli użyjemy nazwy coffeetime, a czytelność poprawimy, używając nazwy coffeeTime (zgodnie ze stylem Kotlin, nazwy rozpoczynamy małą literą).

• -

Zmienne var są użyteczne, gdy dane muszą się zmieniać w trakcie działania programu. Może się to wydawać typowym wymaganiem, ale jak się okazuje, w praktyce powinniśmy starać się tego unikać. W ogólności nasze programy będą łatwiejsze w rozbudowie i utrzymywaniu, jeśli będziemy używać val. Jednak zdarzają się (niezbyt często) sytuacje, gdy rozwiązanie problemu przy użyciu samych stałych jest zbyt złożone. Z tego względu Kotlin udostępnia elastyczność zmiennych var. Gdy jednak spędzimy więcej czasu przy pracy z niemutowalnymi zmiennymi, odkryjemy, że niemal nigdy nie potrzebujemy mutowalnych zmiennych, a bez nich nasze programy są bezpieczniejsze i bardziej niezawodne.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

Typy danych

Dane mogą mieć różne typy.

Aby rozwiązać problem arytmetyczny, napiszemy wyrażenie:

5.9 + 6

Wiemy, że dodawanie liczb tworzy inną liczbę. Kotlin również to wie. Wiemy także, że jedna z nich jest liczbą ułamkową (5.9), co w Kotlinie nazwiemy Double, a druga jest liczbą całkowitą (6), co Kotlin nazywa Int. Wiemy wreszcie, że wynik jest liczbą ułamkową.

Typ (nazywany również typem danych) informuje Kotlin, jak zamierzamy używać tych danych. Udostępnia zbiór danych, spośród których wyrażenie może przybierać wartości. Definiuje operacje, które można wykonywać na danych, znaczenie tych danych, a także to, jak można przechowywać wartości tego typu.

Kotlin używa typów do weryfikowania, że nasze wyrażenia są poprawne. W przypadku wyrażenia powyżej Kotlin utworzy nową wartość typu Double do przechowania wyniku.

Kotlin próbuje dostosować się do naszych potrzeb. Jeśli jednak zażądamy wykonania czegoś, co łamie reguły, wygeneruje komunikat o błędzie. Dla przykładu spróbujmy dodać do siebie łańcuch tekstowy (String) oraz liczbę:

// DataTypes/StringPlusNumber.kt

fun main() {

println"Sall" + 5.9)

}

/* Output:

Sally5.9

*/

Typy informują Kotlin, jak należy ich poprawnie używać. W tym przypadku reguły typów mówią Kotlinowi, jak dodać liczbę do łańcucha: złączając obie wartości i tworząc nowy String dla przechowania wyniku.

Spróbujmy teraz pomnożyć dane typu String oraz Double, zamieniając + w przykładzie StringPlusNumber.kt na *:“”

"Sally" * 5.9

Połączenie typów w taki sposób nie ma żadnego sensu dla języka Kotlin, zatem otrzymamy błąd.

W atomie „var oraz val” zapisywaliśmy kilka typów. Kotlin ustalił za nas, jakie są to typy, opierając się na ich użyciu. Takie zachowanie nosi nazwę wnioskowania o typie (type inference).

Możemy jednak być bardziej dosłowni i jawnie określić typ:

val identifier: Type = initialization

Zaczynamy od słowa kluczowego val albo var, po którym następuje identyfikator, dwukropek, określenie typu, znak = i, na koniec wartość inicjująca. Tak więc zamiast pisać:

val n = 1

var p = 1.2

możemy powiedzieć:

val n: Int = 1

var p: Double = 1.2

W tym przykładzie informujemy Kotlin, że n jest typu Int, a p jest typu Double, zamiast pozwalać mu wywnioskować typ.

Oto kilka podstawowych typów języka Kotlin:

// DataTypes/Types.kt

fun main() {

val whole: Int = 11 // [1]

val fractional: Double = 1.4 // [2]

val trueOrFalse: Boolean = true // [3]

val words: String = "Wartość" // [4]

val character: Char = 'z' // [5]

val lines: String = """Potrójne cudzysłowy

pozwalają umieścić w łańcuchu

wiele wierszy""" // [6]

println(whole)

println(fractional)

println(trueOrFalse)

println(words)

println(character)

println(lines)

}

/* Output:

11

1.4

true

Wartość

z

Potrójne cudzysłowy

pozwalają umieścić w łańcuchu

wiele wierszy

*/

• [1] Typ danych Int to liczba całkowita (ang. integer), co oznacza, że może przechowywać tylko liczby całkowite (nie ułamki).

• [2] Do przechowywania liczb ułamkowych używamy typu Double.

• [3] Typ Boolean (logiczny) może przechowywać tylko dwie wartości specjalne: true (prawda) oraz false (fałsz).

• [4] Typ String (łańcuch) przechowuje sekwencję znaków. Wartość można przypisać, ujmując łańcuch znaków w znaki podwójnego cudzysłowu.

• [5] Typ Char (character) może przechować pojedynczy znak.

• [6] Jeśli mamy dłuższy tekst obejmujący wiele wierszy i/lub znaków specjalnych, można go obramować trzema podwójnymi cudzysłowami (tzw. potrójnie cytowany łańcuch – triple-quoted String).

Kotlin używa wnioskowania o typach do ustalania znaczenia mieszanek typów. Na przykład przy połączeniu Int z Double, w trakcie dodawania Kotlin, rozstrzyga się, jaki jest typ wynikowej wartości:

// DataTypes/Inference.kt

fun main() {

val n = 1 + 1.2 println(n)

}

/* Output:

2.2

*/

Gdy dodajemy wartości typu Int do Double z wykorzystaniem wnioskowania typu, Kotlin ustala, że wynikowa wartość n ma typ Double i gwarantuje, że spełnia ona wszystkie reguły dotyczące liczb tego typu.

Wnioskowanie typów w Kotlinie jest częścią strategii wykonywania pracy w zastępstwie programisty. Jeśli pominiemy deklarację typu, Kotlin zazwyczaj zdoła go wywnioskować.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

Funkcje

Funkcja jest czymś w rodzaju małego programu, który ma własną nazwę i można go uruchomić (wywołać) poprzez przywołanie tej nazwy w innej funkcji.

Funkcja stanowi połączenie pewnej grupy działań i jest najbardziej podstawowym sposobem organizowania programów i ponownego wykorzystania kodu.

Informacje przekazujemy do funkcji, która używa tych informacji do przeprowadzenia obliczeń i wytworzenia wyniku. Podstawową formą funkcji jest:

fun functionName(p1: Type1, p2: Type2, ...): ReturnType {

lines of code

return result

}

p1 oraz p2 są parametrami: to informacje, które przekazujemy do funkcji. Każdy parametr ma identyfikującą go nazwę (p1, p2), po której następuje dwukropek oraz oznaczenie typu tego parametru. Po zamykającym nawiasie listy parametrów znajduje się dwukropek, a następnie typ wyniku zwracanego przez funkcję. Wiersze kodu, stanowiące ciało funkcji, są ujęte w nawiasy klamrowe. Wyrażenie następujące po słowie kluczowym return to rezultat, który tworzy funkcja po ukończeniu działania.

Parametr definiuje to, co przekazujemy do funkcji – jest to symbol zastępczy. Natomiast rzeczywistą wartość przekazywaną do funkcji nazywamy argumentem.

Połączenie nazwy, parametrów i typu zwracanego nazywamy sygnaturą funkcji.

Oto prosta funkcja o nazwie multiplyByTwo():

// Functions/MultiplyByTwo.kt

fun multiplyByTwo(x: Int): Int { // [1]

println("Inside multiplyByTwo") // [2]

return x * 2

}

fun main() {

val r = multiplyByTwo(5) // [3]

println(r)

}

/* Output:

Inside multiplyByTwo

10

*/

• [1] Zwróćmy uwagę na słowo kluczowe fun, nazwę funkcji oraz listę parametrów złożoną z jednego parametru. Ta funkcja przyjmuje parametr typu Int i zwraca wartość typu Int.

• [2] Te dwa wiersze to ciało funkcji. Ostatni wiersz zwraca wartość obliczenia x * 2 jako wynik funkcji.

• [3] Ten wiersz wywołuje funkcję z odpowiednim argumentem i przechwytuje wynik do val r. Wywołanie funkcji naśladuje formę jej deklaracji: nazwa funkcji, po której następują argumenty umieszczone w nawiasach.

Kod funkcji jest wykonywany poprzez wywołanie funkcji za pomocą jej nazwy multiplyByTwo() – nazwę traktujemy jako skrót tego kodu. To dlatego funkcje stanowią w programowaniu najbardziej podstawową formę upraszczania i ponownego wykorzystania kodu. Można je również traktować jako wyrażenia, w których dokonuje się podstawień wartości (parametrów).

println() również jest wywołaniem funkcji – tym razem jest ona dostarczana przez Kotlin. Funkcje definiowane przez język Kotlin nazywamy funkcjami bibliotecznymi.

Jeśli funkcja nie udostępnia rezultatu mającego jakieś znaczenie, jej typem zwracanym jest Unit. Typ Unit można wyspecyfikować jawnie, jeśli chcemy, ale Kotlin pozwala go pominąć:

// Functions/SayHello.kt

fun sayHello() {

println("Hallo!")

}

fun sayGoodbye(): Unit {

println("Auf Wiedersehen!")

}

fun main() {

sayHello()

sayGoodbye()

}

/* Output:

Hallo!

Auf Wiedersehen!

*/

Obie funkcje sayHello() i sayGoodbye() zwracają Unit, ale sayHello() pomija jawną deklarację. Zauważmy, że funkcja main() również zwraca Unit.

Jeśli funkcja jest tylko pojedynczym wyrażeniem, można użyć skrótowej składni zawierającej znak równości, po którym następuje to wyrażenie:

fun functionName(arg1: Type1, arg2: Type2, ...): ReturnType = wyrażenie

Ciało funkcji obramowane nawiasami klamrowymi nazywane jest ciałem blokowym, natomiast wykorzystujące składnię ze znakiem równości nazywamy ciałem wyrażeniowym.

W poniższym przykładzie funkcja multiplyByThree() używa ciała wyrażeniowego:

// Functions/MultiplyByThree.kt

fun multiplyByThree(x: Int): Int = x * 3

fun main() {

println(multiplyByThree(5))

}

/* Output:

15

*/

Jest to skrótowa wersja użycia sformułowania return x * 3 wewnątrz ciała blokowego. Kotlin dedukuje typ zwracany przez funkcję, która używa ciała wyrażeniowego:

// Functions/MultiplyByFour.kt

fun multiplyByFour(x: Int) = x * 4

fun main() {

val result: Int = multiplyByFour(5)

println(result)

}

/* Output:

20

*/

W tym przypadku Kotlin wnioskuje, że funkcja multiplyByFour() zwraca typ Int.

Kotlin może dedukować typy zwracane jedynie dla ciał wyrażeniowych. Jeśli funkcja ma ciało blokowe i pominiemy typ zwracany, funkcja taka zwraca Unit.

• -

Przy pisaniu funkcji należy wybierać zrozumiałe, opisowe nazwy. Ułatwia to czytanie kodu, a często może zmniejszyć potrzebę stosowania komentarzy. W tej książce nie zawsze jesteśmy w stanie stosować tak opisowe nazwy, jak byśmy chcieli, ze względu na ograniczenia wynikające z długości wiersza.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

Wyrażenia if

Wyrażenie if umożliwia dokonywanie wyboru.

Słowo kluczowe if testuje podane wyrażenie i sprawdza, czy jest ono prawdą, czy fałszem, po czym wykonuje działanie zależnie od tego wyniku. Wyrażenie „prawda lub fałsz” nazywane jest wyrażeniem logicznym (boolowskim – ang. Boolean). Określenie typu Boolean pochodzi od nazwiska matematyka George’a Boole’a, który był twórcą logiki kryjącej się za tymi wyrażeniami. Oto przykład wykorzystujący symbole > (większe niż) oraz < (mniejsze niż):

// IfExpressions/If1.kt

fun main() {

if (1 > 0)

println("It's true!")

if (10 < 11) {

println("10 < 11")

println("ten is less than eleven")

}

}

/* Output:

It's true!

10 < 11

ten is less than eleven

*/

Wyrażenia wewnątrz nawiasów po słowie kluczowym if muszą dawać w wyniku true albo false. Jeśli wynikiem jest true, wykonywane jest następne wyrażenie. Aby stworzyć wiele wierszy kodu, należy umieścić je w nawiasach klamrowych.

Możemy utworzyć wyrażenie logiczne w jednym miejscu i użyć go w innym:

// IfExpressions/If2.kt

fun main() {

val x: Boolean = 1 >= 1

if (x)

println("It's true!")

}

/* Output:

It's true!

*/

Jako że x jest typu Boolean (logicznego), możliwe jest jego bezpośrednie testowane przez if, poprzez składnię if(x).

Operator logiczny >= zwraca wartość true, jeśli wyrażenie po lewej stronie operatora jest większe lub równe wyrażeniu po prawej stronie. Analogicznie <= zwróci true, gdy wyrażenie po lewej stronie jest mniejsze lub równe wyrażeniu po prawej stronie.

Słowo kluczowe else pozwala na obsłużenie obydwu ścieżek – dla wartości true i false:

// IfExpressions/If3.kt

fun main() {

val n: Int = -11

if (n > 0)

println("It's positive")

else

println("It's negative or zero")

}

/* Output:

It's negative or zero

*/

Słowo kluczowe else może być używane tylko w połączeniu z if. Nie jesteśmy przy tym ograniczeni do pojedynczego sprawdzenia – można testować wiele kombinacji, łącząc ze sobą else oraz if:

// IfExpressions/If4.kt

fun main() {

val n: Int = -11

if (n > 0)

println("It's positive")

else if (n == 0)

println("It's zero")

else

println("It's negative")

}

/* Output:

It's negative

*/

W tym przykładzie użyliśmy symbolu == w celu sprawdzenia równości dwóch liczb. Symbol != testuje nierówność liczb.

Typowy wzorzec polega na rozpoczęciu od if, po czym następuje tyle klauzul else if, ile potrzebujemy, a na końcu finalna klauzula else dla dowolnej wartości, która nie pasowała do żadnego z wcześniejszych testów. Gdy wyrażenie if osiąga pewien poziom złożoności, zapewne będziemy zamiast niego woleli użyć wyrażenia when, które jest opisane w dalszej części książki w atomie „Wyrażenia when”.

Operator negacji ! pozwala testować przeciwieństwo wyrażenia logicznego:

// IfExpressions/If5.kt

fun main() {

val y: Boolean = false

if (!y)

println("!y is true")

}

/* Output:

!y is true

*/

Wyrażenie if(!y) możemy przeczytać jako "jeżeli nie y".

Cała konstrukcja if jest wyrażeniem, zatem może tworzyć wynik:

// IfExpressions/If6.kt

fun main() {

val num = 10

val result = if (num > 100) 4 else 42

println(result)

}

/* Output:

42

*/

W tym przykładzie przechowaliśmy wartość tworzoną przez całe wyrażenie if w pośrednim identyfikatorze o nazwie result (wynik). Jeśli warunek jest spełniony, wynik jest tworzony przez pierwszą gałąź kodu. Jeśli nie, wynikiem jest wartość gałęzi else.

Przećwiczmy teraz tworzenie funkcji. Oto przykład funkcji przyjmującej parametr logiczny (Boolean):

// IfExpressions/TrueOrFalse.kt

fun trueOrFalse(exp: Boolean): String {

if (exp)

return "It's true!" // [1]

return "It's false" // [2]

}

fun main() {

val b = 1

println(trueOrFalse(b < 3))

println(trueOrFalse(b >= 3))

}

/* Output:

It's true!

It's false

*/

Parametr logiczny exp jest przekazywany do funkcji trueOrFalse(). Jeżeli przekazywanym argumentem jest wyrażenie, takie jak b < 3, najpierw jest obliczane to wyrażenie, po czym jego wynik jest przekazywany do funkcji. trueOrFalse() testuje wartość exp i jeśli wynikiem jest true, wykonywany jest wiersz [1], a w przeciwnym razie wiersz [2].

• [1] Słowo kluczowe return nakazuje „Opuść funkcję i utwórz jej wartość jako wynik funkcji”. Zauważmy, że return może pojawić się w dowolnym miejscu funkcji i nie musi znajdować się na jej końcu.

Zamiast używania return, jak w poprzednim przykładzie, możemy wykorzystać słowo kluczowe else w celu uzyskania wyniku jako wyrażenia:

// IfExpressions/OneOrTheOther.kt

fun oneOrTheOther(exp: Boolean): String =

if (exp)

"True!" // Nie jest potrzebne 'return'

else

"False"

fun main() {

val x = 1

println(oneOrTheOther(x == 1))

println(oneOrTheOther(x == 2))

}

/* Output:

True!

False

*/

Zamiast dwóch wyrażeń, jak w trueOrFalse(), funkcja oneOrTheOther() jest pojedynczym wyrażeniem. Rezultatem tego wyrażenia jest wynik funkcji, zatem wyrażenie if staje się ciałem funkcji.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

Szablony łańcuchów

Szablon łańcucha to programowy sposób generowania łańcuchów (String).

Jeśli umieścimy znak $ przed nazwą identyfikatora, szablon łańcucha wstawi zawartość tego identyfikatora do typu String:

// StringTemplates/StringTemplates.kt

fun main() {

val answer = 42

println("Found $answer!") // [1]

println("printing a $1") // [2]

}

/* Output:

Found 42!

printing a $1

*/

• [1] $answer zastępowane jest wartością answer.

• [2] Jeśli to, co następuje po znaku $, nie jest rozpoznawalne jako identyfikator programowy, nic specjalnego się nie dzieje.

Wartości do łańcucha można wstawiać również za pomocą konkatenacji (+):

// StringTemplates/StringConcatenation.kt

fun main() {

val s = "hi\n" // \n to znak końca wiersza (nowej linii)

val n = 11

val d = 3.14

println("first: " + s + "second: " + n + ", third: " + d)

}

/* Output:

first: hi

second: 11, third: 3.14

*/

Umieszczenie wyrażenia wewnątrz ${} powoduje jego obliczenie. Zwracana wartość jest konwertowana na typ String i wstawiana do wynikowego łańcucha:

// StringTemplates/ExpressionInTemplate.kt

fun main() {

val condition = true

println(

"${if (condition) 'a' else 'b'}") // [1]

val x = 11

println("$x + 4 = ${x + 4}")

}

/* Output:

a

11 + 4 = 15

*/

• [1] Wyrażenie if(condition) 'a' else 'b' jest obliczane i jego wynik jest podstawiany w miejsce całego wyrażenia ${}.

Kiedy łańcuch musi zawierać znak specjalny, taki jak cudzysłów, możemy albo poprzedzić go znakiem ucieczki \ (odwrotny ukośnik), albo użyć literału String w potrójnych cudzysłowach:

// StringTemplates/TripleQuotes.kt

fun main() {

val s = "value"

println("s = \"$s\".")

println("""s = "$s".""")

}

/* Output:

s = "value". s = "value".

*/

Przy używaniu potrójnych cudzysłowów wartość wyrażenia wstawiamy w taki sam sposób, jak w przypadku zwykłego łańcucha w pojedynczych cudzysłowach.

Ćwiczenia i rozwiązania można znaleźć na stronie www.AtomicKotlin.com.

Przypisy

[1] http://www.Will-Harris.com

[2] https://kotlinlang.org/foundation/kotlin-foundation.html

[3] https://github.com/BruceEckel/AtomicKotlinExamples

[4] http://AtomicKotlin.com

[5] http://AtomicKotlin.com/exercises/

[6] https://www.jetbrains.com/help/education/install-edutools-plugin.html

[7] https://www.jetbrains.com/help/education/learner-start-guide.html?section=Atomic%20Kotlin

[8] https://www.jetbrains.com/help/education/troubleshooting-guide.html

[9] https://youtrack.jetbrains.com/newIssue?project=EDC&summary=AtomicKotlin%3A&c=Subsystem%20Kotlin&c=

[10] http://www.WinterTechForum.com

[11] http://www.atomicscala.com/

[12] http://www.WinterTechForum.com

[13] http://www.BruceEckel.com

[14] https://www.mindviewllc.com/

[15] http://www.will-harris.com

[16] Język Visual Basic for Applications – VBA (czyli język skryptowy pakietu Office) nie został napisany w języku BASIC. VBA przypomina BASIC jedynie nazwą – jest to język obiektowy, a nie proceduralny (przyp. tłum.).

[17] Wydanie polskie: Thinking in C++. Edycja polska, Helion 2002.

[18] Wydanie polskie: JavaScript – mocne strony, Helion 2011.

[19] http://www.AtomicScala.com

[20] Chodzi po prostu o null (przyp. tłum.).

[21] https://source.android.com/devices/tech/dalvik