Въведение в Kotlin (за Java програмисти)

През последните няколко месеца на работа ползвах езика за програмиране Kotlin и реших да споделя какво научих за него и как може да ти бъде полезен, ако си Java програмист. 😎

Kotlin всъщност е доста свързан с Java. Даже в днешно време все повече и повече екипи избират да ползват него вместо Java в различни софтуерни проекти (причините за това ще обясня надолу). Ако искаш да станеш по-динамичен програмист (като в същото време не излизаш от “Java сферата”), според мен си заслужава да го научиш. 😉

Сега ще разгледаме:

  1. Какво е Kotlin (и как може да ти помогне)
  2. Защо не трябва да се плашиш от Kotlin
  3. Как да използваш Kotlin (някои основни и готини възможности на езика)
  4. Как да продължиш от тук нататък (полезни ресурси и съвети)

1. Какво е Kotlin (и как може да ти помогне)

Kotlin е език за програмиране, който работи на JVM (Java виртуалната машина). Проектиран е от JetBrains, които най-вероятно са ти познати от това, че са създали средата за разработка IntelliJ IDEA (която вероятно и ползваш за разработка на Java приложения). Имайки това предвид, сигурно може да се досетиш, че всъщност ползвайки Kotlin няма да се отдалечиш твърде много от вече познатите ти неща от Java.

Най-често Kotlin се използва за разработване на Android приложения (Google го предпочитат пред Java). Обаче всъщност езикът намира все по-голямо приложение и в web програмирането, най-вече в backend частта. Изненадващо може да се ползва дори за frontend, а даже и за desktop приложения!

Kotlin има “оперативна съвместимост” (interoperability) с Java. Технически казано, Kotlin генерира Java-съвместим bytecode. Това означава, че сравнително лесно можеш да преобразуваш Java код в Kotlin (и обратно), а също така може да ползваш Kotlin библиотеки в Java проектите си.

Обаче по-важното: може да използваш Java код (не бъркай със синтаксис 😅) в Kotlin приложенията си! Това означава, че все още може да ползваш вече познатите ти Java библиотеки, frameworks, build инструменти и т.н.

A как точно може да ти помогне Kotlin?

Основните технически ползи са, че пишеш по-малко (и по-прост) код, а в същото време все още може да ползваш това, което Java ти предоставя.

Личните ползи за теб са, че научаваш нов модерен език за програмиране, а в същото време растеш като Java програмист (дори и да пишеш на Kotlin): надолу ще обясня какво имам предвид под това.

2. Защо не трябва да се плашиш от Kotlin

В човешката природа е да се страхуваме от неизвестното и да искаме да си продължим както сме си карали досега. Ако си свикнал с Java, най-вероятно не ти се минава на друг език за програмиране. И аз бях така… Спомням си, че имах мисли от рода на:

а) “Ъъъ, не знам дали ще успея да науча цял нов език, ДОКАТО съм на работа и докато трябва да допринасям в проекта… 😯”.

Представи си все още да нямаш никакъв професионален опит (тепърва започваш първата си работа) и ти казват, че ще започнеш да работиш по проект написан на Java. Започваш, но накрая се оказва, че трябва да превключиш на друг език за програмиране, който ти е напълно непознат. Точно такъв беше случаят ми. Едно е да имаш време, в което бавно и спокойно можеш да научиш някоя нова технология (или език), друго е да трябва да побързаш, защото се очаква от теб да започнеш да допринасяш в проекта.

б) “Не искам да се отдалечавам от Java… искам да придобия опит като Java програмист, а не като Kotlin програмист!”

Ако си решил да специализираш с Java, едва ли имаш интерес да минаваш към друг език. Инвестирал си бая време в изучаване на Java и искаш да си конкурентоспособен като Java програмист, a сега ти казват “Айде научи това и забрави за Java-та!”? Най-вероятно няма да искаш да го направиш.

в) “Страхувам се, че ще забравя Java и ще съм зле, когато спра да ползвам Kotlin… 😱“

Колкото и да си добър в някой език или технология за програмиране, ако не ги използваш известно време, малко или много ги забравяш и/или вече не знаеш за всичките нововъведения в тях. Ако си се занимавал дълго време с Java, не искаш всичкият ти труд да е бил напразно, а не искаш и след това да си изостанал и да не знаеш какви са новите функционалности в JDK (или пък да забравиш старите).

Разбирам всичко това. Обаче, след като ползвах Kotlin за няколко месеца, мога да кажа, че определено си заслужаваше научаването му.

Ето няколко причини защо…

2.1. Научаването на Kotlin (като Java програмист) беше сравнително бързо и лесно

Първо трябва да спомена, че аз самият съм на ниво Junior, така че не бих казал, че дори и в Java съм експерт! 😅 Все пак ми бяха нужни само около 2–3 кратки “crash” курса в YouTube и около 2–3 седмици практика, експериментиране, четене и проучване, за да започна да се чувствам комфортно с основите на Kotlin. Имайки това предвид, ако си с повече опит, може да го научиш и по-бързо от мен!

След като свикнах с основите, бавно започнах да понаучавам и по-сложните възможности на езика (и до ден днешен откривам по някои нови неща).

Два полезни ресурса са този безплатен crash курс в YouTube (линк има най-отдолу) и документацията на Kotlin.

2.2. Използването на Kotlin (като Java програмист) също е сравнително лесно (и удобно!)

Както споменах горе, основната полза е, че пишеш по-малко код (виж картинката отдолу за малък preview на това).

Синтаксисът е малко по-различен от този на Java, но пак не е особено труден за разбиране и ползване (след като си се упражнявал малко в него).

Има много налична документация и примери, които може да използваш, когато забиеш някъде (а понякога дори някое намерено Java решение може да ти помогне, защото преобразуването на Java в Kotlin е сравнително просто).

Интересното е, че след известно време даже не искаш да се връщаш към Java, защото Kotlin прави имплементирането на нови функционалности доста по-просто. 😅

2.3. Всъщност не “забравяш” Java (поне според моя личен опит)

Забелязвам, че всъщност не се чувствам “ръждясал”, когато превключвам към Java. Например през свободното си време понякога решавам алгоритмични задачи в сайтове като LeetCode (за да поддържам уменията си), а там използвам Java. И това, което наблюдавам, е, че дори през повечето време да ползвам Kotlin, връщането към Java не е болезнено. 😅

Обикновено когато превключа към нов език за програмиране, малко или много започвам да забравям част от синтаксиса и имената на някои често използвани класове/методи в предишния език… Например си спомням, че в университета имахме някои курсове със C++, а други бяха с Java. Ако се бях хванал да пиша предимно на C++, забравях Java, а ако бях фокусиран върху Java, позабравях възможностите на C++.

Когато ползвах Kotlin, обаче, не изпитвах това явление често, защото голяма част от кода, който пишеш на Kotlin, e свързан с Java.

2.4. Растеш като Java програмист (макар и да пишеш на Kotlin)

Защо? Защото все още си оставаш в “Java сферата”:

а) Използваш същото IDE (най-вероятно).

Като се има предвид, че Kotlin e творение на JetBrains, съответно и поддръжката на IntelliJ за Kotlin e чудесна. Също така, както споменах вече, можеш и лесно да конвертираш Java код в Kotlin и наобратно (надолу ще покажа как става). Все пак да, минус е, че ако си с Eclipse или някое друго IDE, най-вероятно ще ти е по-трудно да програмираш на Kotlin. Обаче при всички случаи ще задълбаеш в някои вече познати ти инструменти или поне инструменти, които се ползват и за Java програмиране.

б) Използваш същите frameworks, библиотеки, build инструменти и т.н.

Можеш да ползваш познати от Java технологии като Spring, Maven, Hibernate, JUnit и други. Ако трябва да съм напълно честен, открих, че някои библиотеки не работят на 100% с Kotlin (доколкото си спомням, имаше някакви проблеми с MapStruct и Lombok), но дори и в тези случаи съществува заместител или поне някакъв начин да получиш резултата, който искаш.

в) Все още четеш Java код често (докато преглеждаш кода на някоя библиотека и докато търсиш различни решения в StackOverflow)

Тъй като често ще използваш Java код в Kotlin приложенията си, най-вероятно също ще ЧЕТЕШ такъв код периодично. Може например да погледнеш как е имплементирана някоя част от Java библиотеката, която използваш. Или пък може би получаваш някаква грешка и откриваш Java решения в StackOverflow, които след това нагаждаш за твоя случай, за да можеш да продължиш напред… Както и да го погледнеш, почти сигурно е, че ако пишеш на Kotlin, няма да избягаш напълно от Java.

Като цяло възприемам научаването на Kotlin сякаш просто РАЗШИРЯВАШ знанията си в Java сферата. Не е като да минеш на C# например, защото там трябва да използваш различен framework, различни инструменти и т.н. Вместо това е просто като да научиш някоя нова технология свързана с Java.

3. Как да използваш Kotlin (основи на езика и готини възможности)

Имай предвид, че най-добрият начин да научиш Kotlin е да ЕКСПЕРИМЕНТИРАШ с него. Колкото повече се упражняваш, толкова повече всичко започва да придобива смисъл в главата ти и започваш да го разбираш (само да четеш и да гледаш клипове няма да е достатъчно).

Идеята на този раздел е просто да ти покаже една част от това, което Kotlin предлага (и потенциално да те “зариби” да го пробваш 😅).

На моменти допускам някои грешки в техническите си обяснения (все още нямам много опит в такъв тип статии/уроци и затова се извинявам предварително), но се надявам да разбереш поне основната идея.

3.1. Основи на езика

3.1.1. Променливи (variables)

Ако в Java използваме например final int /int или final String / String, то в Kotlin използваме val и var. Тези две ключови думички ще ги срещаш отново и отново в Kotlin кода, който четеш и пишеш.

Също забележи, че няма нужда от точка и запетая (;) в края на някой израз (expression). Може и да сложиш, но най-често се избягва.

Освен това е важно да се отбележи, че в Kotlin нямаме примитивни типове както в Java (int, long, char и т.н.).

Може да видиш също, че за разлика от Java, типът на променливите се изписва след тяхното име (често изписването на типа всъщност може да бъде пропуснато изцяло, благодарение на т.нар. “type inference”, но за това ще спомена малко по-надолу).

3.1.2. Функции (functions)

Функциите в Kotlin наподобяват тези в Java, но имат някои особености.

Пример за една елементарна функция е sum(). Всяка функция започва с ключовата дума fun. Параметрите са разделени със запетайка, а след това се изписва какво се очаква да върне функцията (в случая Int). Самото връщане на резултата става по същия начин както в Java (с return).

Интересна особеност са void функциите, които в Kotlin всъщност също връщат стойност (от тип Unit), но при тях може и да не пишеш изрично какъв е очакваният return type.

Една друга подробност е, че Kotlin предоставя свои варианти на често използвани функции в Java. Пак можеш да използваш и съществуващите Java функции, но обикновено Kotlin вариантът е предпочитан (например виж в горния пример как можеш да извеждаш текст в конзолата).

3.1.3. Масиви, списъци и т.н.(arrays, lists, etc.)

В Kotlin инициализирането на масиви, списъци и така нататък, е малко по-различно.

Ако например в Java използваме new int[] {} за инициализация на масив от цели числа, то в Kotlin използваме специален метод intArrayOf(), в който можем да подадем колкото искаме елементи. Подобни методи има и за символи, символни низове, byte и т.н. Ако искаш да инициализираш масив от някой твой тип, използваш arrayOf<MyType>().

Друга малка подробност е, че в Kotlin полето за размера на масива се казва size, a не length както в Java.

Интересно е, че също така може да достъпваш и поставяш стойности на елементи от масива чрез get() и set() методи. Пак е предпочитано да използваш доброто старо директно достъпване по индекс (arr[0] , arr[1] = 5), но все пак имаш възможността да използваш и другия начин.

При списъците (а и при другите структури от данни) ситуацията е много сходна. Отново използваме специални методи за инициализация. listOf() ни дава read-only списък, a mutableListOf() такъв, който можем да модифицираме (видът на списъка е този, който Kotlin дава по подразбиране: към днешна дата това е ArrayList; можем и изрично да поискаме ArrayList чрез arrayListOf() или даже чрез ArrayList()).

Любопитно е, че при списъците можем да използваме достъпване по индекс и не ни се налага да ползваме get() и set(). Всъщност в Kotlin това е и предпочитаният начин за достъпване и манипулиране на стойности на елементи в списъци (т.е. по-добре използвай myList[0] и myList[1] = 123, дори и да не боравиш с масив 😅).

3.1.4. Класове, getters & setters, конструктори и т.н. (classes, getters & setters, constructors, etc.)

В Kotlin е нужно да добавим ключовата дума open, ако искаме някой наш клас да бъде наследим. И докато в Java използваме extends(а при интерфейсите implements), в Kotlin използваме просто :.

Друга интересна особеност е, че в Kotlin автоматично получаваме getter & setter за нашите член-променливи. А когато достъпваме някоя член-променлива, всъщност не я достъпваме директно, а използваме нейния автоматично генериран getter.

На практика става така, че ако имаме член-променлива number, няма да използваме нещо като getNumber() и setNumber(int number), a просто ще я достъпим с number и ще й присвоим стойност с number = 123.

Ако имаш нужда, можеш да промениш съдържанието на getter/setter за дадена променлива, както е показано горе (изглежда малко странно, но самият начин на ползване е прост: просто пишеш какво трябва да върне getter-ът и каква стойност да постави setter-ът). 😅

Освен автоматично генерирани getters & setters, Kotlin има възможност да предостави и автоматично генериран конструктор с дадени параметри. Просто се изброяват параметрите при декларацията на класа (забележи, че в горния пример number и str са извън тялото на класа), а след това имаме генериран конструктор, чрез който можем да направим инициализацията.

Забележи също, че в Kotlin не използваме new при създаване на обекти.

Една малка особеност е, че в Kotlin синтаксисът при използването на инстанция на анонимен клас е малко по-различен, а също и начинът по-който правим override на някой метод.

Например в Java, когато искаме да използваме собствен Comparator за сортиране на масив, използваме new Comparator<MyType>() { }, като в тялото override-ваме метода, който сравнява два елемента, и поставяме @Override анотация над него.

В Kotlin е малко по-различно и използваме object : Comparator<MyType> { } (използваме анонимен “object”), a в тялото използваме ключова дума override вместо анотация.

В Kotlin не използваме ключова дума static (както в Java). Вместо това всеки клас може да има свой companion object, в който се добавят статични полета и методи. И както се вижда в примера горе, имаме различни начини, по които можем да достъпим след това този специален обект и полетата/методите му.

Другият начин да имаме статични полета е да използваме object (обяснено надолу).

В Kotlin модификаторите за достъп са малко по-различни от тези в Java.

По подразбиране всичко в Kotlin e public (т.е. ако няма модификатор за достъп, полето/методът ни ще бъде публично достъпен). При private и protectedняма нищо особено.

Интересното е модификаторът internal на Kotlin. Това е нещо като Kotlin версията на package-private в Java, но не точно. Като цяло Kotlin не придава голямо значение на пакетите, в които са класовете ти. Това, което е маркирано с internal, е видимо само в същия Kotlin “модул” (група от файлове, които се компилират заедно; пример за такава група е един Maven модул в проект с множество модули).

3.1.5. “when”, “is” и други

В Kotlin не ползваме switch, а вместо това when. Структурата на when е доста сходна, но в същото време доста по-гъвкава. Примерите горе говорят сами за себе си.

На мен лично най-много ми харесва възможността да изброяваш различни булеви изрази и да определяш какво да се случва, когато някой от тях е true (много по-компактно от if-else 😅).

Oще някои (дребни?) неща:

  • В Kotlin използваме isвместо instanceof, когато искаме да проверим дали типът на някой обект е този, който очакваме.
  • Използваме as за explicit casting (според мен е по-приятно за четене така, отколкото с множеството скоби в Java 😅).
  • В Java имаме Object, а в Kotlin еквивалентът е Any.
  • Нямаме ternary оператор за разлика от Java, но пък има алтернатива, която (според гледната точка) може да се каже, че е по-добра и по-гъвкава (ще я обясня надолу).
  • Нямаме и “класически” for цикъл в Kotlin, но отново има алтернатива, която според мен е доста добра (и тя е обяснена надолу).

3.2. Готини възможности на Kotlin

Тук ще ви споделя някои от любимите ми възможности на Kotlin (подредени в случаен ред 😅).

3.2.1. String templates (шаблони за символни низове)

Това е малка готина функционалност, която може и да ти е позната, ако си ползвал TypeScript. 😅

Просто не ти се налага да правиш тъмни магии с printf(), да добавяш символи за нов ред или да правиш досадни конкатенации. Kotlin ти дава голяма гъвкавост в оформянето на символните низове, които създаваш.

3.2.2. Type inference (автоматично определяне на типа на променлива)

Досега най-често примерите горе все включваха типа на дадена променлива (например val i: Int = 5). Oбаче готиното е, че всъщност най-често можеш да го пропуснеш, тъй като компилаторът може да го извлече автоматично от стойността, която си задал.

Например горе i ще е Int, j ще е Long, str ще е String, a list ще е ArrayList<Int>. Това е един от начините, по които кодът, който пишеш на Kotlin, става значително по-кратък.

3.2.3. Ranges (диапазони)

Както споменах, в Kotlin нямаме класически for цикъл, но имаме нещо като for-each в Java (for (el : elements) { … }), а ако искаме да достъпваме по индекс в цикъла, използваме възможността за диапазони в Kotlin.

Някои интересни особености са, че когато използваме например 1..4, това означава, че итерираме от 1 до 4 включително. Ако искаме да изключим 4, използваме until. Другото интересно е, че until и downTo не са ключови думи, а пример за т.нар. “infix функции” (обяснено надолу).

Харесва ми, че тези диапазони дават доста голяма гъвкавост (a дори можеш да извикваш и методи, с които да получиш нещо конкретно, което те интересува за диапазона — например сумата от елементите в него, средно аритметичното и т.н.).

3.2.4. По-лесно обхождане (и инициализация) на Map

Това е нещо дребничко, но на мен лично много ми харесва. 😆

При обхождане на Map, вместо да се занимаваш с достъпване на map.entrySet() и след това извличане на ключ/стойност, можеш просто да ги подадеш в цикъла и след това да ги достъпиш директно.

Забележи също, че и инициализацията на таблицата е значително по-просто (с наличието на mapOf() метод и Pair клас).

3.2.5. Data класове

Това е една от най-любимите ми възможности на Kotlin. Общо взето тези “data” класове ти дават наготово toString(), hashCode(), equals() и конструктор с параметри.

В Java би ти се налагало да ги добавяш ръчно (дори и да са генерирани от IDE-то, което ползваш) или да ползваш Lombok, a това често е неудобно. Все пак, в полза на Java, наскоро открих, че и там вече се предлага нещо такова чрез т.нар. Java “records” (проблемът е, че тази функционалност идва чак от Java 14 нататък, a понякога не е толкова просто да вдигнеш версията на Java в проекта си). 😅

Типично за Kotlin, имаш и добра гъвкавост по отношение на това, което ИСКАШ да ползаш от тези data класове (например имаш възможността да не включваш определени полета в автоматично генерираните методи и в конструктора).

Друг готин бонус е copy() методът, който получаваш, ако създадеш data клас. Този метод ти позволява лесно да създадеш нов обект, който е почти същия като друг (лесно можеш да нанесеш някаква малка промяна, а всичко друго си остава както си е било).

3.2.6. Object declarations & “top-level” функции

Друга готина възможност на Kotlin са т.нар. “object declarations”, които ни предоставят нещо като имплементация на Singleton шаблона. Общо взето все едно създаваме обикновен клас, но използваме ключова дума object, a всичко в тялото на този “object” е статично.

Още едно малко готино нещо: можеш да имаш функции, които не принадлежат на никой клас (т.нар. “top-level functions”). След това можеш да използваш тези функции в класовете си.

3.2.7. Null safety

Това е може би една от най-съществените възможности на Kotlin: можеш да имаш полета, които не позволяват null стойност (и компилаторът те спира, ако се опиташ да им поставиш такава стойност).

В резултат се налага доста по-рядко да правиш ръчни проверки за null стойност и съответно доста по-рядко се налага да се бориш с NullPointerException.

3.2.8. Extension функции

Ако някога ти се е искало String (или който и да е клас) да има определен метод, вече можеш сам да си го създадеш чрез т.нар. “еxtension” функции. По този начин можеш да добавяш нова функционалност в клас, без да ти се налага да го наследяваш.

Особено полезно е ако нямаш възможността да модифицираш самия клас. Сега можеш просто да създадеш extension функция, а след това да я извикаш като обикновен метод на класа, който използваш.

3.2.9. Именувани параметри & стойности по подразбиране
(named parameters & default values)

Kotlin ти дава възможността да разместваш реда, в който подаваш аргументи на някоя функция. Освен това можеш да поставиш стойност по подразбиране, която да се използва, ако ти не си подал никаква такава на функцията.

Доста удобна функционалност. 😅👍

3.2.10. Kotlin изрази (“If-else”, “return”, “when”, “try-catch”, “throw”)

В Kotlin почти всичко е израз. В резултат на това можеш да правиш интересни неща като:

  • val str = if (num > 10) “hello” else “world” (споменах, че нямаме ternary оператор, а това е алтернативата, която Kotlin предлага)
  • val numCopy = anotherNum ?: return и val myStr = myNum ?: throw Exception(“Something went wrong…”)
  • val a = when (something) { … } и val number = try { … } catch (e: Exception) { … }
  • return if (something) “hello” else “world”

С подобни изрази можеш да намалиш редовете код значително (макар и в началото да е малко трудно за четене 👀). За мен лично писането на код така е много по-удобно (макар в началото да е малко странно и неестествено).

3.2.11. Single-expression функции

Нещо сравнително дребничко, но ми харесва много. 👀 Когато цялата функция се състои от само един израз, не е нужно да пишеш тип на връщаната стойност, а също така не пишеш иreturn.

Ако използваш функционално програмиране, това можа да е полезно, защото обикновено там имаме една голяма верига от извиквания на методи.

3.2.12. По-прости lambdas & функционално програмиране

Накратко: Kotlin значително опростява ползването на ламбди и функционално програмиране (примерите горе го илюстрират).

Докато в Java често такъв код става сравнително труден за писане и четене, в Kotlin обикновено кодът е по-кратък и по-разбираем (разбирам, че е субективно, но поне това е моето мнение 😅).

Kotlin ти предоставя и някои полезни функции, които улесняват извършването на някои често срещани операции (като например сортиране на списък и променяне на стойност на полета в обект).

3.2.13. Гъвкави имена за полета & функции

Това е нещо, което не те съветвам да ползваш, ако няма крайна необходимост, но все пак го има като възможност. 😅 Можеш да създаваш полета и функции с интервали и други специални символи в имената им. Доста полезно е, когато се чудиш как да именуваш JUnit тестовете си например. 👀

3.2.14. “Infix” функции (без точка и скоби)

Infix функциите са функции, които се използват между две променливи. Това понякога прави кода по-лесен за четене и писане, защото се доближава до начина, по който хората комуникираме. Най-простият пример е събирането на 2 числа или пък поставяне на диапазон във for цикъл.

Честно казано най-вероятно няма да ти се наложи да ползваш тази функционалност на Kotlin много, но все пак е още една интересна възможност на езика. 😉

3.2.15. Преобразуване на Java код в Kotlin (и обратно) [IntelliJ IDEA]

Преобразуването на Java код в Kotlin (в IntelliJ IDEA) e изключително лесно: просто постави Java код в Kotlin файл, а след това IDE-то ще те попита дали искаш да го конвертираш в Kotlin код. Можеш да преобразуваш и Kotlin в Java като следваш показания горе път в IntelliJ.

Имай предвид, че конвертирането не е перфектно (особено от Kotlin в Java), но все пак е полезно в началотo, когато се опитваш да разбереш как работи Kotlin (по-нататък едва ли ще ти трябва). 😎

4. Как да продължиш от тук нататък (полезни ресурси и съвети)

Ето и някои ресурси/идеи, които ми бяха полезни. Може да ги използваш като допълнение на този пост.

4.1. Гледай (за да схванеш основите):

4.2. Прочети (за по-дълбоко разбиране на нещата):

  • https://kotlinlang.org/docs/home.html (документация на Kotlin)
  • … и потърси в Google, ако някоя специфична част от Kotlin не ти е ясна. 😉

4.3. Упражнявай се (за да можеш спокойно да използваш наученото):

  • Прегледай Kotlin кода в проектите, по които работите (ако има такива).
  • Опитай се да преобразуваш Java код в Kotlin (и наобратно).
  • Създай си малък Kotlin проект и експериментирай с него.

Това е засега. Успех и have fun! 😎

--

--