Skip to main content

05. Первое приложение

В этой главе мы создадим простейшей оконное приложение, используя современные методы и подходы разработки на Java.

example

Пример готового проекта лежит здесь. Для выполнения текущего задания Fork от него делать запрещается.

В ходе работы вам будет нужно сделать несколько коммитов с промежуточными состояниями проекта, поэтому в прямом копировании нет смысла. Готовый проект даётся для того, чтобы было проще разобраться, как выполнить поставленное задание.

Современная Java

Мы будем использовать современные средства Java, поэтому если у вас стоит версия Java ниже 17, обновите её. Для этого нужно скачать установщик java либо с официального сайта Oracle.

Здесь лежит установщик JDK 17.0.2 для Windows.

Maven

Чтобы автоматически собирать проекты java, используют два основных инструмента: maven и gradle. Оба инструмента являются в своей сути консольными приложениями, но давно уже встроены в продукты jetbrains. Потому рассматривать их установку как отдельного продукта не будем.

Мы будем использовать maven. В консоли он запускается командой mvn. Он позволяет автоматически не только собирать проект, но и скачивать библиотеки. Именно это его умение нам и нужно.

Как в любой системе, в Maven, есть свой набор терминов и понятий.

Зависимости - это те библиотеки, которые непосредственно используются в вашем проекте для компиляции кода или его тестирования.

Плагины же используются самим Maven'ом при сборке проекта или для каких-то других целей (деплоймент, создание файлов проекта для Eclipse и др.).

Подробнее о maven можно прочитать здесь и здесь.

Создадим в Idea проект maven. Для этого нажмём File->New...->Project

maven

В появившемся окне в меню слева выбираем Maven. После этого жмём кнопку Next.

maven

При создании проекта мавен нужно указать немного больше информации. Чтобы её указать, в следующем окне нажмите на треугольник слева от Artifact Coordinates.

maven

Три скрытых пункта не просто так скрыты. Если вы создаёте проект, который не планируете публиковать, то можете просто жать Next, но рассмотреть эти пункты стоит.

Первый параметр - это id группы. Обычно он совпадает с вашим доменом, развёрнутым по уровням. Например, домену example.com соответствует id группы com.example, а домену 'mvn.buran.restidrest.buran.mvn`.

Ключевым понятием Maven является артефакт - это, по сути, любая библиотека, хранящаяся в репозитории. Это может быть какая-то зависимость или плагин. ArtifactId - это id самого проекта. Он у нас будет test.

Последний параметр - это версия. По умолчанию - это первый snapshot. Snapshot – это специальная версия, которая показывает текущую рабочую копию. При каждой сборке мавен проверяет наличие новой snapshot версии на удалённом репозитории.

maven

Вся структура проекта описывается в файле pom.xml (POM – Project Object Model). Он находится в корневой папке проекта. Вы можете его найди в дереве проекта слева.

При создании проекта этот файл открывается автоматически. Этот файл имеет расширение XML. Расшифровывается он как eXtensible Markup Languge - расширяемый язык разметки. Подробнее об этом языке можно прочитать здесь.

XML построен на основе HTML. Он также состоит из парных тегов, оборачивающих ту или иную часть текста. Чтобы обернуть тот или иной текст, нужно поставить открывающий тег до него и закрывающий после. Открывающий тег всегда начинается с символа < и заканчивается символом >. Закрывающий тег начинается также с символа <, а заканчивается двумя />. Между этими знаками помещается слово. При этом слова закрывающего и открывающего тега должны совпадать.

Например <tag1>my text</tag1>.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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>rest.buran.mvn</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

</project>

Первая строка любого xml файла задаёт версию, у нас это 1.0, и кодировку, у нас это UTF-8. Это один из немногих непарных тэгов.

Дальше идёт уже описание проекта. Оно оборачивается парным тэгом `.

Многим тэгам можно задавать параметры. У <project> их может быть немало, их рассмотрение выходит за рамки курса.

Тэг <modelVersion>4.0.0</modelVersion> указывает версию.

Следующие теги задают ту информацию, которую мы указали при создании проекта.

    <groupId>rest.buran.mvn</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>

Последний блок - это блок параметров. Он оборачивается тэгом <properties></properties>. В нём указывается версия исходников и версия сборки. У нас обе равны 17, лучше при изменении оставляйте их одинаковыми.

    <properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
Будьте внимательны

Вам нужно опубликовать созданный проект на github. Здесь лежит инструкция, как опубликовать обычный java-проект, вам нужно прочитать теорию на этой странице и выполнить те же действия по аналогии. По аналогии означает не копирование команд, а осмысление, ЧТО ИМЕННО ДЕЛАЕТ КОМАНДА. Когда вы осмыслили, что происходит, вам нужно придумать, какие команды сделают такое же ПО СМЫСЛУ действие, но с вашими файлами.

После того, как вы опубликуете начальную версию проекта, вам нужно просто продолжить работу с ним.

Подключение Skija

Добавим блок зависимостей <dependencies></dependencies>, в нём укажем четыре зависимости.

Первая отвечает за менеджер окон. Хотя у java есть встроенные менеджеры, они являются частью awt и swing, но они уже откровенно устарели. Менеджер окон - это некая прослойка между областью рисования Canvas и операционной системой.

Т.к. мы хотим использовать Skija, то мы возьмём менеджер окон, который советуют её разработчики. Вторая зависимость отвечает за саму Skija.

Третья отвечает за упрощённую работу с json-файлами. JSON формат очень удобен для записи сложных структур данных. Его мы рассмотрим ниже. Но чтобы не возвращаться к конфигурации maven, добавим его зависимость сразу.

Четвёртая зависимость позволит нам запускать Unit-тесты, а пятая упрощает рутинное ООП программирование

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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>rest.buran.mvn</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>io.github.humbleui</groupId>
<artifactId>jwm</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>io.github.humbleui.skija</groupId>
<artifactId>skija-windows</artifactId>
<version>0.96.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>

Обратите внимание: я указал зависимость skija-windows для windows, если вы работаете в другой операционной системе, нужную вам зависимость можете найти здесь.

Для пересборки проекта, откройте pom.xml и нажмите на кнопку Maven в правом верхнем углу.

maven

В открывшемся окне вам нужно нажать на значок обновления.

maven

Дождёмся, пока загрузятся новые зависимости

maven

Теперь добавленные нами зависимости перестали гореть красным.

maven

Чтобы свернуть окно Maven, нажмите на знак -.

Создайте новый коммит skija connected и отправьте его на сервер.

Классы приложения

Откроем дерево проекта. Обратите внимание: в проекте новые папки: resources, java и test.

maven

В папке resources обычно лежат все ресурсы приложения, в java - исходники, test - unit-тесты.

maven

Создадим класс с названием Main.

maven

Пока что он пустой

maven

Введём четыре символа psvm и нажмём Enter.

maven

Idea за нас создаст метод main. Напомню, этот метод - точка входа в программу.

maven

Создадим теперь пакет. Пакет - это просто папка с классами. Для этого нажмите правой кнопкой мыши по папке src. В появившемся меню выберите New->Package.

maven

В центре окна появится поле. Введите в него app и нажмите Enter.

maven

Теперь внутри пакета создадим второй класс.

Для этого уже кликаем по нему, т.е. по папке app, правой кнопкой мыши и выбираем пункт New->Java Class.

maven

У нас появится второй класс Application. В нём мы пропишем всю нужную нам логику, а в классе Main запустим работу приложения с помощью команды:

import app.Application;
import io.github.humbleui.jwm.App;

public class Main {

public static void main(String[] args) {
App.start(Application::new);
}

}

maven

App - это JWM класс, он запускает приложения, описанные в объекте, переданном в аргументе. Реализация этого метода предполагает передачу лямбда-выражения анонимного интерфейса Runnable в качестве аргумента. У него определён единственный метод run(), который ничего не принимает и не возвращает, а просто выполняет всё, что определено в его теле.

Создайте новый коммит classes и отправьте его на сервер.

Ссылки на методы*

Теоретический блок

Вам нужно просто прочитать его. Команды выполнять не требуется.

Вместо лямбда-выражения можно передавать ссылки на статические методы класса. Например, если мы хотим передать методу обработчик Consumer<Integer>, то код будет таким:

import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;

public class Main {

// функция, которая генерирует случайное число от 0 до 1000
// и передаёт его обработчику `consumer`
public static void processRandInt(Consumer<Integer> consumer) {
consumer.accept(ThreadLocalRandom.current().nextInt(1000));
}

// главный метод программы
public static void main(String[] args) {
// передаём обработчик, который просто выводит число на консоль
processRandInt(a-> System.out.println(a));
}
}

Получим:

941

У вас скорее всего будет другое число. Так происходит потому, что мы генерируем случайное целое число из заданного диапазона. В нашем случае диапазон от 00 до 10001000.

Чтобы получить случайное целое число в заданном диапазоне [a,b)[a,b) в общем виде используется команда:

int val = ThreadLocalRandom.current().nextInt(a,b);

После bb в диапазоне [a, b)[a,~b) я указал круглую скобку вместо квадратной. Я так сделал, чтобы показать, что bb - выколотая граница, т.е. в интервале [a, b)[a,~b) достижимы все числа от aa до b1b-1.

Если a=0, тогда указывается только bb

int val = ThreadLocalRandom.current().nextInt(b);

В Idea System.out.println(a) горит неспроста. Если нажать Alt+Enter - автоисправление ошибок и слабых решений, то среда предложит нам заменить лямбда-выражение ссылкой на метод.

maven

Жмём Enter. Idea сама исправит его на ссылку. Теперь наш метод Main будет таким:

import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;

public class Main {

// функция, которая генерирует случайное число от 0 до 1000
// и передаёт его обработчику `consumer`
public static void processRandInt(Consumer<Integer> consumer) {
consumer.accept(ThreadLocalRandom.current().nextInt(1000));
}

// главный метод программы
public static void main(String[] args) {
// передаём обработчик, который просто выводит число на консоль
processRandInt(System.out::println);
}
}

System.out - это внутренний класс System, у него есть метод public void println(Object x), ссылку на который мы теперь передаём вместо лямбда-выражения.

Точно также можно передать ссылку на свой метод, если его интерфейс совпадает с интерфейсом лямбда-выражения.

Например, напишем свой метод, который выводит само число и это же число, но взятое со знаком минус.

public static void display(Integer i) {
System.out.println(i + " " + (-i));
}

Теперь заменим ссылку на метод System.out::println ссылкой на наш метод:

processRandInt(Main::display);

Весь код будет выглядеть так:

import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;

public class Main {

// выводим число и его инверсию
public static void display(Integer i) {
System.out.println(i + " " + (-i));
}

// передаём обработчик, который просто выводит число на консоль
public static void processRandInt(Consumer<Integer> consumer) {
consumer.accept(ThreadLocalRandom.current().nextInt(1000));
}

// главный метод программы
public static void main(String[] args) {
// передаём ссылку на наш метод
processRandInt(Main::display);
}
}

Получим:

76 -76

Опять же у вас вывод будет отличаться по значения, но выглядеть будет примерно также.

Обратите внимание

System.out::println принимал в качестве аргумента Object - базовый для всех элементов класс, но если мы пишем свой метод, то в качестве аргумента надо указать именно то, что ожидается. В нашем случае обработчик ожидает именно Integer, значит, и метод тоже должен принимать в качестве аргумента Integer или любого его потомка.

Обработчик событий

Вернёмся теперь к нашему классу Main:

import app.Application;
import io.github.humbleui.jwm.App;

public class Main {

public static void main(String[] args) {
App.start(Application::new);
}

}

App.start принимает ссылку на метод new. Так в Java обозначается ссылка на конструктор. Причём, т.к. App.start принимает в качестве аргумента реализацию интерфейса Runnable, то рассматриваемый нами метод принимает ссылку на конструктор по умолчанию.

Строго говоря, с этим конструктором потом происходят скрытые преобразования. Но нам достаточно знать, что если мы передадим ссылку на конструктор по умолчанию любого класса, расширяющего особый интерфейс, то на его основе будет создано приложение.

Возможно, это звучит довольно сложно, но по своей сути нам нужно просто приписать после Application дополнительно implements Consumer<Event>. И переопределить в нём метод public void accept(Event event).

Всё общение с объектом нашего приложения выполняется через инструмент событий. Каждое событие - это объект класса Event.

Определение

Событие - это любой сигнал от операционной системы нашему приложению. Например: нажатие кнопок, изменение размера окна, клик по нему. Операционная система не использует впрямую определённый язык, она позволяет обмениваться приложениям при помощи универсальных посылок, а уже менеджеры окон позволят перевести эти универсальные посылки-события в конструкции того или иного языка.

В java основная конструкция - это класс. Поэтому и события передаются в JWM как объекты класса Event.

Грубо говоря, на каждое событие запускается метод accept(Event event), при этом в объекте event, переданном в аргументе, хранится вся информация об этом событии.

Если мы просто вручную добавим эту строчку, то слово Event загорится красным.

maven

Жмём Alt+Enter и в списке выбираем Import class. Чаще всего проблему решает первый вариант из списка. Обычно он самый вероятный.

maven

В новом списке опять же выбираем первый пункт, т.е Event(io.github.hubleui.jwm).

maven

Здесь несколько пунктов, потому что класс Event определён в нескольких библиотеках. Нам нужна io.github.hubleui.jwm, именно её мы скачали при помощи maven.

Теперь Consumer горит красным.

maven

Кликните по нему левой кнопкой мыши и нажмите Alt+Enter.

Теперь нам нужно переопределить метод, заявленный в интерфейсе Consumer.

maven

Опять жмём Alt+Enter, в списке выбираем пункт Implements methods.

maven

В открывшемся меню по умолчанию выделены все необходимые методы. В действительности у обработчика определено два метода. Первый - это просто обработка, второй имеет реализацию по умолчанию и позволяет строить конструкции в духе:

consumer.andThen().andThen()...

Т.е. обработчик можно вызвать несколько раз одной командой. Это довольно удобно, особенно при использовании swich конструкций в лямбда-стиле. Об этом тоже будет рассказано позже.

Жмём в новом окне "ОК".

maven

Теперь нам нужно запустить наше приложение

maven

Жмём в меню Idea Run->Run...

maven

В появившемя меню выбираем Main

maven

Программа запустится, но окна пока что не появилось. Это не просто так.

Останавливаем приложение. Для этого нужно нажать на красный квадрат в верхнем левом углу.

maven

Создайте новый коммит events consumer works и отправьте его на сервер.

Создание окна

Мы прописали обработчик событий, теперь создадим окно.

Дальше я не буду подробно рассматривать импорт методов и классов. Если у вас не запускается программа, скорее всего вы пропустили где-то импорт или указание пакета. Скорее всего вашу проблему решит Alt+Enter.

Можно было бы создать объект окна Window внутри класса Main, но удобнее его сделать полем именно класса Application, потому что из других методов этого же класса мы будем часто обращаться к объекту окна.

Поэтому в Application добавим поле private Window window и инициализируем его в конструкторе.

package app;

import io.github.humbleui.jwm.App;
import io.github.humbleui.jwm.Event;
import io.github.humbleui.jwm.Window;

import java.util.function.Consumer;

public class Application implements Consumer<Event> {
// окно приложения
private final Window window;

// конструктор приложения
public Application() {
// создаём окно
window = App.makeWindow();
// задаём обработчиком событий текущий объект
window.setEventListener(this);
// делаем окно видимым
window.setVisible(true);
}

// обработчик событий
@Override
public void accept(Event event) {

}
}
Обратите внимание

я добавил ключевое слово final у поля окна. На протяжении всей работы приложения мы создаём окно всего один раз в конструкторе. Т.е. всего один раз полю присваивается значение, ещё и в конструкторе. Тогда для защиты кода от взлома, да и самоконтроля следует делать эту переменную неизменяемой, финальной. Иными словами, такие переменные следует делать константами.

Внутри конструктора первой строчкой создаётся окно. Обратите внимание: окно создаётся не с помощью конструктора, а за счёт вызова метода. Этот подход называется фабричный метод. Фабричный метод в зависимости от аргументов создаёт тот или иной объект. Наиболее удачные подходы называют паттернами проектирования. Подробнее о них можно прочитать здесь.

Основная польза такого подхода в том, что мы можем внутри класса, в котором задан фабричный метод, определить ту или иную логику взаимодействия. Более подробно фабрики будут рассмотрены ниже.

Если теперь мы запустим программу, то появится окно:

w

Но пока что окно не получится закрыть. Если мы нажмём на крестик в правом верхнем углу окна, то ничего не произойдёт. Это не просто так. Закрытие окна - это тоже событие.

Пока мы не обрабатываем событие закрытия окна, окно и не будет закрываться. Поэтому остановим программу вручную с помощью красного квадрата и добавим обработку закрытия окна:

package app;

import io.github.humbleui.jwm.*;

import java.util.function.Consumer;

public class Application implements Consumer<Event> {
// окно приложения
private final Window window;

// конструктор приложения
public Application() {
// создаём окно
window = App.makeWindow();
// задаём обработчиком событий текущий объект
window.setEventListener(this);
// делаем окно видимым
window.setVisible(true);
}

// обработчик событий
@Override
public void accept(Event e) {
// если событие - это закрытие окна
if (e instanceof EventWindowClose) {
// завершаем работу приложения
App.terminate();
}else if (e instanceof EventWindowCloseRequest) {
window.close();
}
}
}

В обработчике я использую конструкцию a instanceof CustomClassName. Она позволяет проверить, построен ли объект a на основе класса CustomClassName. В нашем случае нужно узнать, каким именно событием является тот или иной объект, переданный в обработчик. instanceof - самое простое решение этой задачи.

Внутри public void accept(Event e) мы обрабатываем два типа событий. Первый - это непосредственно сигнал, что окно закрыто. В этом случае нам нужно просто завершить работу приложения. Это делается при помощи команды App.terminate().

Второе событие - это запрос на закрытие окна. Есть несколько способов послать такой запрос, но нас интересует только нажатие на крестик. Именно его мы и обрабатываем во втором условии.

Теперь, если нажать на крестик, приложение корректно закроется, а в консоли выведится сообщение:

Process finished with exit code 0

Создайте новый коммит window works и отправьте его на сервер.

JavaDoc

Если проект разрастается до значительных размеров, его следует снабжать документацией. Первый способ - это развёртывание сайта, похожего на этот. Такие сайты заполняются вручную. Второй способ - это автоматическая сборка документации на основе специальных команд в исходниках. Сборка документации - один из основных этапов сборки мавен проекта.

В Java этот инструмент называется JavaDoc. Чтобы написать документацию по тому или иному элементу, перед ним нужно поставить специальную последовательность символов:

    /**
* Текст документации
*/

Для примера задокументируем поле window.

Над полем введите /** и нажмите Enter

w

Idea достроит оформление документации

w

В java при компиляции все строки документации игнорируются, их видит только сборщик документации javaDoc.

Документация может состоять из скольки угодно строк. Главное, чтобы первая была /**, а последняя - */. Все остальные строки должны начинаться с символа * и пробела после.

Добавим правильную подпись в документацию нашего поля.

    /**
* окно приложения
*/
private final Window window;

Попробуем задокументировать таким же полуавтоматическим способом обработчик событий. Для этого над @Override введём /** и нажмём Enter

w

Получим автоматическую документацию:

    /**
*
* @param e
*/

Здесь появилась дополнительная строка @param e. Это специальная команда, позволяющая расширить документацию. Все команды начинаются с символа @. После идёт сама команда и через пробел её аргументы. Например, у команды @param два аргумента. Первый - это название параметра метода, который мы документируем, второй - это текст документации. Причём сам текст может быть многострочным.

    /**
* Обработчик событий
* @param e событие
*
* они бывают разные
*/
@Override
public void accept(Event e) {
...

Многострочный комментарий здесь совсем необязателен. Я его привёл просто для примера.

Удалим лишние строки:

    /**
* Обработчик событий
* @param e событие
*/
@Override
public void accept(Event e) {
// если событие - это закрытие окна
if (e instanceof EventWindowClose) {
// завершаем работу приложения
App.terminate();
}else if (e instanceof EventWindowCloseRequest) {
window.close();
}
}

Чтобы собрать документацию, выберите в меню Tools->Generate JavaDoc

w

В появившемся окне выберите уровень документирования private. Этот параметр задаёт степень закрытости поля, начиная с которой будет выполняться документирование. Например, если мы поставим protected, то документироваться будут все public и protected поля. А если package, то все, кроме private. Сейчас нам нужно убедиться, что документация составлена верно, поэтому выберем максимальный уровень документирования.

Также нам надо указать папку, в которую будет записана документация. Обратите внимание: именно папку, а не папку, в которой будет создана документация. Для выбора папки кликните по соответствующему значку.

w

В новом окне перейдите в раздел, где мы вы планируете создать новую папку. У меня - это диск D:. После этого нажмите на значок создания папки

w

Вводим название и жмём "ОК"

w

Снова жмём "ОК"

w

Жмём снова "ОК"

w

После сборки документации автоматически откроется главная страница документации.

w

JavaDoc собирает документацию в HTML сайт. Главная страница расположена в корне папки с документацией и называется index.html.

w

Заходим в пакет app

w

Выбираем класс Application

w

Страница с документацией отображается с ломанной кодировкой.

w

Это происходит из-за того, что виндоус работает с кодировкой cp-1251, а исходники в utf-8. Это не проблема, просто надо явно сказать JavaDoc, какая кодировка нам нужна.

w

Для этого в поле Other command line arguments введите строку:

-encoding UTF-8 -charset UTF-8 -docencoding UTF-8

Запустим сборку документации заново. Теперь Документация отображается корректно

w

Посмотрим теперь в консоль. В ней выводится несколько ошибок. Так происходит, потому что мы не задокументировали все нужные элементы. У каждой ошибки есть ссылка(выделена синим) на место ощибки. Если по ней кликнуть, idea откроет именно это место.

w

Завершим документацию наших классов:

import app.Application;
import io.github.humbleui.jwm.App;

/**
* Главный класс приложения
*/
public class Main {
/**
* Главный метод приложения
*
* @param args аргументы командной строки
*/
public static void main(String[] args) {
App.start(Application::new);
}
}

Теперь документация собирается без ошибок

w

Если папка с собранной документацией находится внутри проекта, добавьте эту папку в .gitignore.

Создайте новый коммит java doc и отправьте его на сервер.

Задание

Пример готового проекта лежит здесь. Для выполнения первого задания Fork от него делать запрещается.

В ходе работы вам будет нужно сделать несколько коммитов с промежуточными состояниями проекта, поэтому в прямом копировании нет смысла. Готовый проект даётся для того, чтобы было проще разобраться, как выполнить поставленное задание.

Вам необходимо создать свой репозиторий, в котором добавлены следующие коммиты (для каждого сначала указывается название, потом требования):

  1. Init - maven проект idea по умолчанию, все служебные файлы должны быть добавлены в .gitignore.
  2. skija connected - проект с подключенными зависимостями для работы со skija, javaDoc и юнит-тестами
  3. classes - проект с двумя классами Main и App
  4. events consumer works - проект с двумя классами Main и App, при этом App должен расширять интерфейс Constumer<Event>
  5. window works - оконное приложение, созданное средствами skija. Окно должно корректно закрываться при клике по крестику в правом верхнем углу

w

Ссылку на github репозиторий необходимо отправить в поле ввода задания на сайте mdl.

Ссылка на контест