Menu Zamknij

Kurs Java / Kotlin

Cena szkolenia

Cena spotkania grupowego wynosi 100 zł netto za 60 min. Cena spotkania indywidualnego wynosi 120 zł netto za 60 min. Ilość godzin szkolenia zależy od Twoich postępów w nauce oraz ilości czasu, którą poświęcisz na realizację zadań i projektów zleconych w ramach szkolenia.

Opis kursu

Kurs przygotuje Cię do pracy na stanowisku programisty Java, Java Web Developer lub każdym innym, gdzie wymagana jest znajomość języka Java. Po zakończeniu szkolenia będziesz w stanie tworzyć lub rozwijać nowoczesne aplikacje bazodanowe i webowe w języku Java oraz frameworkach, których nauczysz się w tym szkoleniu.

Naukę zaczniesz od zrozumienia podstaw języka Java i struktur programistycznych, które w nim występują. Nauczysz się zagadnień związanych z algorytmiką oraz zasad optymalizacji kodu i szybkości działania aplikacji. Wykorzystasz komunikację z bazami danych SQL oraz mechanizmy ORM. Zbudujesz nowoczesne aplikacje bazodanowe i webowe, które następnie skonteneryzujesz z wykorzystaniem platformy Docker. Programy przetestujesz z wykorzystaniem bibliotek dedykowanych do testów. To oczywiście nie wszystko. Poznasz wiele innych narzędzi wspomagających pracę z opisanymi wyżej technologiami (np. Git). Twoja wiedza będzie systematycznie poszerzana o dodatkowe materiały dydaktyczne, które znajdziesz na moich kanałach YouTube, TikTok lub otrzymasz w formie cotygodniowych newsletterów.

W ramach szkolenia rozwiążesz ogromną ilość zadań teoretycznych i praktycznych, które skutecznie przygotują Cię do rozmowy kwalifikacyjnej. Twoja wiedza będzie systematycznie sprawdzana w trakcie naszych spotkań i organizowanych co pewien czas indywidualnych próbnych rozmów kwalifikacyjnych w języku polskim lub angielskim.

Po zakończeniu szkolenia Twoje portfolio powiększy się o kilka lub kilkanaście rozbudowanych projektów, które potwierdzą Twoje praktyczne umiejętności posługiwania się zagadnieniami, pojawiającymi się w kursie. Na każdym etapie szkolenia możesz liczyć na nieograniczoną pomoc mentora. Zagwarantuje to zgodność projektów z przyjętymi założeniami oraz dobrymi praktykami programistycznymi, jak również zmusi Cię do systematycznej pracy nad projektami. Dodatkowo zmierzysz się z zadaniami rozwijania lub modyfikowania istniejących już projektów oraz ich analizy pod kątem wyszukiwania błędów.

Potrzebne aplikacje i narzędzia

Do odbycia szkolenia potrzebujesz komputer lub laptop z zestawem narzędzi do budowania aplikacji pojawiających się w trakcie kursu. Spotkania możemy odbywać również na moim komputerze. Po spotkaniu zawsze otrzymasz wszystkie materiały, które pojawiły się w trakcie spotkania. Potrzebne oprogramowanie szczegółowo opiszę oraz pomogę Ci zainstalować na pierwszym darmowym spotkaniu testowym.

Informacje na temat aplikacji potrzebnych do nawiązania połączenia przekażę w odpowiednim momencie przed rozpoczęciem szkolenia. Dostaniesz wtedy szczegółowy wykaz kolejnych kroków, które należy wykonać w celu przygotowania się do rozpoczęcia kursu.

Umiejętności potrzebne przed
rozpoczęciem kursu

Kurs przeznaczony jest dla osób o różnym stopniu zaawansowania. Możesz do niego przystąpić nawet jeżeli do tej pory nie miałeś nic wspólnego z programowaniem. Jeżeli już pracujesz na podobnym stanowisku, szkolenie również jest dla Ciebie. Celem kursu jest przygotowanie do pierwszej pracy programisty, ale również podnoszenie kwalifikacji osób już pracujących w branży IT. Szkolenie możesz rozpocząć od dowolnego punktu w planie szkolenia. Na początek odbędziemy szczegółową rozmowę kwalifikacyjną w języku polskim lub angielskim. Dzięki niej ocenimy Twój poziom z zakresu wiedzy przed wybranym punktem w planie szkolenia.

Plan szkolenia

  • Konfiguracja i uruchomienie środowiska programistycznego
  • Zmienne
  • Proste typy danych
  • Słowo kluczowe final
  • Klasy opakowujące proste typy danych
  • Słowo kluczowe var
  • Operatory
  • Pobieranie i prezentacja danych
  • Instrukcje sterujące i pętle
  • Instrukcja switch
  • Konstrukcja switch expressions
  • Różne postaci instrukcji switch
  • Tablice jednowymiarowe i wielowymiarowe
  • Rozszerzona pętla for
  • Zarządzanie tablicami z wykorzystaniem metod klasy Arrays
  • Podział programu na metody
  • Stos i sterta
  • Omówienie zasady działania garbage collector
  • Przekazywanie argumentów do metod
  • Metody o zmiennej ilości parametrów
  • Zwracanie wartości przez metody
  • Przeciążanie metod
  • Obsługa zasobów tekstowych – klasa String
  • Zarządzanie pojedynczymi znakami – klasa Character
  • Konwersje z napisu na inny typ danych
  • Konwersje z innego typu danych na napis
  • Omówienie klas StringBuilder oraz StringBuffer
  • Kodowanie znaków w języku Java
  • Wyrażenia regularne
  • Zagadnienia uzupełniające
  • Definicja klasy i obiektu
  • Referencja do obiektu
  • Omówienie założeń enkapsulacji
  • Praca z modyfikatorami dostępu
  • Pola składowe klas
  • Metody dostępowe i modyfikujące
  • Referencja this
  • Przegląd wszystkich rodzajów konstruktorów
  • Wywołanie jednego konstruktora z innego
  • Domyślna inicjalizacja
  • Bloki inicjalizujące
  • Statyczne składniki klasy
  • Statyczne bloki inicjalizujące
  • Metody wytwórcze
  • Klasa Object oraz klasa Objects
  • Przegląd ważnych wbudowanych klas Java
  • Statyczne klasy zagnieżdżone i klasy wewnętrzne
  • Klasy osłonowe
  • Typ wyliczeniowy
  • Kompozycja
  • Rekordy
  • Klasy zapieczętowane (sealed)
  • Klasy ukryte
  • Zagadnienia uzupełniające
  • Klasy nadrzędne i podrzędne
  • Słowo kluczowe super
  • Konstruktory w dziedziczeniu
  • Przesłanianie metod
  • Składniki statyczne w dziedziczeniu
  • Polimorfizm
  • Przypisania klas nadrzędnych i rzutowanie w dziedziczeniu
  • Operator instanceof oraz mechanizm pattern matching
  • Metody i klasy z modyfikatorem final
  • Omówienie problemu diamentowego
  • Klasy i metody abstrakcyjne
  • Anonimowe klasy podrzędne
  • Zagadnienia uzupełniające
  • Klasa Class
  • Wczytywanie zasobów
  • Wyliczanie elementów klas
  • Tworzenie i kontrolowanie
  • Wywoływanie metod
  • Praca z tablicami
  • Klasa Proxy
  • Konwersja zawartości obiektów do różnych postaci z wykorzystaniem mechanizmu refleksji
  • Praktyczne przykłady wykorzystania programowania refleksyjnego
  • Zagadnienia uzupełniające obiektów
  • Deklarowanie interfejsu
  • Metody abstrakcyjne, domyślne i statyczne w interfejsie
  • Metody publiczne i prywatne w interfejsie
  • Pola składowe w interfejsie
  • Implementowanie interfejsu
  • Konwersja do typu interfejsu
  • Rzutowanie i operator instanceof w pracy z interfejsem
  • Implementowanie wielu interfejsów – rozstrzyganie konfliktów
  • Przegląd interfejsów wbudowanych
  • Anonimowa implementacja interfejsu
  • Interfejsy funkcyjne
  • Wyrażenia lambda
  • Wyrażenia lambda i zasięg zmiennych
  • Funkcje wyższych rzędów
  • Referencje do metod i konstruktora
  • Przykłady zastosowania interfejsów w praktycznych przykładach
  • Zagadnienia uzupełniające
  • Algorytmy badające właściwości geometryczne
  • Algorytm badające właściwości matematyczne
  • Konwersje pomiędzy systemami liczbowymi
  • Badanie ciągów danych pod kątem wybranych właściwości
  • Sortowanie ciągów danych
  • Zastosowanie metody dziel i zwyciężaj
  • Wybrane metody numeryczne
  • Programowanie zachłanne
  • Algorytmy na tekstach
  • Wybrane algorytmy kryptograficzne
  • Rekurencja
  • Przegląd wybranych struktur danych
  • Zagadnienia uzupełniające
  • Hierarchia klas wyjątków
  • Sposoby zgłaszania wyjątków
  • Sposoby przechwytywania wyjątków
  • Zarządzanie wyjątkami kontrolowanymi
  • Wyrażenie try z określeniem zasobów
  • Klauzula finally
  • Ponowne rzucanie wyjątków
  • Implementacja własnych klas do obsługi wyjątków
  • Dziedziczenie klas wyjątków
  • Asercje
  • Dobre praktyki wykorzystywania wyjątków
  • Zagadnienia uzupełniające
  • Klasyfikacja wzorców projektowych
  • Implementacja wzorców kreacyjnych
  • Implementacja wzorców strukturalnych
  • Implementacja wzorców czynnościowych
  • Zagadnienia uzupełniające
  • Omówienie hierarchii kolekcji
  • Omówienie wzorca projektowego Iterator
  • Listy
  • Zestawy
  • Kolejki
  • Właściwości
  • Zestawy bitów
  • Zestawy wyliczeniowe
  • Przegląd klas oraz interfejsów współpracujących z kolekcjami
  • Mapy i widoki map
  • Przegląd klas oraz interfejsów współpracujących z kolekcjami
  • Implementacja kolekcji z wykorzystaniem metod statycznych Java 9
  • Zagadnienia uzupełniające
  • Prezentacja założeń programowania funkcyjnego
  • Omówienie zasady działania strumieni
  • Tworzenie strumienia
  • Przegląd wszystkich operacji strumieniowych
  • Metody końcowe i pośrednie
  • Zastosowanie wyrażeń lambda w pracy ze strumieniami
  • Zastosowanie referencji do metod w pracy ze strumieniami
  • Omówienie wzorca Lazy Evaluation podczas analizy pracy strumienia
  • Typ Optional
  • Zastosowanie typu Optional w pracy ze strumieniami
  • Implementacja własnych Collector-ów
  • Strumienie typów prostych i strumienie równoległe
  • Praktyczne przykłady wykorzystania strumieni
  • Biblioteka StreamEx 
  • Zagadnienia uzupełniające
  • Klasy i metody uogólnione
  • Ograniczenie typów
  • Symbole wieloznaczne w typach podrzędnych
  • Symbole wieloznaczne typów nadrzędnych
  • Symbole wieloznaczne ze zmiennymi typami
  • Nieograniczone symbole wieloznaczne
  • Przechwytywanie symboli wieloznacznych
  • Mechanizm wymazywania i metody pomostowe
  • Ograniczenia wynikające z mechanizmu wymazywania
  • Refleksje w programowaniu uogólnionym – klasa Class
  • Przechwytywanie informacji o typie w czasie wykonywania programu
  • Wykorzystanie programowanie uogólnionego w przykładowych aplikacjach
  • Zagadnienia uzupełniające
  • Omówienie architektury i sposobu działania aplikacji Maven
  • Analiza szablonów projektów
  • Przegląd pluginów Maven
  • Tworzenie oraz zarządzanie projektem z wykorzystaniem Maven
  • Praca z plikiem konfiguracyjnym aplikacji Maven
  • Cykle życia projektu
  • Praca z modułami zewnętrznymi
  • Implementacja aplikacji wielomodułowych w oparciu o Maven
  • Generowanie repozytoriów
  • Zarządzanie wersjami dependencies – BOM
  • Zarządzanie wersjami projektu – Maven Flatten Plugin
  • Zarządzanie wersjami narzędzi projektu – Maven Enforcer Plugin
  • Zarządzanie zasobami – Maven Resources Plugin
  • Przygotowanie aplikacji produkcyjnej w oparciu o Maven
  • Język Groovy
  • Omówienie architektury i sposobu działania aplikacji Gradle
  • Tworzenie oraz zarządzanie projektem z wykorzystaniem Gradle
  • Implementacja aplikacji wielomodułowych w oparciu o Gradle
  • Przygotowanie aplikacji produkcyjnej w oparciu o Gradle
  • Zagadnienia uzupełniające
  • Konfiguracja projektu do pracy z frameworkiem JUnit
  • Cykl życia obiektu klasy testującej
  • Adnotacje wykorzystywane w pracy z frameworkiem JUnit
  • Asercje wbudowane
  • Zwiększenie możliwości testujących – framework Hamcrest
  • Implementacja własnych klas do pracy z asercjami
  • Testy powtarzalne
  • Testy zagnieżdżone
  • Testy parametryzowane
  • Testy dynamiczne
  • Testy równoległe
  • Interfejsy w pracy z testami jednostkowymi
  • Mechanizm rozszerzeń JUnit
  • Konfiguracja projektu do pracy z frameworkiem Mockito
  • Konfiguracja i przygotowanie mock obiektów
  • Zarządzanie argumentami
  • Partial mocks
  • Konfiguracja obiektów spy
  • Wstrzykiwania mock obiektów
  • Dobre praktyki implementacji testów jednostkowych
  • Zagadnienia uzupełniające
  • Omówienie założeń mechanizmu modułowości
  • Prezentacja różnych rodzajów modułów
  • Analiza pliku module-info.java
  • Implementacja przykładowych aplikacji z wykorzystaniem mechanizmu modułowości
  • Zagadnienia uzupełniające
  • Przedstawienie tematów projektów do implementacji
  • Omówienie wzorca architektury wielowarstwowej
  • Omówienie wzorca DDD
  • Zarządzanie zasobami w formacie JSON – komunikacja ze zdalnym API
  • Zarządzanie zasobami w formacie JSON – przechowywanie danych w generowanych plikach JSON
  • Implementacja serwisu do wysyłania wiadomości email
  • Implementacja serwisu do konwersji danych w formacie HTML
  • Wykorzystanie poznanych mechanizmów do implementacji kompletnej aplikacji w architekturze wielowarstwowej
  • Zagadnienia uzupełniające
  • Omówienie architektury aplikacji bazodanowej
  • Instalacja środowiska bazodanowego
  • Omówienie języka SQL
  • Relacje w bazach danych
  • Konfiguracja aplikacji do pracy z JDBC
  • Przypomnienie wzorca architektury wielowarstwowej
  • Zapoznanie się ze wzorcem architektury wielowarstwowej
  • Implementacja kompletnej komunikacji bazodanowej z wykorzystaniem JDBC
  • Przegląd bibliotek zewnętrznych do wygodnego zarządzania komunikacją bazodanową z poziomu JDBC
  • Implementacja komunikacji bazodanowej z wykorzystaniem biblioteki JDBI
  • Wykorzystanie wzorców projektowych, programowania uogólnionego oraz refleksji przy zarządzaniu komunikacją bazodanową
  • Transakcje
  • Zarządzanie wynikami zapytań
  • Zagadnienia uzupełniające
  • Omówienie architektury ORM na przykładzie Hibernate / JPA
  • Omówienie i prezentacja cyklu życia obiektu w Hibernate / JPA
  • Konfiguracja aplikacji do pracy z Hibernate / JPA
  • Klasy mapujące tabele i ich adnotacje
  • Analiza strategii generowania kluczy głównych
  • Przegląd oraz implementacja wszystkich rodzajów relacji bazodanowych
  • Dziedziczenie i kompozycja w Hibernate / JPA
  • Mapowanie kolekcji w Hibernate / JPA
  • Język HQL oraz JPQL
  • Sposoby optymalizacji komunikacji bazodanowej
  • Optimistic locking oraz pessimistic locking
  • Hibernate / JPA oraz mechanizmy cache
  • Wykorzystanie wzorców projektowych i programowania uogólnionego w zarządzaniu danymi
  • Architektura wielowarstwowa oraz architektura DDD
  • Architektura wielowarstwowa
  • Implementacja kompletnej aplikacji zarządzającej komunikacją bazodanową z wykorzystaniem Hibernate / JPA
  • Zagadnienia uzupełniające
  • Omówienie architektury aplikacji webowej
  • REST
  • Omówienie założeń frameworka Spark
  • Przygotowanie aplikacji do pracy z frameworkiem Spark
  • Uruchamianie i konfiguracja serwera
  • Routing
  • Zarządzanie obiektami request oraz response
  • Przechwytywanie parametrów żądania oraz nagłówków
  • Przechwytywanie i zarządzanie cookies
  • Zarządzanie sesją
  • Filtry aplikacji webowej
  • Przekierowania
  • Zarządzanie błędami aplikacji webowej
  • Implementacja mechanizmów security
  • Dokeryzacja aplikacji webowej opartej o framework Spark
  • Zagadnienia uzupełniające
  • Pojęcie procesu i wątku
  • Różne sposoby implementacji i uruchamiania wątków
  • Przerywanie wątku
  • Różne sposoby implementacji i uruchamiania procesu
  • Zarządzanie wartościami zmiennych lokalnych w wątku
  • Wątki użytkownika oraz wątki daemon
  • Problem współdzielenia zasobów
  • Livelock i deadlock
  • Synchronizacja
  • Omówienie metod wait i notify
  • Locks
  • Różne sposoby implementacji problemu producent – konsument
  • Semafory
  • Synchronizacja pracy wielu wątków przy użyciu mechanizmu latch
  • Zadania współbieżne – Executor, interfejs ExecutorService
  • Przechwytywanie wyników obliczeń – Callable, Future
  • Cykliczne wykonywanie wątków – ScheduledExecutorService
  • Wartości atomowe
  • Kolejki blokujące
  • Klasa ConcurrentHashMap
  • Przegląd innych struktur danych bezpiecznych dla wątków
  • Strategie bezpiecznego korzystania z wątków
  • Obliczenia asynchroniczne – klasa CompletableFuture
  • Synchroniczna i asynchroniczna komunikacja HTTP
  • Algorytmy równoległe do zarządzania strumieniami i tablicami
  • Framework Fork/Join
  • RxJava
  • Zagadnienia uzupełniające
  • Omówienie Flow API
  • Implementacja aplikacji realizującej założenia programowania
    reaktywnego
  • Mechanizmy reaktywne do zarządzania strumieniami
  • Zagadnienia uzupełniające
  • Parametry wiersza poleceń
  • Pakiety
  • Generowanie dokumentacji
  • Rejestrowanie danych z wykorzystaniem loggerów
  • Projekt Lombok
  • Biblioteka GSON
  • Parsowanie XML
  • Debugowanie
  • Przetwarzanie danych wejściowych i wyjściowych
  • Zarządzanie ścieżkami, plikami, katalogami
  • Adnotacje
  • Przegląd języków programowania wykorzystujących JVM
  • Konfiguracja i uruchomienie środowiska programistycznego
  • Zmienne
  • Podstawowe typy danych
  • Typy bez znaku
  • Typy nullable
  • Literały
  • Zmienne var oraz val
  • Konwersje pomiędzy typami
  • Operatory
  • Tablice jednowymiarowe i wielowymiarowe
  • Klasy specjalizowane – uproszczony zapis typów
  • Napisy
  • Instrukcje sterujące oraz pętle
  • Instrukcje break oraz continue
  • Zagadnienia uzupełniające
  • Definiowanie funkcji
  • Parametry funkcji
  • Argumenty domyślne
  • Argumenty nazwane
  • Funkcje o zmiennej ilości argumentów
  • Wartość zwracana przez funkcję – typ Unit
  • Funkcje oznaczone specyfikatorem infix
  • Funkcje inline
  • Rekurencja i funkcje oznaczone specyfikatorem tailrec
  • Funkcje wyższych rzędów
  • Typy funkcyjne
  • Funkcje anonimowe
  • Wyrażenia lambda
  • Słowo kluczowe it
  • Zwracanie wartości z wyrażenia lambda
  • Operator _ dla nieużywanych parametrów wyrażenia lambda
  • Destrukturyzacja w wyrażeniach lambda
  • Zagadnienia uzupełniające
  • Definiowanie klasy i obiektu
  • Omówienie założeń enkapsulacji
  • Pola składowe w klasie
  • Backing fields
  • Pola stałe w czasie kompilacji – compile-time constants
  • Pola inicjalizowane z opóźnieniem – słowo kluczowe lateinit
  • Metody składowe w klasie
  • Konstruktory – primary constructors, secondary constructors
  • Blok inicjalizatora – słowo kluczowe init
  • Implementacja funkcji dostępowych oraz modyfikujących
  • Klasy zagnieżdżone
  • Klasy wewnętrzne i anonimowe klasy wewnętrzne
  • Wyrażenia obiektowe
  • Deklaracja obiektów
  • Obiekty towarzyszące
  • Kompozycja
  • Zagadnienia uzupełniające
  • Omówienie założeń dziedziczenia
  • Klasa Any
  • Słowo kluczowe open
  • Klasy nadrzędne i podrzędne
  • Odwołanie do klasy nadrzędnej – słowo kluczowe super
  • Przesłanianie metod
  • Polimorfizm
  • Metody i klasy final
  • Przesłanianie pól składowych
  • Klasy abstrakcyjne
  • Klasy ze specyfikatorem data
  • Klasy ze specyfikatorem sealed
  • Klasy enum
  • Zagadnienia uzupełniające
  • Deklaracja interfejsu
  • Implementowanie interfejsu
  • Przesłanianie pól składowych interfejsu
  • Przesłanianie metod interfejsu
  • Dziedziczenie interfejsów
  • Rozwiązywanie konfliktów związanych z implementowaniem wielu interfejsów
  • Omówienie zasady działania rozszerzeń
  • Funkcje rozszerzające
  • Właściwości rozszerzające
  • Funkcje i właściwości rozszerzające dla obiektów towarzyszących
  • Rozszerzenia jako składniki klasy
  • Zagadnienia uzupełniające
  • Omówienie architektury platformy Docker
  • Zasada działania obrazu oraz kontenera
  • Różnica pomiędzy kontenerem Docker oraz maszyną wirtualną
  • Instalacja platformy Docker
  • Praca z Docker CLI
  • Tworzenie i zarządzanie obrazami platformy Docker
  • Image Layers
  • Wprowadzenie do pracy z Docker Hub
  • Osadzenie przygotowanego obrazu w repozytorium Docker Hub
  • Tworzenie i zarządzanie kontenerami w platformie Docker
  • Omówienie cyklu życia kontenera
  • Kopiowanie plików z maszyny lokalnej do kontenera
  • Kopiowanie plików z kontenera do maszyny lokalnej
  • Klasyfikacja rodzajów danych przechowywanych w kontenerach
  • Tworzenie i zarządzanie volumes w platformie Docker
  • Różnica pomiędzy anonymous volumes oraz named volumes
  • Tworzenie i zarządzanie bind mounts w platformie Docker
  • Omówienie różnic pomiędzy volumes oraz bind mounts
  • Tworzenie volumes tylko do odczytu
  • Instalacja i uruchamianie przykładowych narzędzi z wykorzystaniem platformy Docker
  • Omówienie struktury pliku konfiguracyjnego Dockerfile
  • Praca z argumentami oraz zmiennymi środowiskowymi
  • Konteneryzacja aplikacji webowej z wykorzystaniem Docker
  • Komunikacja kontenera z zewnętrznym REST API
  • Komunikacja kontenera z narzędziami osadzonymi w localhost
  • Komunikacja container to container z użyciem networks w platformie Docker
  • Przegląd sterowników do konfiguracji zachowania networks
  • Omówienie zasady działania narzędzia Docker Compose
  • Instalacja narzędzia Docker Compose
  • Struktura pliku konfiguracyjnego narzędzia Docker Compose
  • Przygotowanie kontenera bazy danych 
  • Konfiguracja kontenerów do pracy z wieloma bazami danych
  • Konteneryzacja aplikacji webowej
  • Przykład tworzenia utility containers
  • Zagadnienia uzupełniające
  • Omówienie architektury GIT
  • Konfiguracja i personalizacja ustawień GIT
  • Klasyfikacja repozytoriów
  • Tworzenie repozytorium lokalnego
  • Zarządzanie plikami w ramach obszarów repozytorium
  • Rozgałęzianie i scalanie
  • Przegląd wszystkich operacji GIT
  • Aplikacje i wtyczki do pracy z GIT
  • Praca ze zdalnym repozytorium
  • Dobre praktyki tworzenia repozytoriów GIT
  • GitHub Pages
  • Zagadnienia uzupełniające

Materiały video

Wstecz 1 z 11 Dalej
Wstecz 1 z 11 Dalej

Newsletter

Zapisz się na KURS i zyskaj - licencja JetBrains na 6 miesięcy za darmo!