05/03/2020

Великолепная Java (1): сборка проекта — компиляция, JAR

Как начинающим изучать новый язык программирования, так и опытным разработчикам иногда нужно разобраться в том как из кода получается «конечный продукт» — программа, которую можно запускать и поставлять пользователям. В этой статье мы рассмотрим все этапы, необходимые для того чтобы из Java-кода получить программу. Будем изучать Java Basic Tools или Java Development Tools, то есть, ручную, консольную сборку проекта. JDK является основным инструментом в работе с Java. Инструменты эти находятся в каталоге $JAVA_HOME/bin (например /opt/JDK-1.8.0_221/bin/).
Напишем простую программу, файл назовём Hello.java:
class HelloWorld
{
  public static void main (String args[])
  {
    System.out.println ("Hello, Java-World!");
  }
}
Для того чтобы скомпилировать Java-программу, нужно выполнить:
$JAVA_HOME/bin/javac ./Hello.java
javac — компилятор Java, транслирует .java-файлы в .class-файлы. В нашем случае, после работы javac в каталоге появится файл HelloWorld.class. Чтобы запустить нашу программу выполняем:
$JAVA_HOME/bin/java HelloWorld
Обратите внимание на три вещи. Первое — запускаем мы именно класс, то есть, нужно указывать не имя .class-файла, а имя класса (то есть имя .class-файла без расширения). Java сама найдёт (или не найдёт) в текущем каталоге файл, содержащий заданный в параметре класс. Поскольку мы указываем имя класса, а не файла, «./» перед ним ставить не нужно — это приведёт к ошибке. Второе — Java чувствительна к регистру в именах классов, файлов, переменных, поэтому все названия должны указываться с учётом регистра. Третье — каждый .class-файл соответствует имени класса, который он содержит. Имя .class-файла не связано с именем .java-файла, в котором он был реализован. Если в одном .java-файле будут реализованы несколько классов, то javac «разберёт» этот файл на соответствующее количество .class-файл'ов. Продемонстрируем это примером. Дополним файл Hello.java другим классом Hello (дополните предыдущий файл этим фрагментом кода):
class Hello
{
  public static void main(String _Args[])
  {
    try
    {
      System.out.println ("Hello, " + _Args[0] + "!");
    } catch (ArrayIndexOutOfBoundsException _E)
    {
      System.out.println ("Please pass parameter to program.");
    }
  }
}
P.S.
В Java список аргументов организован несколько иначе: нулевой элемент — это уже первый параметр, а не имя исполняемого файла.
Теперь, после компиляции (та же команда, что мы использовали в первый раз) мы увидим два .class-файла: Hello.class и HelloWorld.class. Так как оба файла имеют функцию main (), мы можем запустить оба. HelloWorld — как описано выше, и Hello (который принимает параметр командной строки):
$JAVA_HOME/bin/java Hello Daft
Отсюда мы постепенно приходим к необходимости создания какого-то единого файла для поставки пользователям. Мы же не хотим высылать пользователям море непонятных ему .class-файлов и объяснять какой именно является главным и как его запускать, так?
Для этих целей в Java реализованы Java-архивы (.jar-файлы). Чтобы создать .jar-файл и записать в него класс, нужно выполнить команду:
$JAVA_HOME/bin/jar -cvf ./Hello.jar ./Hello.class
Эта команда заархивирует указанный .class-файл в .jar-файл с заданным именем.
Чтобы запустить .jar-файл выполняем следующее:
$JAVA_HOME/bin/java -cp ./Hello.jar Hello
В предыдущей команде мы запаковывали .class-файл и указывали имя файла (с расширением), здесь мы запускаем класс — указываем имя класса (без расширения). Ключ -cp означает classpath — путь, где нужно искать заданный класс. В данной ситуации — это наш .jar-файл.
Как я уже указывал, .jar-файл является архивом и он действительно сжимается при создании, что дополнительно экономит место на носителе или время при передаче его по сети. Конкретно это zip-архив, мы можем посмотреть его содержимое:
unzip -l ./Hello.jar
Увидим:
Archive:  ./Hello.jar
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  03-04-2020 17:50   META-INF/
       69  03-04-2020 17:50   META-INF/MANIFEST.MF
      736  03-04-2020 17:36   Hello.class
---------                     -------
      805                     3 files
В вышеописанную команду запаковки можно добавить не только классы, но и любые другие файлы, которые в последствии можно будет использовать как ресурсы из ваших программ (иконки, картинки, любое иное содержание и даже другие .jar-файлы).
Но пока запуск скомпилированных и даже уже запакованных в один файл классов является всё-таки не самым «дружественным». Как упростить запуск .jar-файла? Для этого предусмотрена возможность указать главный класс, добавив ключ e (что означает указать точку входа в программу, entry point): 
$JAVA_HOME/bin/jar -cvfe ./Hello.jar Hello ./Hello.class
Теперь мы можем запустить .jar-файл самым простым для Java способом:
$JAVA_HOME/bin/java -jar ./Hello.jar
На этом на сегодня всё. В следующих статьях мы рассмотрим сборку более сложных Java-программ (да, ещё есть куда двигаться).