Программирование в IDE это прекрасно: связанность зависимостей кода, удобный дебаг, понятное тестирование, темная тема. Так вот, благодаря IDE разработка развивается семимильными шагами. Но она расслабляет. С каждым днем, погружаясь в функционал IDE, разработчик привыкает к коммиту одной кнопкой или сборке двумя кликами. Гораздо хуже обстоит ситуация с новичками в программировании, которые с самого начала работают в IDE, игнорируя работу в командной строке. Например, в Intellij IDEA компиляция Java приложения демонстрируется загрузочным баром в нижней панели, а все параметры компиляции, обработка classpath и прочих прелестей Java-жизни остается за кадром. Предлагаем поговорить о компиляции в Java без IDE. Если совсем коротко, весь процесс это две команды: javac Test.java превращает исходник в байт-код и кладет рядом файл Test.class, а java Test этот байт-код запускает. Все остальное в статье это подробности: что делать, когда классов много, как собрать их в jar и что берут на себя Ant, Maven и Gradle. Для запуска примеров в статье следует убедиться, что на вашей машине установлена JDK. Команды javac, java и jar работают начиная с седьмой версии, а для запуска исходника напрямую, о котором речь ниже, понадобится Java 11 или новее.

Кратко

  • Компиляция в Java это превращение исходника .java в байт-код .class, который потом выполняет виртуальная машина.
  • Всю базовую работу делают две команды: javac компилирует, java запускает. Расширение .class при запуске не пишут.
  • Когда классов много, исходники держат в src, результат компиляции отправляют в отдельную папку флагом -d, а при запуске указывают ее в -classpath.
  • Запускать нужно полное имя класса вместе с пакетом, например src.BoxMachine, а не просто BoxMachine.
  • Чтобы программу было удобно переносить, классы собирают в jar-архив командой jar, а главный класс прописывают в файле манифеста.
  • Для проектов крупнее учебных руками это не делают: есть Ant со сценарием в build.xml, Maven с описанием проекта в pom.xml и Gradle с build.gradle.
Заставка в стиле «Матрицы»: зеленые символы, падающие столбцами на черном фоне

Как скомпилировать программу?

Компиляция в программировании это приведение исходного кода в байт-код для последующего старта программы. Порядок действий от исходного кода до запуска программ выглядит так:
  1. Есть исходный код в файле с именем НазваниеКласса.java;
  2. Если в коде нет ошибок, он компилируется в байт-код (в файл НазваниеКласса.class);
  3. Программа запускается.
Обычно каждая программа содержится в отдельном каталоге. В качестве самого простого примера возьмем вывод в консоль:

class Test {
   public static void main(String[] args) {
      System.out.println("Это говорит приложение из командной строки");
   }
}

Для чего нужна команда javac

Окей, первый пункт выполнен. Идем дальше, чтобы понять: скомпилировать это как? :) В этом нам поможет команда javac, в аргументе которой необходимо указать нужный файл:

javac Test.java
Если нет ошибок в коде, рядом с файлом Test.java появится файл Test.class. Это и есть скомпилированный байт-код. Теперь его нужно запустить. Здесь используется команда java, запускающая байт-код: Скриншот консоли: после команды java Test вместо русского текста выводятся нечитаемые символы из-за сбитой кодировкиНа скриншоте видно, что в выводе получаем какие-то иероглифы: очевидно, это сбитая кодировка. Как правило, это происходит в системе Windows. Для корректного отображения кириллицы в консоли, есть следующие команды:

REM change CHCP to UTF-8
CHCP 65001
CLS
Они меняют текущую кодовую страницу командной консоли на время работы текущего окна. Попробуем еще раз:

D:\Java>java Test
Это говорит приложение из командной строки. Знать принцип работы команды javac очень полезно, так как эта команда лежит в основе любой системы сборки проектов.

Как запустить файл без компиляции

Начиная с Java 11 для одного файла шаг с javac можно пропустить: команда java умеет принимать сам исходник.

java Test.java
Компиляция никуда не делась, просто происходит в памяти, и файл Test.class рядом не появится. Возможность описана в JEP 330, и придумана она ровно для таких случаев, как наш: «It is desirable to be able to run the program directly from source with the java launcher». На учебных примерах и небольших утилитах это экономит одну команду. С Java 22 (JEP 458) так же запускаются и программы из нескольких файлов: достаточно указать тот, где лежит метод main, остальные исходники java найдет и скомпилирует сама. Все остальное в статье от этого не устаревает: как только проект перестает быть учебным, классы все равно приходится раскладывать и собирать по-настоящему.

Компиляция и выполнение нескольких классов

Для работы с несколькими классами нужен classpath. Он похож на файловую систему, в которой содержатся классы, а функцию папок выполняют пакеты (packages). На этом этапе стоит задуматься об отделении файлов исходного кода от скомпилированных файлов. Как правило, исходники находятся в каталоге src, а скомпилированные классы в bin. Например, у нас есть класс Box и класс BoxMachine, в котором содержится метод main. Класс Box:

package src;

public class Box {
   private double size;

   public Box(double size) {
       this.size = size;
   }

   public String toString() {
       return "Box have size " + size;
   }
}
Он находится в пакете src, это необходимо зафиксировать. Класс BoxMachine:

package src;

public class BoxMachine {
   public static void main(String[] args) {
       for(int  i = 0; i < 5; i++) {
           System.out.println(new Box(Math.random()*10));
       }
   }
}
Этот класс также находится в пакете src. В методе main он создает пять объектов класса Box разного размера и выводит в консоль информацию о них. Чтобы скомпилировать эту группу классов, необходимо из главного каталога (в котором лежат папки src и bin) использовать команду javac с аргументами:

javac -d bin ./src/*
-d: флаг, после которого следует указать расположение, куда попадут скомпилированные классы. Это очень удобно, так как перекладывать, например, 1000 классов вручную очень трудоемко. bin: название папки. ./src/*: расположение исходных файлов. * указывает, что необходимо скомпилировать все файлы. Теперь скомпилированные классы появились в папке bin. Для их запуска используется команда java из той же директории, также с аргументами:

java -classpath ./bin src.BoxMachine
-classpath: флаг, после которого следует указать местоположение скомпилированных классов. Java будет искать главный класс и все сопутствующие именно в этой директории. ./bin: название папки, в которой находятся скомпилированные классы. src.BoxMachine: полное имя главного класса вместе с пакетом. Просто BoxMachine здесь не сработает, потому что оба класса объявлены в пакете src, и искать их java будет по полному имени. Разбор именно этой ошибки есть в отдельной статье про нюансы компиляции через cmd. Как и в первом случае, не следует указывать .class, так как это название класса, а не файла. Вывод:

D:\Java>java -classpath ./bin src.BoxMachine
Box have size 4.085985295359718
Box have size 8.63682158248986
Box have size 6.027448124299726
Box have size 7.288317703877914
Box have size 1.106181659384694

Создание JAR-файлов

Чтобы программу было легко переносить и запускать, можно собрать скомпилированные классы в jar-файл, то есть в архив классов. Главное отличие от zip или rar-архивов это наличие файла манифеста. В этом манифесте указывается главный класс, который будет запускаться при выполнении jar-файла, classpath, а также много дополнительной информации. В главном каталоге создадим файл manifest.mf. Его содержимое будет следующим:

main-class: src.BoxMachine
class-path: bin/
main-class указывает класс, который содержит метод main и будет выполнен при запуске. class-path: путь к скомпилированным классам или дополнительным библиотекам. Настало время собрать настоящую программу без IDE с помощью команды jar:

jar -cmf manifest.mf box-machine.jar  -C bin .
-cmf: это сразу три флага. c создает архив, m подставляет манифест, f задает имя выходного файла. Аргументы идут в том же порядке, в каком стоят буквы: сначала манифест, потом имя jar. manifest.mf: путь к манифесту. box-machine.jar: название выходного jar-файла. -C: флаг, после которого указывается путь к скомпилированным классам. В документации он описан так: «changes the specified directory and includes the files specified at the end of the command line». .: то, что нужно положить в архив. Точка здесь означает «все содержимое папки bin», в которую мы перешли предыдущим флагом. Сам jar-файл окажется не тут, а там, где мы его назвали флагом f, то есть в текущем каталоге. Теперь можно запустить. Запуск jar-файлов выполняется также с помощью команды java, но следом нужно указать флаг -jar: он говорит о том, что запускается Jar-файл, а второй аргумент это путь к jar-файлу, включая расширение:

java -jar box-machine.jar
Вывод:

Box have size 5.5495235762547965
Box have size 9.695870044165662
Box have size 2.3408385788129227
Box have size 7.2790741216674135
Box have size 2.3620854470160513

Компиляция в Java без IDE: обзор систем сборок

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

Ant, Maven и Gradle: чем они отличаются

Самые известные системы сборки на Java это Ant, Maven и Gradle. Среди них нет плохой или хорошей: каждая из них создана для решения определенных задач. Рассмотрим каждую подробнее.
Система Файл сборки Подход Чем берет
Ant build.xml Сценарий: вы сами описываете последовательность действий Полный контроль над каждым шагом сборки
Maven pom.xml Описание проекта, а не шагов Управление зависимостями и стандартная структура каталогов
Gradle build.gradle Описание проекта плюс скрипт Граф задач, инкрементальные и многопроектные сборки

Ant

Ant это инструмент сборки, который использует сценарий, описанный с помощью xml-файла. Структура xml-файла:

<?xml version="1.0"?>
<project name="имяПроекта" default="сценарийПоУмолчанию">
    <target name="имяСценария">
  //  Действия сценария
        <echo>Hello, World!</echo>
    </target>
  //  Второй сценарий
  //  И тд
</project>
Создадим в главном каталоге файл build.xml со следующим содержимым:

<?xml version="1.0"?>
<project name="BoxMachine" default="test">
   <target name="test">
       <echo>First build in Ant!</echo>
   </target>
</project>
В этом же каталоге вызовем команду ant:

D:\Java>D:\Temp\ant\bin\ant
Buildfile: D:\Java\build.xml

test:
     [echo] First build in Ant!

BUILD SUCCESSFUL
Total time: 0 seconds
В теге <target> можно указывать различные задания, позволяющие управлять сборкой и файловой системой. У Ant есть более 150 доступных команд, которые указаны в документации. В примере ниже используем только 4:
  • mkdir: создание директорий
  • delete: удаление файлов и директорий
  • javac: компиляция Java-кода
  • java: запуск скомпилированного кода
Так выглядит простой сценарий компиляции, сборки или очистки:

<?xml version="1.0"?>
<project name="BoxMachine" default="compile">
   <target name="compile">
       <mkdir dir="result/classes"/>
       <javac destdir="result/classes" includeantruntime="false">
           <src path="src"/>
       </javac>
   </target>
   <target name="run" depends="compile">
       <java classname="src.BoxMachine" classpath="result/classes"/>
   </target>
   <target name="clean">
       <delete dir="result"/>
   </target>
</project>
В сценарии описано три действия: compile, run и clean. compile создает директорию result, в ней classes, затем с помощью javac компилирует классы в созданную директорию. run запускает скомпилированные классы командой java. clean удаляет директорию result. Если в главном каталоге выполнить команду ant без аргументов, запустится действие compile. Если нужно выполнить определенное действие, его указывают в аргументе.

D:\Java>D:/Temp/ant/bin/ant compile
Buildfile: D:\Java\build.xml

compile:
    [mkdir] Created dir: D:\Java\result\classes
    [javac] Compiling 2 source files to D:\Java\result\classes

BUILD SUCCESSFUL
Total time: 1 second

Maven

Maven предлагает несколько другой подход к сборке проектов. Здесь разработчик скорее описывает свой проект и дополнительные инструменты, которые использует, в отличие от Ant, где сборка это последовательность действий. Maven популярен среди разработчиков благодаря простому управлению зависимостями и удобной интеграции со всеми средами разработки. Подробный разбор самого инструмента есть в статье Основы Maven. При работе с Maven придерживаются такой структуры проекта: Скриншот панели Project в IDE: структура Maven-проекта с папками src/main/java, src/main/resources, src/test/java и файлом pom.xml в корнеПравила сборки, зависимости и прочее описывается в файле pom.xml. Как правило, он находится в главной папке проекта. При запуске Maven проверяет структуру и синтаксис файла, предупреждая об ошибках. В главной директории рядом с папками bin и src создаем файл pom.xml, внутрь добавляем:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>ru.javarush.testmaven</groupId>
  <artifactId>testMavenWithoutIde</artifactId>
  <version>1.0.0</version>

  <build>
     <defaultGoal>compile</defaultGoal>
     <sourceDirectory>src</sourceDirectory>
     <outputDirectory>bin</outputDirectory>
     <finalName>${project.artifactId}-${project.version}</finalName>
  </build>
</project>
Далее в командной строке выполняем команду mvn:

D:\Java>mvn
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< ru.javarush.testmaven:testMavenWithoutIde >--------------
[INFO] Building testMavenWithoutIde 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ testMavenWithoutIde ---
[WARNING] Using platform encoding (Cp1251 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Java\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ testMavenWithoutIde ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1251, i.e. build is platform dependent!
[INFO] Compiling 2 source files to D:\Java\bin
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 15.521 s
[INFO] Finished at: 2019-06-25T20:18:05+03:00
[INFO] ------------------------------------------------------------------------
Теперь в папке bin есть папка src, в которой находятся скомпилированные классы. В pom.xml в теге build определена цель сборки, то есть компиляция, директории файлов исходного кода и результата компиляции, а также имя проекта. У Maven есть множество целей сборки и плагинов для запуска тестирования, создания Jar-файлов, сборки дистрибутивов и других задач.

Gradle

Это самая молодая система сборки, которая основывается на Ant и Maven. Главное отличие это работа на базе ациклического графа для определения порядка выполнения задач. Это очень полезно при более сложных задачах, например, инкрементальных и многопроектных сборках. При сборке с помощью Gradle также рекомендуется придерживаться структуры папок проекта Maven. Отдельное знакомство с Gradle у нас тоже есть. Кстати, файл для сборки в Gradle называется build.gradle и выглядит гораздо меньше, чем у Maven. Пример для наших классов:

plugins {
   id 'java'
   id 'application'
}

sourceSets {
   main {
       java {
           srcDirs 'src'
           destinationDirectory = file('bin')
       }
   }
}

application {
   mainClass = 'src.BoxMachine'
}

defaultTasks 'compileJava', 'run'
В файле происходит подключение плагинов, определение директории файлов исходного кода (если не используется структура проектов Maven), директория результатов сборки, имя главного класса, а также задачи по умолчанию. Если вам попадется старый пример, где вместо этого написано sourceSets.main.output.classesDir и mainClassName, копировать его не стоит. Первое свойство удалено в Gradle 5.0, второе в Gradle 8.0, и сборка на них просто не запустится. Папку для скомпилированных классов теперь задают через destinationDirectory, а главный класс через блок application. За запуск сборки отвечает команда gradle в директории, где лежит файл build.gradle:

d:\Java>D:\Temp\gradle\bin\gradle

Welcome to Gradle 5.4.1!

Here are the highlights of this release:
 - Run builds with JDK12
 - New API for Incremental Tasks
 - Updates to native projects, including Swift 5 support

For more details see https://docs.gradle.org/5.4.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :help

Welcome to Gradle 5.4.1.

To run a build, run gradle <task> ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

To see more detail about a task, run gradle help --task <task>

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 52s
1 actionable task: 1 executed
В выводе видно строку > Task :help: так Gradle отвечает, когда не получил ни задачи в аргументах, ни defaultTasks в файле сборки. С нашим build.gradle рядом та же команда выполнит уже compileJava и run.

Заключение

На первый взгляд, умение компиляции и сборки кода без IDE кажется бесполезным. Действительно, зачем мучиться с командными строками и гуглить все команды, когда есть уютная IDEшечка с плагинами, автопроверкой всего, что можно (современные IDE разве что уровень IQ не проверяют) и интеграцией с популярными системами. Однако практика показывает, что умение собирать код без среды разработки и понимание каждого шага этого процесса это суровая необходимость. Этот навык сэкономит немало нервных клеток и времени вам и вашей компании. Освоить работу в IDE, попрактиковаться в написании кода и, конечно же, получить фундаментальные основы по программированию на Java вы можете здесь, на JavaRush. Пора вернуться к обучению :)

Вопросы и ответы

Как скомпилировать Java-файл из командной строки?

Перейдите в папку с файлом и выполните javac ИмяКласса.java. Если в коде нет ошибок, рядом появится файл ИмяКласса.class с байт-кодом. Запускается он командой java ИмяКласса, уже без расширения.

Чем отличаются команды javac и java?

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

Что такое classpath и зачем он нужен?

Classpath это список мест, где java и javac ищут классы. Он похож на файловую систему, только роль папок в нем играют пакеты. Когда скомпилированные классы лежат не рядом, а в отдельной папке, путь к ней передают флагом -classpath, иначе запуск закончится ошибкой о ненайденном классе.

Почему при запуске пишут java Test, а не java Test.class?

Потому что аргумент команды java это имя класса, а не имя файла. Расширение тут лишнее. По той же причине класс из пакета нужно называть полностью, вместе с пакетом: не BoxMachine, а src.BoxMachine.

Как собрать программу в jar-файл и запустить его?

Сначала создается файл манифеста, в котором указан главный класс. Потом выполняется команда jar с флагами c, m и f: она создает архив, кладет в него манифест и скомпилированные классы. Запускается готовый архив командой java -jar имя.jar, и тут расширение как раз пишут, потому что это уже путь к файлу.

Зачем нужны Ant, Maven и Gradle, если есть javac?

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

Читайте также