Уроки
Принцип работы JVM
O JVM
Java Virtual Machine
Виртуальная машина Java (JVM) – это механизм, обеспечивающий среду выполнения для управления Java-кодом или приложениями. Он преобразует байт-код Java в машинный язык. JVM является частью Java Run Environment (JRE). В других языках программирования, к примеру, в C, C++ компилятор создает машинный код для конкретной системы. Однако компилятор Java создает код для виртуальной машины, известной как виртуальная машина Java.

Немного предыстории
До появления Java, примерно в январе 1996 года, многие приложения писали на С или С++. У этих языков есть проблема: разработчику приходится думать, на какой операционке и архитектуре процессора будет работать его код. Например, если он пишет программу под Linux, то, скорее всего, она не запустится на Windows или MacOS. Поэтому код приходилось пичкать директивами условной компиляции или писать отдельную версию для каждой операционки.

Самую сильную боль вызывали приложения с GUI — код графических компонентов для разных операционок был совершенно разным. Причина в том, что C и C++ довольно близки к железу, а значит, сишник должен учитывать архитектуру процессора и тип операционной системы. Понятно, что никакой кроссплатформенностью здесь и не пахнет.

Ещё в 1960-е годы у инженеров появилась идея: писать программы не для конкретного железа, а для абстрактного «исполнителя». Программы на Java как раз пишутся для такого исполнителя — виртуальной машины, или Java Virtual Machine (JVM). Java-разработчик не задумывается, на какой платформе будет запускаться его код. В то же время виртуальная машина не знает, что исполняет инструкции на Java, ведь она принимает и исполняет байт-код.

Две основные функции JVM:
  • Позволяет запускать Java-приложения на любых устройствах или операционных системах (принцип — «Написал один раз, запускай везде»)
  • Управляет и оптимизирует память, используемую приложением.
Архитектура JVM
Архитектура jvm состоит из нескольких частей
  • Загрузчик классов
  • JVM-память
  • Исполнительный механизм
  • Интерфейс системных методов
  • Библиотека нативных методов
Загрузчик классов
Он в основном отвечает за три вида деятельности.
  • Загрузка
  • Связывание
  • Инициализация
Загрузка
Загрузчик классов считывает файл .class, создает соответствующие двоичные данные и сохраняет их в области методов.
Для каждого файла .class JVM хранит следующую информацию в области методов:
  • Полное имя загруженного класса и его непосредственного родительского класса.
  • Связан ли файл .class с классом, интерфейсом или перечислением.
  • Информация о модификаторах, переменных и методах и т. д.
После загрузки файла .class JVM создает объект типа Class для представления этого файла в динамической памяти. Обратите внимание, что этот объект имеет тип Class, предопределенный в пакете java.lang. Этот объект класса может использоваться программистом для получения информации об уровне класса, такой как имя класса, имя родителя, методы и информация о переменной и т. д.

Чтобы получить ссылку на этот объект, мы можем использовать метод getClass() класса Object.

Пример кода:
import java.lang.reflect.Field;
import java.lang.reflect.Method;


public class Test {
    public static void main(String[] args) {
        Student s1 = new Student();
        Class c1 = s1.getClass();
        System.out.println(c1.getName());
        // Получить массив методов
        Method[] methods = c1.getDeclaredMethods();
        for (Method method : methods)
            System.out.println(method.getName());
        //Получить массив полей
        Field[] fields = c1.getDeclaredFields();
        for (Field field : fields)
            System.out.println(field.getName());
    }
}


class Student {
    private String name;
    private int number;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}
Для каждого загруженного файла « .class» создается только один объект класса.

Связывание
Выполняет проверку, подготовку и (необязательно) разрешение.
  • Проверка – проверяет правильность файла .class , т. е. проверяет, правильно ли этот файл отформатирован и сгенерирован допустимым компилятором или нет. Если проверка не пройдена, мы получаем исключение времени выполнения java.lang.VerifyError . Это действие выполняется компонентом ByteCodeVerifier. После завершения этого действия файл класса готов к компиляции.
  • Подготовка – JVM выделяет память для переменных класса и инициализирует память значениями по умолчанию.
  • Разрешение – это процесс замены символических ссылок из типа прямыми ссылками. Это делается путем поиска в области метода, чтобы найти объект, на который ссылаются.

Инициализация
На этом этапе всем статическим переменным присваиваются значения, определенные в коде и статическом блоке (если есть). Это выполняется сверху вниз в классе и от родителя к дочернему в иерархии классов.

Есть три загрузчика классов:
  • Загрузчик классов начальной загрузки – каждая реализация JVM должна иметь загрузчик классов начальной загрузки, способный загружать доверенные классы. Он загружает основные классы API Java, присутствующие в каталоге «JAVA_HOME/jre/lib». Этот путь широко известен как путь начальной загрузки. Он реализован на родных языках, таких как C, C++.
  • Загрузчик класса расширения – это дочерний элемент загрузчика класса начальной загрузки. Он загружает классы, присутствующие в каталогах расширений «JAVA_HOME/jre/lib/ext» (путь расширения) или в любом другом каталоге, указанном системным свойством java.ext.dirs. Он реализован в java классом sun.misc.Launcher$ExtClassLoader.
  • Загрузчик класса системы/приложения – это дочерний загрузчик класса расширения. Он отвечает за загрузку классов из пути к классам приложения. Он внутренне использует переменную среды, которая сопоставляется с java.class.path. Он также реализован в Java классом sun.misc.Launcher$AppClassLoader.

JVM следует принципу делегирования-иерархии для загрузки классов. Загрузчик системного класса делегирует запрос загрузки загрузчику класса расширения, а загрузчик класса расширения делегирует запрос загрузчику класса начальной загрузки. Если класс найден в пути начальной загрузки, класс загружается, в противном случае запрос снова передается загрузчику класса расширения, а затем системному загрузчику класса. Наконец, если загрузчику системного класса не удается загрузить класс, мы получаем исключение времени выполнения java.lang.ClassNotFoundException .
JVM – Память
Method Area(Область методов)
В области методов хранится вся информация уровня класса, такая как имя класса, имя непосредственного родительского класса, информация о методах и переменных и т. д., включая статические переменные. У каждой JVM есть только одна область методов, и это общий ресурс.

Heap&Stack
Для оптимальной работы приложения JVM делит память на область стека (stack) и область кучи (heap). Всякий раз, когда мы объявляем новые переменные, создаем объекты или вызываем новый метод, JVM выделяет память для этих операций в стеке или в куче.

Heap
Эта область памяти используется для динамического выделения памяти для объектов и классов JRE во время выполнения. Новые объекты всегда создаются в heap'e, а ссылки на них хранятся в стеке.
Эти объекты имеют глобальный доступ и могут быть получены из любого места программы.

Эта область памяти разбита на несколько более мелких частей, называемых поколениями:
  • Young Generation — область, где размещаются недавно созданные объекты. Когда она заполняется, происходит быстрая сборка мусора
  • Old (Tenured) Generation — здесь хранятся долгоживущие объекты. Когда объекты из Young Generation достигают определенного порога «возраста», они перемещаются в Old Generation
  • Permanent Generation — эта область содержит метаинформацию о классах и методах приложения, но начиная с Java 8 данная область памяти была упразднена. Так же мы можем управлять размерами кучи в зависимости от наших требований.
Помимо рассмотренных ранее, heap имеет следующие ключевые особенности
  • Когда эта область памяти полностью заполняется, Java бросает java.lang.OutOfMemoryError
  • Доступ к ней медленнее, чем к стеку
  • Эта память, в отличие от стека, автоматически не освобождается. Для сбора неиспользуемых объектов используется сборщик мусора
  • В отличие от стека, heap не является потокобезопасной и ее необходимо контролировать, правильно синхронизируя код
Stack
Стек работает по схеме LIFO (последним вошел, первым вышел).
Всякий раз, когда вызывается новый метод, содержащий примитивные значения или ссылки на объекты, то на вершине стека под них выделяется блок памяти. Из этого можно сделать вывод, что стек хранит значения примитивных переменных, создаваемых в методах, а также ссылки на объекты в куче, на которые ссылается метод. Когда метод завершает выполнение, блок памяти (frame), отведенный для его нужд, очищается, и пространство становится доступным для следующего метода. При этом поток выполнения программы возвращается к месту вызова этого метода с последующим переходом к следующей строке кода.

Помимо того, что мы рассмотрели, существуют и другие особенности стека
Он заполняется и освобождается по мере вызова и завершения новых методов. Переменные в стеке существуют до тех пор, пока выполняется метод, в котором они были созданы. Если память стека будет заполнена, Java бросит исключение java.lang.StackOverFlowError. Доступ к этой области памяти осуществляется быстрее, чем к куче. Является потокобезопасным, поскольку для каждого потока создается свой отдельный стек

Пример
Рассмотрим пример кода и далее разберем его:
class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class PersonBuilder {
    private static Person buildPerson(int id, String name) {
        return new Person(id, name);
    }

    public static void main(String[] args) {
        int id = 23;
        String name = "John";
        Person person = null;
        person = buildPerson(id, name);
    }
}
Рассмотрим выполнение кода по шагам
  • До начала выполнения метода main() в стеке будет выделено пространство для хранения примитивов и ссылок этого метода: примитивное значение id типа int будет храниться непосредственно в стеке; ссылочная переменная name типа String будет создана в стеке, но сама строка "John" будет храниться в области, называемой String Pool (является частью Кучи); ссылочная переменная person типа Person будет также создана в памяти стека, но будет указывать на объект, расположенный в куче;
  • Для вызова конструктора с параметрами Person (int, String) из метода main() в стеке поверх предыдущего вызова метода main() будет выделен блок памяти, который будет хранить: this — ссылка на текущий объект; примитивное значение id; ссылочную переменную name типа String, которая указывает на объект строки из пула строк;
  • В методе main() дополнительно вызывается метод buildPerson для которого будет выделен блок памяти в стеке поверх предыдущего вызова. Этот блок снова сохранит переменные способом, описанным выше.
  • Для вновь созданного объекта person типа Person все переменные будут сохранены в памяти кучи.
Исполнительный механизм
Механизм выполнения выполняет .class (байт-код). Он считывает байт-код построчно, использует данные и информацию, находящиеся в различных областях памяти, и выполняет инструкции.

Его можно разделить на три части:
  • Интерпретатор
  • JIT-Компилятор
  • Сборщик мусора
Интерпретатор
Интерпретатор считывает инструкции байт-кода и выполняет их последовательно

Компилятор Just-In-Time (JIT)
JIT-компиляция — это способ динамической компиляции, которая запускается после запуска программы и компилирует ее код «на лету». JIT-компиляторы преобразуют код высокоуровневых языков в инструкции, понятные виртуальной машине или процессору. JIT — это Just-inTime, что подразумевает компиляцию кода, когда это нужно, а не до выполнения программы, в реальном времени.

JIT обладает следующими свойствами
  • не весь код сразу компилируется в машинный, а только необходимая часть, — это снижает нагрузку на процессор;
  • машинный код генерируется во время выполнения программы, значит, он будет изначально оптимизирован под архитектуру устройства, что позволит делать кроссплатформенные программы.
Главный принцип JIT — это компилировать не всю программу, а лишь те ее участки, которые наиболее часто используются пользователем. Такой подход ускоряет работу программы в будущем, поэтому сами программы становятся производительнее, потому что виртуальная машина будет выполнять уже скомпилированный машинный код, а не компилировать его еще раз.

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

Сборщик мусора
Мусором считается объект, который больше не может быть доступен по ссылке из какого-либо объекта. Поскольку такие объекты больше не используются в приложении, то их можно удалить из памяти.
Например, в приведенном ниже коде объект student2 может быть удален из памяти, поскольку на него нет ссылок.
Student student = new Student("S1");
Student student2 = new Student("S2");
student2 = null;
Вот диаграмма:
Краткий итог
Java Virtual Machine – это среда выполнения для управления Java-кодом или приложениями. Он преобразует байт-код Java в машинный язык
  • Позволяет запускать код на любой "машине"
  • Сам управляет памятью
  • Раньше разработчик думал об этом сам
Основые компоненты Архитектуры JVM
  • Загрузчик классов
  • Heap
  • Stack
  • Исполнительный механизм
  • JIT-Компилятор
  • Сборщик мусора
Пакетные менеджеры
Как Java внедряет сторонние библиотеки
Разработка программного обеспечения построена на принципе повторного использования кода, который уже был написан (вами или другими программистами). Более того, современные программные системы настолько сложны, что написать их без использования сторонних библиотек и фреймворков практически невозможно. Ведь в таком случае вам бы пришлось всю эту функциональность писать самостоятельно.

Предположим, что вы пишите программу по выводу MD5-хэша для входной строки в консоли. Посмотрите на пример кода ниже.
package com.example;

class Main {

  public static void main(String[] args) {
    String inputStr = args[0];
    String hash = calculateMD5Hash(inputStr);
    System.out.println(hash);
  }

  private static String calculateMD5Hash(String input) {
    // implementation...
  }
}
В данном случае, процесс подсчета MD5-хэша должен быть реализован в функции calculateMD5Hash. Но зачем нам его писать самостоятельно, если кто-то уже сделал это за нас? Допустим, что существует некий класс com.apache.HashUtil, который предоставляет статический метод md5. Тогда наш код изменится.
package com.example;

import com.apache.HashUtil;

class Main {

  public static void main(String[] args) {
    String inputStr = args[0];
    String hash = HashUtil.md5(inputStr);
    System.out.println(hash);
  }
}
Процесс компиляции и запуска этой программы в терминале представлен ниже.
javac Main.java       # компиляция в byte-код (файл с расширением .class)
java com.example.Main # запуска программы (указываем название класса вместе с пакетом)
При запуске мы получим ошибку ClassNotFoundException в той строчке, где происходит обращение к HashUtil. Дело в том, что JVM не знает, откуда прочитать byte-код класса HashUtil. Для решения этой проблемы существует classpath. Это указание директории, откуда JVM будет загружать классы. Предположим, что скомпилированный класс HashUtil находится в той же директории, что и Main.java. Тогда процесс компиляции и запуска будет выглядеть следующим образом.
javac Main.java
java -classpath . com.example.Main # несколько директорий classpath можно разделять знаком ;
А теперь представьте, что вам нужно использовать не один сторонний класс, а десятки или даже сотни. Если следовать предложенному сценарию, вам придется:
  1. Искать все нужные библиотеки в Интернете ручным поиском.
  2. Сохранять их в одну директорию в системе.
  3. Корректно указывать classpath при запуске.
  4. Вовремя обновляться до актуальных версий.
  5. Каждому программисту из вашей команды придется повторно проделывать эти действия.
Чтобы автоматизировать и упростить эти шаги, придумали пакетные менеджеры.
Maven
Maven – один из самых популярных пакетных менеджеров для Java на текущий момент. Создать новый Java-проект на основе Maven можно с помощью IDEA.

Суть пакетного менеджера проста:
  • Каждая зависимость характеризуется тремя параметрами: groupId, artifactId и version. Есть правила относительно того, какими groupId пользователь может маркировать модули, которые он публикует в открытый доступ. Например, для Maven Central groupId должен быть равен реверсивному доменному имени (например, google.com превращается в com.google). Причем тот, кто производит публикацию, должен доказать, что он владеет указанным доменом (есть определенная процедура проверки). artifactId может быть любым, но обычно он совпадает с названием конкретной библиотеки. version же – это версия зависимости. Обычно записывается в формате 1.2.3.
  • Все модули публикуются в едином реестре – репозитории. По умолчанию Maven скачивает зависимости из Maven Central. Но в компаниях часто используются приватные репозитории, которые недоступны извне.
  • Зависимости описываются декларативно в файле pom.xml.
Посмотрите на пример описания зависимости на библиотеку Apache Commons.
<dependencies>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
  </dependency>
</dependencies>
Maven предоставляет команду package, которая собирает ваш код и все зависимости в единый артефакт – файл с расширением .jar. При запуске вам не нужно указывать classpath, так как все зависимости уже инкапсулированы внутри.

Таким образом, Maven предоставляет:
  • Универсальные правила для идентификации зависимостей
  • Декларативный способ объявления нужных зависимостей и их версий
  • Простой способ смены удаленного репозитория, откуда зависимости будут запрашиваться (достаточно указать нужный remote в теге repository).
Есть правда один нюанс, на который вы могли обратить внимание. При импорте класса в программе вы указываете только его package и название. Что произойдет, если в classpath окажутся несколько одинаковых классов разных версий? Это хороший вопрос. Дело в том, что JVM загружает только первый класс, идентифицируя его по сочетанию package + class. Последующие дубликаты будут игнорироваться. А вот какой именно класс JVM расценит как «первый», неизвестно. Поэтому нужно внимательно следить за тем, чтобы в вашей программе не было расхождений по версиям. Иначе это может привести, например, к MethodNotFoundException (вы ожидали вызвать метод класса из новой версии, но загрузилась старая, где он еще не был добавлен). В Maven есть команда dependency:tree, которая покажет все зависимости проекта. После этого вы сможете точечно убрать ненужные с помощью тега exclusion.

Создать новый Java проект на Maven можно двумя способами:
  • С помощью командной строки. Например, приведенная ниже команда создаст Maven проект для Java со стандартными директориями.
mvn archetype:generate \
	-DgroupId=com.mts.meta \
	-DartifactId=student-project \
	-DarchetypeArtifactId=maven-archetype-quickstart \
	-DinteractiveMode=false
  • С помощью IDEA. Выберите пункт: File -> Project... -> Maven
Внедрение Maven Shade Plugin
Давайте в наш Java-проект добавим Maven Shade Plugin, который соберет проект и все наши зависимости в jar файл. Посмотрите на блок кода ниже, который нужно добавить в pom.xml:
<build>
   
    <plugins>
       
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <shadedArtifactAttached>true</shadedArtifactAttached>
              <transformers>
                <transformer implementation=
                  "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>org.example.Main</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
       
    </plugins>
   
</build>
Тег build содержит конфигурацию сборки. В plugins располагаем плагины (их может быть несколько). Ну и затем добавляем информацию о самом maven-shade-plugin. Обратите внимание на тег mainClass. В нем указывается название класса, который содержит функцию main, вместе с package. Если вы указали другое название package при создании проекта, он может немного отличаться. Проверьте, что у вас все совпадает.

Теперь запустите команду package в Maven. Это можно сделать в Idea, открыв вкладку Maven, либо выполнив следующую команду в консоли:
mvn package
Чтобы операция в консоли выполнилась, нужно, чтобы команда mvn была доступна. Для этого вам нужно будет явно скачать и установить Maven (в Idea он встроен).

После этого откройте папку target в вашем проекте и запустите собранный jar файл:
java -jar my-test-project-1.0-SNAPSHOT-shaded.jar
Название jar файла может немного отличаться. Вам нужно выбрать тот, что с суффиксом shaded. Также команда java должна быть доступна из командной строки. Для этого нужно установить JDK 17.

Если вы увидели в консоли тот результат, который добавляли в функцию main, то вы всё сделали правильно. Поздравляем!

Если у вас возникли трудности, можете посмотреть пример готового pom.xml по этой ссылке.
Пара слов о Gradle
Хотя Maven остается одним из самых популярных пакетных менеджеров для Java, другие решения также набирают обороты. Например, есть такая штука как Gradle. Это более современная альтернатива, которая призвана побороть ограничения декларативного подхода в объявлении зависимостей. Вместо XML используется Groovy DSL. Тем не менее, принципы сохраняются те же самые (репозитории, идентификация зависимостей с помощью тройки artifactId-groupId-version и так далее).

Все примеры в курсе будут показаны на Maven, но вы можете полюбопытствовать и использовать Gradle. Это не запрещается.
Как Java захватила мир
Одной из главных причин популярности Java является её платформонезависимость. Она достигается тем, что исходники Java-программы собираются не в родной формат платформы, а в так называемый байткод, который выполняется в виртуальной машине Java (Java virtual machine – JVM). JVM – это спецификация, описывающая, как программа должна выполняться. А программная реализация JVM входит в состав среды выполнения java-программ – Java Runtime Environment (JRE). Для каждой платформы существует своя версия JRE. Таким образом, Java-программу можно запустить на любой платформе: главное, чтобы на ней была установлена JRE.

Другой причиной популярности Java является то, что разработчики языка стараются поддерживать его обратную совместимость: код, написанный на старой версии Java, будет работать и на более свежей версии. Это делает Java надёжным, стабильным инструментом, поэтому многие проекты для крупных компаний, таких как банки и страховые компании, реализованы именно на Java.

В Java реализован сборщик мусора (garbage collector), что, во-первых, облегчает разработку программ, поскольку разработчику не нужно самостоятельно освобождать память, а во-вторых, делает программы более стабильными, поскольку в них становится меньше утечек памяти, вызванных некорректными действиями разработчика.
Java – объектно-ориентированный язык, а ООП – одна из наиболее популярных парадигм программирования.

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

Другим популярным языком программирования с ООП-подходом является C#. Данный язык был выпущен в 2000-м году и долгое время был рассчитан на выполнение только в среде Windows. В 2016 году компания Microsoft выпустила .NET Core в попытке добавить платформонезависимость, однако к тому моменту рынок уже принадлежал Java.

У обратной совместимости в Java есть негативная сторона: язык привязан к тем решениям, которые были приняты много лет назад. Даже если впоследствии оказывается, что решение было не самое лучшее, оно всё равно должно остаться в языке, чтобы не нарушить обратную совместимость. Из-за этого каждое нововведение в языке делалось осторожно, что являлось причиной обвинений в медленном развитии. Если посмотреть на историю выхода версий Java, то будет видно, что критика была обоснованной: между выходами 7, 8 и 9 версиями проходило по 3 года. Однако одновременно с выходом Java 9 было принято решение перейти на полугодовой релизный цикл Java, который соблюдается по сей день. Благодаря этому в языке регулярно появляются новые возможности: Stream API, лямбда-выражения, records, pattern matching, sealed classes и так далее.
Пример CI на Java
В прошлом модуле мы рассмотрели с вами основы Git. Давайте сохраним ту работу, что мы уже сделали, в репозитории и настроим там CI-процесс для сборки нашего проекта.

Откройте репозиторий, который вы создали в прошлом модуле. Давайте немного отредактируем .gitignore (мы обсуждали смысл этого файла в прошлом модуле). Его контент вы можете посмотреть ниже. Здесь мы игнорируем содержимое папки target, где находятся бинарники (результат компиляции программы), и оставляем только исходный код.

Содержимое .gitignore
*#
*.iml
*.ipr
*.iws
*.jar
*.sw?
*~
.#*
.*.md.html
.DS_Store
.attach_pid*
.classpath
.factorypath
.gradle
.idea
.metadata
.project
.recommenders
.settings
.springBeans
.vscode
/code
MANIFEST.MF
_site/
activemq-data
bin
build
!/**/src/**/bin
!/**/src/**/build
build.log
dependency-reduced-pom.xml
dump.rdb
interpolated*.xml
lib/
manifest.yml
out
overridedb.*
target
.flattened-pom.xml
secrets.yml
.gradletasknamecache
.sts4-cache
Также стоит добавить еще файл .gitattributes. Он нужен для того, чтобы автоматически преобразовывать переносы строк в LF, потому что на Windows и Linux/Mac они отличаются. Пример содержимого:
* text=auto
Теперь сделайте коммит и запушьте то, что вы сделали на Java в рамках этого модуля. Что же, осталось лишь настроить CI-процесс. В прошлом модуле мы рассматривали пример с созданием простого файла .github/workflows/build.yml. Добавьте такой же. Правда содержимое будет другим:
name: Java CI with Maven

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: maven
      - name: Build with Maven
        run: mvn package
Здесь мы выполняем следующие шаги:
  • Устанавливаем Java 17.
  • Запускаем команду mvn package, которая и собирает проект.
Если вы всё сделали правильно, то увидите вот такой желтый значок рядом с количеством коммитов:
Желтый цвет означает, что сборка идет. Вы можете нажать на иконку, чтобы посмотреть логи. Когда значок сменится на зеленую галочку, это будет значить, что билд прошел успешно.

Если у вас возникли затруднения, вы можете посмотреть на пример этого тестового проекта.
ТЕСТ
Проверь свои знания
на практике и прикрепляй
в репозиторий скриншот с результатами!
Проходи тест только после того, как изучишь все уроки спринта!
Пройти тест
Для высокоуровневых языков програграммирования справедливы следующие утверждения
Да, это верный ответ! Вспомни первый урок: * Кроссплатформенность - Это значит, что результирующий «билд» должен запускаться не только на той системе, на которой он был собран, а на любой ( или почти любой). *
Как раз таки нет, наоборот высокоуровневый ЯП абстрагирует нас от работы с памятью.
Неа, порог входа ниже, так как написанный код описывает бизнес-логику, а не детали работы с памятью.
Дальше
Проверить
Узнать результат
Какие типизации языков есть?
В статически типизированных языках корректность типов проверяется на этапе компиляции.
Означает отсутствие или наличие автоматического преобразование типов.
Неявная типизация - компилятор сам приводит типы данных. Явная - программист.
Это неверный ответ, так как Низкоуровневой/Высокоуровневой типизация не существует, зато есть языки программирования.
Дальше
Проверить
Узнать результат
Методика разработки программ, в основе которой лежит понятие объекта как некоторой структуры, описывающей объект реального мира, его поведение, – это …
Да, это верный ответ - Ключевым понятием в ООП является класс. Класс - это некая сущность, содержащая данные, а также методы для их обработки.
В логических языках программы задаются в виде логических утверждений и правил вывода
Программа разбивается на блоки — подпрограммы (изолированные друг от друга), а основными элементами управления являются последовательность команд, ветвление и цикл
Базовым элементом является функция, а сама задача моделируется в виде их композиций. Например, f(.) и g(.)— функции, а f(g(.)) — их композиция.
Дальше
Проверить
Узнать результат
Возможность скрыть внутреннее устройство объекта от его пользователей, предоставив через интерфейс доступ только к тем членам объекта, с которыми клиенту разрешается работать напрямую, – это
Позволяет выделить что то общее в отдельный класс с дополнительными свойствами и/или поведением.
Да, верный ответ! Это сокрытие поведения объекта внутри этого объекта.
Набор значимых характеристик объекта, исключая из рассмотрения незначимые.
Полиморфизм - это переопределение поведения путем подстановки подкласса или реализации интерфейса.
Дальше
Проверить
Узнать результат
Что такое Java Virtual Machine (JVM)?
Место в котором можно писать код называется IDE.
Верно! Это среда выполнения для управления Java-кодом
Набор библиотек это JDK.
Дальше
Проверить
Узнать результат
Что такое JIT – компиляция?
Это не так
Верный ответ! Это способ динамической компиляции, которая запускается после запуска программы и компилирует ее код «на лету».
Это не так
Это не так
Дальше
Проверить
Узнать результат
Какие задачи выполняет Загрузчик классов
Ага, верно! Загрузчик классов считывает файл .class, создает соответствующие двоичные данные и сохраняет их в области методов
И это верный ответ! Связывание - Выполняет проверку, подготовку и (необязательно) разрешение
Верно! На этом этапе всем статическим переменным присваиваются значения, определенные в коде и статическом блоке (если есть)
Это не так
Дальше
Проверить
Узнать результат
Для Heap верные следующие утверждения
Да это верный ответ! Эта область памяти разбита на несколько более мелких частей, называемых поколениями: 'Young Generation, Old Generation, Permanent Generation'
Неа, наоборот, новые обеъекты создаются в heap'e, а ссылки уже хранятся в stack'e
Неверно! Это исключение бросается когда переполнен stack, когда перепонен heap мы получим java.lang.OutOfMemoryError
Дальше
Проверить
Узнать результат
Выберите верные утверждения про enum в Java
Да это верный ответ! В Java enum является синтаксическом сахаром для класса, у которого жестко зафиксированы определенные наборы экземпляров.
Неверно. В Java enum – это класс.
Неверно. В Java enum – это класс.
Неверно. Каждый enum в Java неявно наследуется от абстрактного класса Enum. Поскольку Java запрещает множественное наследование (от нескольких классов), то наследовать enum от классов нельзя.
Правильно! В Java enum, как и любой класс, может наследовать любое количество интерфейсов.
Совершенно точно! Как и обычный класс, enum в Java может содержать поля.
Дальше
Проверить
Узнать результат
Выберите верные утверждения про null в Java
Верно! В Java null является универсальным значением, означающим отсутствие данных. Может быть присвоен объекту любого типа.
Неверно. Это значение можно присваивать любому объекту.
Неверно. Примитивы в Java не могут быть null.
Дальше
Проверить
Узнать результат
Практическое задание
Выполняй его после прохождения всех уроков спринта
Practice
Необходимо спроектировать систему «Зоопарк». Требования:
  1. Есть лошади, тигры, дельфины, орлы и верблюды.
  2. Животные могут быть травоядными, хищниками, сухопутными, водоплавающими и летающими.
  3. Должны быть реализованы следующие методы:
  4. Сухопутные могут ходить
  5. Водоплавающие могут плыть
  6. Травоядные и хищники могут есть, но набор еды, который они принимают на вход, разный. Причем тигры едят только говядину, дельфины только рыбу, а лошади и верблюды - траву. Орлы едят любое мясо. Еда для травоядных не подразделяется на категории. Для простоты можете считать ее одним типом Grass.
  7. Летающие могут лететь.
  8. В качестве реализации методов достаточно будет напечатать в консоль соответствующую фразу. Например, «орел летит», или «дельфин ест».
  9. Добавьте в main несколько примеров вызовов методов.
В рамках MR нужно будет:
  1. Доработать текущий репозиторий так, чтобы это был Java-проект на Java 17 с Maven (также можно использовать Gradle).
  2. Реализовать вышеописанную функциональность.
  3. Добавить этап Maven-сборки полученного java проекта в .github/workflows/build.yml (пример есть в этом модуле).
  4. Добавить maven-shade-plugin для сборки jar (смотрите пример также в этом модуле).
  5. Отправить Pull Request на ревью ментору. Важно, что Pull Request должен проходить сборку успешно (проверьте, что pipeline зеленый).

Дополнительное практическое задание
Его выполнение необязательно, но даст вам дополнительные баллы

В README.md написать эссе минимум на 200 слов на тему «Задачи, с которыми языки с динамической типизацией справляются лучше, чем со статической». При оформлении рекомендуем пользоваться шпаргалкой по Markdown.
!
Дедлайн 03.10.2023