Компиляция и запуск Java-программ (ч. 4)

В прошлой публикации мы установили и настроили Sublime Text в качестве редактора для написания кода на Java.
Из этой статьи вы узнаете:
  • о компиляции и запуске Java-программ из консоли
  • как выводить в нее корректно русский текст
  • способ отображения байт-кода
Введение
На первых порах начинающему программисту очень важно научиться выполнять необходимые действия минимальными средствами, чтобы:
  • иметь представление, что происходит «под капотом»
  • не быть зависимым от среды разработки
  • не превратиться в специалиста по нажиманию кнопок в инструментах для профессионалов
  • не оказаться беспомощным за их пределами
Вам сейчас нужно максимально набить шишек, находясь в спартанских условиях, которые закалят и научат самостоятельно и оперативно решать возникающие проблемы. Это придаст уверенности и повысит вашу экспертность.
Есть такое понятие — профессиональный кругозор. Он важен для людей любой профессии. Программисты не исключение. Если человек хочет стать настоящим разработчиком, обладающим фундаментальным знанием, а не поверхностным и неструктурированным, то ему нужно изучать возможности языка последовательно и системно. Компиляция и запуск — это основа основ, это база, мимо которой никак нельзя пройти.
Компилировать и запускать программы, на первых порах, мы будем из консоли. В этой и последующих статьях мы будем много работать с этим инструментом. Это важный навык, который используется программистами повсеместно.
1. Компиляция
Откроем в Sublime Text терминал, нажав Ctrl + Alt + T (при этом файл MyFirstApp.java должен быть открыт в редакторе). Обязательно проверьте, что терминал запустился в папке StartJava (или где вы сохранили класс).
Для компиляции воспользуемся уже знакомой утилитой javac (java compiler, компилятор java). Она нужна для преобразования Java-кода в язык виртуальной машины (байт-код).
В терминале пишем команду javac MyFirstApp.java
Отсутствие каких-либо сообщений свидетельствует о том, что компилятор не нашел ошибок в нашем коде. Результатом его работы стал файл MyFirstApp.class.
На текущем этапе директория содержит два файла.
2. Запуск
Как мы уже знаем, MyFirstApp.class содержит байт-код. Для его исполнения воспользуемся утилитой java. Именно она стартует JVM, которая в свою очередь запустит класс MyFirstApp.
Пишем java MyFirstApp:
Программа отработала верно, выведя в консоль WORA.
Обратите внимание, что необходимо указать не имя файла MyFirstApp.class, а имя класса. При этом писать какое-либо расширение не нужно. Утилита java принимает в качестве параметра именно имя класса, а не имя файла, где он находится.
3. Вывод байт-кода
Мы уже не первый раз упоминаем про байт-код, но до сих пор в глаза его не видели. Исправим эту ситуацию.
Для отображения (декомпиляции) байт-кода класса необходимо в консоли написать команду javap -c MyFirstApp.
Как правило, умение читать и понимать байт-код, вносить в него изменения для рядовых Java-разработчиков обычно не требуется — это очень специфические знания. Так что долго не сидите на этой теме.
Если обобщить все этапы, которые проходит программа перед запуском, то схематично их можно отобразить в виде схемы:
4. Запуск однофайловых программ
Чтобы посмотреть, как работает программа, необходимо выполнить два действия: компиляцию и запуск. Если эти шаги требуется исполнять часто, то данный процесс рано или поздно надоест. Хотелось бы его упростить.
Начиная с Java 11, однофайловые программы можно запускать не используя явно этап компиляции.
Удалите из папки class-файл, чтобы все было честно. И запустите программу без компиляции в явном виде, написав java MyFirstApp.java.
Мы только что запустили (и скомпилировали) файл с java-кодом без использования команды javac. При этом исходный код в любом случае компилируется в памяти (без создания на диске class-файла), а затем исполняется JVM.
Эта функция ограничена кодом, который хранится в одном файле. Она не сработает для программ, состоящих из двух и более файлов.
5. Эксперименты
Теперь, когда вы умеете компилировать и запускать программу, можете поэкспериментировать с кодом MyFirstApp.java, внося в него изменения. Попробуем удалить точку с запятой.
Чтобы не писать заново ранее введенные команды, можно нажимать на клавиатуре стрелочку вверх ↑.
Компилятор сообщает о том, что в строке 3 пропущена точка с запятой.
Удалим слово static и снова запустим.
> java MyFirstApp.java
error: 'main' method is not declared 'public static'
Возникла новая ошибка, сообщающая, что метод main должен быть объявлен, как public static.
Самостоятельно попробуйте удалять разные части программы и смотреть, какие ошибки выдает компилятор.
6. Вывод кириллицы на консоль
Если вы используете Linux или macOS, то дальше можете не читать, т.к. ниже описаны проблемы и их решения для Windows. У вас таких проблем не будет!
Написанная нами программа выводит текст в консоли на английском языке. Как вы думаете, что произойдет, если попробовать вывести текст на русском?
Внесем исправления в код, написав: «Написано однажды, работает везде», и запустим…
> java MyFirstApp.java
Р?апиС?Р°Р?Р? Р?Р?Р?Р°Р?Р?С?, С?Р°Р?Р?С?Р°Р?С? Р?Р?Р·Р?Р?
Что-то пошло не так и , вместо русского текста, отобразились какие-то закорючки. В чем тут причина и как эту ситуацию можно исправить?
Первое, что приходит в голову — это какая-то проблема с кодировкой. Нужно разобраться в какой момент она возникает.
Посмотрим, какая кодировка у файла с нашим кодом. Она отображается в правом нижнем углу окна. Видим, что это UTF-8.
А какая кодировка используется в OC? Определить это можно разными путями. Например, в Windows в реестре (для его запуска из консоли используйте команду regedit) по следующему адресу HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage можно посмотреть, чему равен параметр под названием ACP (ANSI code page).
У меня он имеет значение 1251. Это стандартная кодировка для русских версий Windows. У вас она может быть другой — все зависит от языка (кодировки) системы.
Есть еще один способ узнать кодировку, который подходит для любой ОС — это воспользоваться Java, поместив в наш класс две новых строки:
import java.nio.charset.Charset;

public class MyFirstApp {
    public static void main(String[] args) {
        System.out.println(Charset.defaultCharset().displayName());
    }
}
Вникать в то, что означает этот код мы не будем — наша задача лишь получить универсальным способом значение кодировки. Но, если в двух словах, то в первой строке мы подключаем класс Charset, а в пятой выводим на экран значение кодировки по умолчанию для ОС, воспользовавшись возможностями данного класса.
Запускаем и видим то же значение, что и в реестре.
> java MyFirstApp.java
windows-1251
Осталось узнать кодировку консоли. Для этого в ней напишем команду chcp.
> chcp
Текущая кодовая страница: 866
Она выдала 866. Это же число можно наблюдать и в реестре у параметра OEMCP (Original equipment manufacturer code page). Это DOS-кодировка, которая досталась нам в наследство.
Не удивительно, что java не смогла корректно отобразить кириллический текст, когда используется столько кодировок в одной ОС.
Дело в том, что во время компиляции компилятор определяет кодировку исходного кода, опираясь на значение кодировки по умолчанию (на кодировку вашей ОС), а не на кодировку файла. В разных системах используются разные кодировки по умолчанию: в Windows — windows-1251, в Linux и Mac — UTF-8.
В Windows ко всему прочему еще и кодировка консоли не совпадает с кодировкой системы! Консоль по историческим причинам имеет кодировку Cp866 (видимо, в целях совместимости).
Затем JVM определяет кодировку по умолчанию во время запуска, используя системное свойство file.encoding. Значение для этого свойства устанавливается Java-машиной один раз при старте на основании данных, взятых из ОС.
Кодировка, используемая при выводе в консоль тоже системная, а не консоли. Если они не совпадают, то не видать нам корректного вывода кириллицы.
В итоге кодировку надо учитывать во время компиляции, во время запуска и во время вывода текста на консоль, например, при вводе с клавиатуры. Невыполнение этого правила в любом из этих мест способно вызвать проблемы.
Давайте исправлять ситуацию. Начнем с компиляции.
У компилятора есть опция -encoding. Она позволяет принудительно указать кодировку исходника, чтобы компилятор не определял ее, опираясь на кодировку системы. Как вы помните, файл имеет кодировку UTF-8. Вот ее мы и укажем javac -encoding utf8 MyFirstApp. java.
Если для однофайловых программы вы решили не использовать явно компиляцию, а сразу писать java, то для переопределения системного свойства file.encoding существует специальная команда. Вместе с ней запуск программы будет выглядеть так java -Dfile.encoding=UTF8 MyFirstApp.java.
Консоль выведет корректный русский текст:
> java -Dfile.encoding=UTF8 MyFirstApp.java
Написано однажды, работает везде
-Dfile.encoding — это уже параметр JVM, с помощью которого мы устанавливаем принудительно нужное нам значение, сохраняя его в file.encoding.
От всех этих параметров и правил может закружиться голова. И все эти годы Java-разработчики жили и мучились с кодировками и ошибками, которые они вызывали в программах.
Но, не прошло и 25 лет, как появилась хорошая новость. Начиная с Java 18, значение кодировки по умолчанию определяется не исходя из кодировки ОС, а исходя из того, что она является UTF-8 — отныне это кодировка по умолчанию. Никакие параметры больше не нужны. Убедимся в этом:
> "C:\Program Files\java\SapMachine-18\bin\java.exe" MyFirstApp.java
Написано однажды, работает везде

> "C:\Program Files\java\SapMachine-18\bin\javac.exe" MyFirstApp.java

> "C:\Program Files\java\SapMachine-18\bin\java.exe" MyFirstApp
Написано однажды, работает везде
7. Переменная среды JAVA_TOOL_OPTIONS
Для тех, кто использует Java меньше 18, придется использовать все параметры, указанные ранее — ни куда от них не деться. Но и тут есть выход.
Утомительно все время указывать кодировку. Этот вопрос можно решить, используя JAVA_TOOL_OPTIONS со значением -Dfile.encoding=UTF8. Необходимо установить эту переменную и ее значение в качестве переменной среды (ранее мы уже имели дело с другой переменной — JAVA_HOME).
Благодаря этой переменной, кодировка будет устанавливаться автоматически при каждом запуске JVM. При этом в консоли будет отображаться сообщение Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
Пробуем скомпилировать и запустить с уже установленным JAVA_TOOL_OPTIONS:
> java MyFirstApp.java
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8                                                                                           
Написано однажды, работает везде                                                                                                             

> javac MyFirstApp.java
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8                                                                                           

> java MyFirstApp
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8                                                                                           
Написано однажды, работает везде
8. Ввод с клавиатуры
Забежим немного вперед и рассмотрим ситуацию, когда нам нужно ввести с клавиатуры что-то на русском языке. Да, ввод мы еще не проходили, но мне совсем не хочется размазывать эту информацию по разным статьям. Пусть все, что связано с компиляцией и запуском, с кодировками, их проблемы и решения будут в одном месте.
Добавим в наш код библиотечный класс Scanner, который позволит считывать, введенные с клавиатуры символы , и пару строк с его использованием:
import java.util.Scanner;

public class MyFirstApp {
    public static void main(String[] args) {
        System.out.println("Написано однажды, работает везде");
        Scanner console = new Scanner(System.in);
        System.out.print("Введите свое имя: ");
        System.out.println(console.nextLine());
    }
}
Запустив программу и введя с клавиатуры имя, видим, что консоль отобразит вместо текста ????:
> java MyFirstApp.java
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
Написано однажды, работает везде
Введите свое имя: Максим
????
Это означает, что кодировка консоли и кодировка, которую использует Scanner, не совпадают.
Чтобы решить проблему, замените строку Scanner console = new Scanner(System.in); на Scanner console = new Scanner(System.in, «cp866»); Тут мы явно указываем кодировку cp866 — это кодировка консоли, как вы помните.
Снова запустим программу:
> java MyFirstApp.java
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8                                                                                            
Написано однажды, работает везде                                                                                                             
Введите свое имя: МАКСИМ ШШШШ ИИИИ
МАКСИМ ШШШШ ИИИИ
Как видим, текст отображается корректно.
Если после всего проделанного, что я описал выше, у вас вместо русского текста выводится пустая строка, то необходимо в консоли ввести chcp 866 и заново запустить программу.
9. Подведем итоги
В этой статье мы научились компилировать и запускать java-программы, нашли способ побороть проблему с выводом кириллических символов в консоль, а также лучше стали понимать, как работают все эти механизмы.
Если не использовать в коде программы или во время ввода с клавиатуры русский язык, то никаких проблем с кодировками не возникнет. Но, если вы все же ступили на путь использования кириллических символов, то вам придется проделать все те настройки, которые были описаны во второй половине статьи. Таких проблем в профессиональных средах разработки практически не бывает, т.к. они умеют правильно отображать текст в консоли, делая автоматически, если нужно, его перекодировку.
Для любознательных
Автор: Чимаев Максим
Оцените статью, если она вам понравилась!