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

от автора

Вступление

Я Николай Парлог, представитель отдела разработки 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. И, между прочим, дело во мне! Правильное построение проекта сопряжено со сложностями. И поэтому очень здорово, что это дополнение помогает отложить эту сложность до тех пор, пока вы действительно не захотите создать проект, а не просто поэкспериментировать с кодом.


ссылка на оригинал статьи https://habr.com/ru/articles/798761/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *