Jak napisać pierwszy kod w Kotlinie w Javovym projekcie
Chociaż JDK 15 zostało wydane przed paroma tygodniami, to niestety jakaś część programistów, szczególnie aplikacji serwerowych, ma nadal do czynienia w projektach z dużo starszą wersją Javy. Prowadzi to do frustracji i bardzo często rotacji w projektach. Świat IT nieubłaganie pędzi do przodu i jako specjaliści w swoim zawodzie chcielibyśmy mieć dostęp do najnowszych technologii, które sprawiają, że nasza praca jest przyjemniejsza, ale również bardziej wydajna.
Michał Kostewicz. Programista w firmie Stepwise, na co dzień tworzący aplikacje webowe w językach Java, Kotlin oraz JavaScript. Miłośnik pisania kodu otwarty na wszystkie technologie frontendowe i backendowe, który swoje pierwsze kroki stawiał w Basic-u na ośmiobitowym Atari. W wolnych chwilach uwielbia zagłębiać się w tematy związane z Machine Learning oraz słuchać książek przerzucając jednocześnie ciężary na siłowni.
Programiści to przebiegłe istoty i próbują sobie radzić stosując różne biblioteki, takie jak lombok, vavr, streamsupport czy retrolambda, jednak użycie ich (szczególnie kilku naraz) powoduje, że konfiguracja staje się mało czytelna i wrażliwa na zmiany. Kiedyś sam byłem w pozycji człowieka, który szukał takich rozwiązań, a dzisiaj, pracując głównie z użyciem języka Kotlin często zastanawiam się, dlaczego nie namawiałem zespołu do przejścia na ten język.
Myślę, że głównym czynnikiem, przez który tego nie zrobiłem był strach przed ewentualnymi problemami z konfiguracją projektu, czas potrzebny do nauczenia się nowego języka i konieczność przekonania reszty zespołu. Nad tym wszystkim ciąży jeszcze fakt, że ciężko namówić do tego “biznes”, bo często wyznawaną przez niego filozofią jest “jak działa to po co psuć”. Dzisiaj wiem jednak, że takie przejście nie jest trudne i chciałbym się tym z Wami podzielić i szczerze do tego namawiać.
Dla tych, którzy słabo znają własności Kotlina: zacznijmy standardowo od odpowiedzenia sobie na pytanie “Dlaczego w ogóle warto przejść na Kotlina?”.
- Interoperacyjność Javy i Kotlina. W naszym projekcie nie musimy od razu przepisywać wszystkiego od nowa. Wystarczy, że w
src/main
oprócz katalogujava
pojawi się równieżkotlin
. Możemy spokojnie wołać kod Java z Kotlina i na odwrót. - Kotlin kompiluje się domyślnie do JVM 6 więc można go użyć w naprawdę starych projektach. Można użyć również nowszych wersji JVM.
- Prosty do nauczenia. Kotlin jest inspirowany istniejącymi językami, takimi jak Java, C #, JavaScript, Scala i Groovy. Jestem pewny, że programiści tych języków są w stanie w ciągu kilku dni zacząć efektywnie pisać w nowym języku.
- Zwięzły. Umożliwiający użycie lambd, funkcji rozszerzeń i funkcji wyższego rzędu. Dzięki temu możemy pozbyć się wszystkich bibliotek, o których wspomniałem na początku tekstu.
- Bezpieczny. Jedną z najczęstszych pułapek w wielu językach programowania jest próba uzyskanie dostępu do elementu referencji z wartością
null
. To oczywiście skutkuje wyrzuceniem przez aplikację wyjątku, którym w Javie jestNullPointerException
. W Kotlin system typów rozróżnia referencje, które mogą zawieraćnull
i te, które nie mogą co znacznie utrudnia popełnienie błędu. - Gotowy produkcyjnie. Za Kotlinem stoi ponad stu inżynierów z JetBrains, firmy znanej z najlepszego IDE IntelliJ IDEA, a w sieci można znaleźć wiele przykładów aplikacji serwerowych, które zostały napisane albo przeniesione na język Kotlin. Nasza firma pracuje również nad takimi projektami i są one realizowane z sukcesem.
- Automatyczne konwertowanie kodu Java do Kotlin w Intellij IDEA. Nie jest to może właściwość samego języka, ale jest to feature, który bardzo pomaga przejść na Kotlina. Każdy znaleziony w sieci przykład napisany w Javie po prostu wklejacie do IDE, który konwertuje go do Kotlina. W ten sposób skonwertowany kod nie jest perfekcyjny, ale w pełni nadaje się do użycia. Nie byłbym szczery gdybym nie nadmienił, że zdarza się jakieś parę procent przypadków w których musimy coś ręcznie poprawić.
Ok, jak już odpowiedzieliśmy sobie na pytanie czy warto, musimy zastanowić się jak to zrobić. Zakładam, że w Waszych projektach używacie jakiegoś narzędzia do budowania. Zacznijmy więc od Maven.
Dla przykładu użyłem pierwszy projekt wielomodułowy, który udało mi się znaleźć w Google po wpisaniu “github maven multi module” .
Konfiguracja root projektu:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>io.jitpack</groupId> <artifactId>example-root</artifactId> <version>2.0-SNAPSHOT</version> <packaging>pom</packaging> <name>example-root</name> <modules> <module>module1</module> <module>module2</module> </modules> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <!-- Compile java 7 compatible bytecode --> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
Pora dodać Kotlina. Do głównego pom.xml
dodaję więc parę niezbędnych elementów:
1. Zmienna z wersją Kotlina, którą użyjemy:
<properties> <kotlin.version>1.4.10</kotlin.version> </properties>
2. Zależność do biblioteki standardowej Kotlina, która dostarcza wiele przydatnych funkcji:
<dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies>
3. Do sekcji build dorzucamy konfigurację ścieżek kodu źródłowego oraz plugin kompilujący kod źródłowy Kotlina:
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <goals> <goal>compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/main/kotlin</sourceDir> <sourceDir>${project.basedir}/src/main/java</sourceDir> </sourceDirs> </configuration> </execution> <execution> <id>test-compile</id> <goals> <goal>test-compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> <sourceDir>${project.basedir}/src/test/java</sourceDir> </sourceDirs> </configuration> </execution> </executions> </plugin>
Całość konfiguracji wygląda następująco:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>io.jitpack</groupId> <artifactId>example-root</artifactId> <version>2.0-SNAPSHOT</version> <packaging>pom</packaging> <name>example-root</name> <modules> <module>module1</module> <module>module2</module> </modules> <properties> <kotlin.version>1.4.10</kotlin.version> </properties> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> <build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <goals> <goal>compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/main/kotlin</sourceDir> <sourceDir>${project.basedir}/src/main/java</sourceDir> </sourceDirs> </configuration> </execution> <execution> <id>test-compile</id> <goals> <goal>test-compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> <sourceDir>${project.basedir}/src/test/java</sourceDir> </sourceDirs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <!-- Compile java 7 compatible bytecode --> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
Przyszedł moment na dodanie jakiegoś kodu napisanego w Kotlin. Dorzucam więc do module1
katalog kotlin
oraz pakiet, który istnieje już w projekcie, a także prosty serwis io.jitpack.KotlinRulezService.kt
, z następującą treścią:
class KotlinRulezService { fun talkToMe(greetingWord: String) { println("Kotlin Rulez $greetingWord") } }
W module1
istnieje jedna klasa napisana w Javie i jest to główna klasa aplikacji io.jitpack.App.java
:
public class App { public static final String GREETING = "Hello World!"; public static void main( String[] args ) { System.out.println(GREETING); } }
Dorzućmy do niej wywołanie naszego serwisu Kotlinowego. Po zmianach powinna wyglądać następująco:
public class App { public static final String GREETING = "Hello World!"; public static void main( String[] args ) { KotlinRulezService service = new KotlinRulezService(); service.talkToMe(GREETING); } }
Ponowne uruchomienie mvn compile
nie powinno sprawić nam żadnego problemu.
Jak widać, dodanie Kotlina w projekcie używającym Maven jest bardzo proste. Spróbujmy zrobić teraz to samo z projektem używającym Gradle. Podejdę do tego zadania podobnie jak wcześniej, czyli używając pierwszego projektu, który znajdzie mi Google po wpisaniu “github gradle multi module”. Tym razem jest to projekt github.com/gwonsungjun/gradle-multi-module.
Nasza konfiguracja wygląda następująco:
buildscript { ext { springBootVersion = '2.1.3.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath "io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE" } } allprojects { group 'com.sungjun' version '1.0-SNAPSHOT' } subprojects { apply plugin: 'java' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' } } project(':sample-api') { dependencies { compile project(':sample-common') } } project(':sample-admin') { dependencies { compile project(':sample-common') } }
Projekt możemy zbudować komendą ./gradlew build
. Pora dodać Kotlina:
1. Podobnie jak w poprzednim przypadku musimy dodać plugin, który skompiluje nam kod Kotlin. W tym celu w głównym pliku build.gradle
pod sekcją buildscript
dodajemy:
plugins { id "org.jetbrains.kotlin.jvm" version "1.3.72" }
W przypadku tego projektu konieczne było zejście do niższej wersji plugina z powodu użycia w projekcie Gradle w wersji 4.10.3.
2. W tym samym pliku mamy sekcję subprojects
, do której musimy dodać apply plugin: 'kotlin'
tak żeby można było w każdym sub-module używać kodu Kotlin.
3. Musimy również w zależnościach sekcji subprojects
dodać zależność do biblioteki standardowej Kotlin implemention "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
.
Ostatecznie główny plik konfiguracyjny wygląda następująco:
buildscript { ext { springBootVersion = '2.1.3.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath "io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE" } } plugins { id "org.jetbrains.kotlin.jvm" version "1.3.72" } allprojects { group 'com.sungjun' version '1.0-SNAPSHOT' } subprojects { apply plugin: 'java' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'kotlin' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } } project(':sample-api') { dependencies { compile project(':sample-common') } } project(':sample-admin') { dependencies { compile project(':sample-common') } }
Możemy teraz już zacząć pisać kod w Kotlinie. W sub-module sample-api
dodaje w katalogu src/main
katalog kotlin
i dodajemy do niego pakiet com.sungjun.api.service
. Jak już wspomniałem na początku tekstu, jeżeli korzystamy z IntelliJ Idea możemy w prosty sposób przekonwertować kod napisany w Javie na język Kotlin.
Myślę, że fajnie byłoby wypróbować to na przykładzie istniejącego w wyżej wymienionym module serwisu com.sungjun.api.service.MemberServiceCustom
. Stworzę więc klasę Kotlin nazywającą się tak samo MemberServiceCustom.kt
i wkleję do niej następujący kod Java:
import com.sungjun.common.member.Member; import com.sungjun.common.repository.MemberRepository; import org.springframework.stereotype.Service; @Service public class MemberServiceCustom { private MemberRepository memberRepository; public MemberServiceCustom(MemberRepository memberRepository) { this.memberRepository = memberRepository; } public Long singup (Member member) { return memberRepository.save(member).getId(); } }
IDE samo zapyta o to czy przekonwertować kod, a w wyniku tej operacji otrzymamy następujący kod Kotlin:
import com.sungjun.common.member.Member import com.sungjun.common.repository.MemberRepository import org.springframework.stereotype.Service @Service class MemberServiceCustom(private val memberRepository: MemberRepository) { fun singup(member: Member): Long { return memberRepository.save(member).id } }
Pierwsze co może rzucić się w oczy to zwięzłość tego kodu, a można zapisać to jeszcze zwięźlej:
import com.sungjun.common.member.Member import com.sungjun.common.repository.MemberRepository import org.springframework.stereotype.Service @Service class MemberServiceCustom(private val memberRepository: MemberRepository) { fun singup(member: Member) = memberRepository.save(member).id }
Musimy teraz usunąć stary serwis napisany w Javie, bo mamy konflikt nazwy w tym pakiecie. Całość naszej pracy możemy sprawdzić uruchamiając test com.sungjun.api.service.MemberServiceCustomTest
, który używa wymieniony przez nas serwis. I to już koniec zmian niezbędnych do użycia w projekcie Gradle języka Kotlin.
Podsumowanie
Mam nadzieję, że w niniejszym tekście zachęciłem trochę do użycia w Waszych projektach języka Kotlin. Wiem, że projekty komercyjne potrafią być bardzo rozbudowane, ale pamiętajmy, że przy odrobinie zachodu możemy wprowadzić projekt w dwudziesty pierwszy wiek. Dosyć często próbujemy wspomagać się różnymi bibliotekami, które potrafią skomplikować proces budowania. Jak widać w tekście, w prosty sposób możemy je wszystkie zamienić na język, który jest bardzo przyjemny, wydajny i może znacznie poprawić odbiór projektu przez obecnych i nowych programistów.
W Stepwise prawie wszystkie nasze aplikacje serwerowe piszemy w języku Kotlin. Nigdy nie mieliśmy problemów z wprowadzeniem tego języka do projektu, jak również we wdrożeniu nowych programistów, którzy mieli do czynienia jedynie z językiem Java.
Zdjęcie główne artykułu pochodzi z unsplash.com.