Как стать автором
Обновить

Погубит ли Java 22 сборочные инструменты?

Время на прочтение6 мин
Количество просмотров12K
Автор оригинала: Nicolai Parlog

Вступление


Я Николай Парлог, представитель отдела разработки Java в Oracle, и, отвечу на вопрос, вынесенный в заголовок — нет, конечно же, нет! Как вы могли только подумать о таком!
Но она немного снижает их актуальность, и в правильных обстоятельствах это очень хорошо. Позже я объясню, что я имею в виду. Сначала давайте разберемся, как работает эта новая функция. Готовы? Тогда давайте окунемся с головой!

Версия Java 22 обогатилась возможностью выполнения исходного кода сразу из нескольких файлов. В таком случае простой команды java достаточно, чтобы выполнять программы, состоящие из нескольких исходных файлов и даже содержащие зависимости. Для опытных разработчиков это упростит поиск и эксперименты, но для тех, кто только осваивает Java или просто программирует, это настоящая революция: теперь можно писать код Java как из одного, так и из нескольких исходных файлов и даже добавлять зависимости, безотносительно IDE или сборочных инструментов.

image

Взгляните:

project-folder
 └─ Hello.java

$ java Hello.java

Итак, имеем один файл с исходным кодом Java, и я собираюсь выполнить его с помощью команды java.

java Hello.java

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

project-folder
 ├─ Hello.java
 └─ Greetings.java

$ java Hello.java

Вы видите этот файл? Greeting.java? Давайте запустим его. Я выполню ту же команду… Бац! Все еще работает. Даже результат изменился.
Но это еще не все. Видите архив JAR в папке lib? Подождите, давайте я его открою. Видите этот JAR?

project-folder
 ├─ lib
 │   └─ audience.jar
 ├─ Hello.java
 └─ Greetings.java

$ java -cp "lib/*" Hello.java

Это тоже попробуем. Теперь мне нужно немного изменить команду… вот так: Все работает!
Вот и все: Java 22 может запускать несколько исходных файлов и даже их JAR-зависимости, не требуя вызова javac, не говоря уже о jar. Можно хоронить Maven и Gradle?

Запуск нескольких файлов с исходным кодом


Вы уже поняли суть. В Java 11 и выше можно компилировать в памяти и затем запускать один файл исходников с помощью только одного только java launcher, сообщая ему путь к исходному файлу. В 22-й версии появилось следующее изменение: если этот файл ссылается на классы из других файлов, Java будет искать их и, если это удастся, продолжит компиляцию и выполнение.
Давайте посмотрим, как Java находит эти файлы. В примере, который я вам показывал, файл с исходным кодом (тот, который я передал в качестве аргумента программы запуска) не содержит объявления пакета и поэтому находится в безымянном пакете. В этом случае Java считает содержащую папку с ним корнем дерева исходников и будет разрешать все ссылающиеся классы оттуда:
  • Это означает, что другие классы в безымянном пакете также будут находиться непосредственно в этом корневом каталоге.
  • Для классов в именованных пакетах пакет будет сопоставляться с с иерархией папок обычным способом, где каждый раздел имени пакета соответствует папке. Далее эта иерархия будет привязана к корню пакета, и класс можно будет найти в получившемся пути.
project-folder # ③ source tree root
 ├─ org # ⑦ searched in folder hierarchy …
 │   └─ example # … that matches pkg name …
 │       └─ Audience.java # … anchored in root
 ├─ Greeting.java # ⑤ searched in root
 └─ Hello.java # ① initial source file

$ java Hello.java # ① initial source file
// Hello.java
// ② объявление пакета отсутствует ⇝ безымянный пакет
class Hello {

	public static void main(String[] args) {
		System.out.println(
			Greeting.get() // ④ в безымянном пакете
				+ ", "
				+ org.example.Audience.get()");
				// ↑↑↑ ⑥ в именованном пакете
	}

}

Если классы не удастся найти в этих местах, мы получим ошибку. Достаточно просто и логично, как мне кажется.

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

project-folder # ③ корень дерева исходников, основанного на …
 └─ org         # … walking up org/example/ …
     └─ example # … (matches the package name) …
         ├─ Audience.java
         ├─ Greeting.java
         └─ Hello.java # … from initial src

$ java org/example/Hello.java # ① initial src
// Hello.java
// ② package name
package org.example;
class Hello { /* ... */ }

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

project-folder
 └─ org
     └─ example
         ├─ Audience.java
         ├─ Greeting.java
         └─ Hello.java

# Все эти команды работают из

# из каталога home/nipa/code/project-folder:
$ java org/example/Hello.java

# из home/nipa/code:
$ java project-folder/org/example/Hello.java

# из home/nipa/code/project-folder/org/example:
$ java Hello.java

Запуск с зависимостями


Добавление зависимостей — дело несложное. Просто используйте опцию запуска --class-path или ее короткую форму -cp и укажите на папку, содержащую JAR-файлы.

Нет, подождите, на самом деле это не сработает, потому что class path не понимает, что такое папка. Вам придется указать на папку с $folderName/*, и, по крайней мере, в Linux вам нужно заключить этот код в кавычки, чтобы избежать расширения оболочки. Так что если ваши JAR-файлы находятся в папке lib, добавьте -cp «lib/*» к команде java; или \ для Windozers.

project-folder
 ├─ lib
 │   └─ audience.jar
 ├─ Hello.java
 └─ Greetings.java

$ java -cp "lib/*" Hello.java
// Hello.java
class Hello {

	public static void main(String[] args) {
		System.out.println(Greeting.get() + ", " + org.example.Audience.get());
	}

}

// Greeting.java
class Greeting {

	static String get() {
		return "Hello";
	}

}

Некоторые детали о компиляции


При компиляции нужно учесть ещё несколько нюансов:
  1. Java компилирует только те файлы, на которые прямо или косвенно ссылается исходный файл, что означает, что вы можете иметь исходные файлы с ошибками компиляции, лежащие в одной и той же иерархии папок, если вы не ссылаетесь на них, и программа все равно будет работать.
  2. Не гарантируется, в каком порядке компилируются разные файлы, и произойдёт ли это вообще до или после начала выполнения функции main.
  3. Такая «динамическая компиляция» означает, что можно получить ошибки компиляции во время выполнения программы, к чему мы не привыкли.
И еще несколько деталей, но я оставлю их на ваше усмотрение. Один интересный аспект, скрытый в этих деталях, — это философия проектирования. Это не просто «сделать все легко», это также «сделать плавными переходы от одного источника к нескольким и от нескольких к JAR». Эти шаги должны быть естественными и не требовать или требовать минимальной адаптации к новой ситуации.

Но… Почему?


Теперь, когда мы лучше понимаем механику выполнения нескольких исходных файлов, давайте обсудим, зачем она была введена. Эта задача решалась JDK Enhancement Proposal 458. В JDK 22 эта функция находится в окончательной версии, а не в качестве превью.

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

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

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

Я до сих пор помню, как был шокирован и потрясен, когда впервые увидел Eclipse и POM. И, между прочим, дело во мне! Правильное построение проекта сопряжено со сложностями. И поэтому очень здорово, что это дополнение помогает отложить эту сложность до тех пор, пока вы действительно не захотите создать проект, а не просто поэкспериментировать с кодом.
Теги:
Хабы:
Всего голосов 17: ↑14 и ↓3+17
Комментарии14

Публикации

Информация

Сайт
piter.com
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия