Spúšťanie programov z viacerých zdrojových súborov popisuje dokument JEP 458: Launch Multi-File Source-Code Programs. Hlavnými dôvodmi tejto zmeny sú uľahčenie spúšťania jednoduchých programov a zjednodušenie výučby Javy pre začínajúcich programátorov.
Doteraz bolo spúšťanie obmedzené len na jediný súbor. Túto možnosť priniesla pred rokmi Java 11.
Prakticky každý moderný programovací jazyk umožňuje jednoduchým spôsobom spustiť program priamo zo zdrojových súborov alebo triviálnym spôsobom zhotoviť výslednú binárku. (Niektoré programovacie jazyky dokážu oboje.) Java bola v tomto ohľade výnimkou. Spúšanie veľmi jednoduchých programov v Jave je pracné a komplikované. Preto sa väčšinou aj na malé programy používajú komplexné vývojové prostredia a Maven alebo Gradle manažéry.
Klasický postup z príkazovej riadky
Najprv si ukážeme, ako sa spúšťa jednoduchá aplikácia klasickým spôsobom z príkazovej riadky.
├── bin ├── lib │ └── jsoup-1.17.2.jar └── src └── main └── java └── com └── example ├── Main.java └── utils ├── Scraper.java └── Utils.java
Majme takúto adresárovú štruktúru projektu. Do adresára bin
sa skompiluje výsledné binárne súbory. V adresári lib
máme JAR súbor pre JSoup knižnicu. Súbor si môžme stiahnuť z príslušného Maven repozitára. V adresári src
máme zdrojové súbory Java programu.
// Main.java package com.example; import com.example.utils.Scraper; import com.example.utils.Utils; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { String word = new Utils().getRandomWord(); System.out.println(word); String url = "https://example.com"; if (args.length > 0) { url = args[0]; } var scraper = new Scraper(url); String title = scraper.getTitle(); System.err.println(title); } }
V súbore Main.java
máme metódu main
, ktorá je vstupným bodom do aplikácie. Tento jednoduchý program vypíše náhodné slovo a načíta titulok z webovej stránky.
// Utils.java package com.example.utils; import java.util.List; import java.util.Random; final public class Utils { List<String> words = List.of("sky", "town", "blue", "forest", "snake", "book", "pen", "cloud", "cup"); public String getRandomWord() { int idx = new Random().nextInt(words.size()); return words.get(idx); } }
V súbore Utils.java
máme implementáciu metódy, ktorá nám vráti náhodné slovo zo zoznamu.
// Scraper.java package com.example.utils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.IOException; public final class Scraper { private final String url; public Scraper(String url) { this.url = url; } public String getTitle() throws IOException { Document doc = Jsoup.connect(url).get(); return doc.title(); } }
Nakoniec v Scraper.java
máme metódu, ktorá získa titulok z uvedenej webovej stránky. Nato využívame populárnu knižnicu JSoup.
Ďalej si ukážeme, ako daný program skompilujeme a spustíme. Taktiež si vytvoríme spustiteľný JAR súbor.
$ javac -d bin -cp lib/jsoup-1.17.2.jar src/main/java/com/example/Main.java src/main/java/com/example/utils/*.java
Pomocou nástroja javac
skompilujeme súbory do Java byte kódu. Pomocou prepínača -d
uvedemie kam sa majú skompilované súbory umiestniť. Prepínačom -cp
informujeme kompilátor, kde sa nachádza naša externá knižnica. Na konci uvádzame cestu ku zdrojovým súborom.
$ java -cp bin:lib/jsoup-1.17.2.jar com.example.Main https://webcode.me blue My html page
Pomocou nástroja java
spustíme nakoniec náš program. Najprv špecifikujeme adresáre, v ktorých sa nachádza spustiteľný byte kód. Potom nasleduje plný názov programu, bez uvedenia .class
prípony. Na konci máme voliteľný argument nášho programu. (Na operačnom systéme Windows treba namiesto dvojbodky použiť bodkočiarku.)
V Jave je štandardom zlúčiť binárne súbory a prislúchajúce zdroje do JAR súborov.
Manifest-Version: 1.0 Main-Class: com.example.Main Class-Path: ../lib/jsoup-1.17.2.jar
Na vytvorenie spustiteľného JAR súboru si vytvoríme manifest.txt
, ktorý umiestnime to bin
adresára. V ňom si uvedemie cestu k nášmu programu a k použitej externej knižnici. Posledný riadok v súbore musí byť ukončený novým riadkom, ináč sa súbor nespracuje správne. (Táto „feature“ vás môže stáť hodiny vášho času.)
$ jar -cvfm main.jar manifest.txt com ../lib
V podadresári bin
si vytvoríme pomocou nástroja jar
náš spustiteľný program.
$ java -jar main.jar book Example Domain
JAR súbory spúšťame pomocou -jar
prepínača nástroja java
.
Z uvedeného je jasné, aké je to zdĺhavé a prácne. Celý tento proces je náchylný ku chybám. Existujú ďalej určité rozdiely medzi postupom na Linuxe a na Windows, ktoré ďalej komplikujú tento proces.
Gradle manažér
Komunita vývojárov vytvorila dva veľmi rozšírené projektové manažéry: Gradle a Maven. Ukážeme si, ako sa použije Gradle na spustenie aplikácie a na tvorbu spustiteľného JAR súboru.
$ gradle -version ------------------------------------------------------------ Gradle 8.7 ------------------------------------------------------------ Build time: 2024-03-22 15:52:46 UTC Revision: 650af14d7653aa949fce5e886e685efc9cf97c10 Kotlin: 1.9.22 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 21.0.2 (Amazon.com Inc. 21.0.2+13-LTS) OS: Linux 6.5.0-27-generic amd64
Najnovšia verzia Gradle 8.7 zatiaľ nepodporuje Javu 22. Preto si potrebujeme pre potreby tohto príkladu nastaviť nižšiu verziu Javy. Ak používame aplikáciu sdkman, použijeme príkaz sdk use java
, alebo nastavíme premennú JAVA_HOME
. Ináč dostaneme chybovú hlášku Unsupported class file major version 66.
$ gradle init
Projekt inicializujeme pomocou gradle init
príkazu. Gradle používa na konfiguráciu interaktívne menu. Vyberieme si voľby Application/Java/Single application project/Groovy/Spock. (Posledné dve nie sú pre náš príklad podstatné.)
Z predchádzajúceho príkladu použijeme všetky tri súbory. Upravíme si adresárovú štruktúru, ktorú nám vytvoril Gradle. (Zmeníme org.example
na com.example
.) Tiež zakomentujeme testovacie súbory.
Ďalej si upravíme build.gradle
súbor. Tento súbor je centrálnym konfiguračným súborom pre manažment projektov. Na konfiguráciu sa používajú jazyky Groovy alebo najnovšie tiež Kotlin.
dependencies { ... implementation 'org.jsoup:jsoup:1.17.2' }
Pridáme závislosť pre JSoup knižnicu.
application { mainClass = 'com.example.Main' }
Zadefinujeme si cestu k vstupnej triede aplikácie.
$ gradle -q run cup Example Domain
Takto si teraz môžeme spustiť aplikáciu pomocou Gradle.
jar { manifest { attributes( 'Main-Class': 'com.example.Main' ) } from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } }
Týmito konfiguračnými nastaveniami si vytvoríme spustiteľný JAR súbor.
$ gradle build $ java -jar app/build/libs/app.jar https://webcode.me sky My html page
Zostavíme si aplikáciu pomocou gradle build
. Spustiteľný JAR súbor sa nám skopíruje do app/build/libs
podadresára. Ten nakoniec spustíme pomocou java -jar
.
Gradle nám výrazne zjednodušil našu prácu. Použitie Gradle pre malé programy je možno prirovnať ku kanónom na vrabce.
Najnovší postup
Pre tento nový postup sa potrebujeme prepnúť na Java verziu 22. Vytvoríme jednoduchšiu adresárovú štruktúru:
├── lib │ └── jsoup-1.17.2.jar ├── Main.java └── utils ├── Scraper.java └── Utils.java
Main.java
súbor si upravíme, ostatné dva súbory sa nemenia.
import utils.Scraper; import utils.Utils; import java.io.IOException; void main(String[] args) throws IOException { String word = new Utils().getRandomWord(); System.out.println(word); String url = "https://example.com"; if (args.length > 0) { url = args[0]; } var scraper = new Scraper(url); String title = scraper.getTitle(); System.err.println(title); }
V príklade tiež využijeme novinku z Javy 21 Unnamed Classes and Instance Main Methods, ktorá nám umožňuje zjednodušiť definíciu metódy main
.
$ java --enable-preview --source 22 -cp lib/jsoup-1.17.2.jar Main.java cup Example Domain
Program nemusíme zvlášť kompilovať, rovno ho spúšťame nástrojom java
. Kvôli zjednodušenej main
metóde potrebujeme použiť --enable-preview
prepínač. Verziu Javy uvedemie prepínačom --source
. Na konci riadku je Main.java
súbor, ktorý je vstupným bodom do nášho programu. Java daný súbor priamo skompiluje a zároveň spustí. Takto po novom môžeme jednoducho spúšťať programy, ktoré sa skladajú z viacerých Java zdrojových súborov. Je možné použiť aj externé knižnice.
Automatický manažment závislostí (ako to má napr. Groovy) zatiaľ nie je zabudovaný. JEP 458 zmieňuje možnosť budúceho JEP dokumentu, ktorý by to mohol v budúcnosti priniesť.
Príklad v jazyku Go
Pre porovnanie si uvedieme obdobný príklad v jazyku Go. Kód sa nachádza v dvoch súboroch main.go
a utils.go
.
$ go mod init com/example/first
Inicializácia projektu prebehne pomocou príkazu go mod init
.
// main.go package main import ( "flag" "fmt" ) func main() { url := flag.String("u", "https://something.com", "website URL") flag.Parse() word := GetRandomWord() fmt.Println(word) title := GetTitle(*url) fmt.Println(title) }
Go má module flag
na jednoduché parsovanie argumentov.
// utils.go package main import ( "math/rand" "github.com/gocolly/colly/v2" ) func GetRandomWord() string { var words []string = []string{"sky", "town", "blue", "forest", "snake", "book", "pen", "cloud", "cup"} idx := rand.Intn(len(words)) return words[idx] } func GetTitle(url string) string { var title string c := colly.NewCollector() c.OnHTML("title", func(e *colly.HTMLElement) { title = e.Text }) c.Visit(url) return title }
Na parsovanie titulku stránky použijeme knižnicku gocolly
. Na stiahnutie závislostí môžeme použiť príkazy go mod tidy
alebo go get
.
$ go mod tidy go: finding module for package github.com/gocolly/colly/v2 go: found github.com/gocolly/colly/v2 in github.com/gocolly/colly/v2 v2.1.0 $ go run main.go utils.go pen Something.
Pomocou príkazu go run
môžme náš kód priamo spustiť, ako keby sa jednalo o klasický skript.
$ go build $ ./first -u https://root.cz sky Root.cz - informace nejen ze světa Linuxu
Kompilácia programu do binárky je triviálna, stačí použiť príkaz go build
.
Podobne to má aj .NET (dotnet run/build) alebo Rust (cargo run/build).
Uľahčenie práce
V poslednej dobe priniesla Java množstvo zaujímavých noviniek. Viaceré z nich uľahčujú vývojárom ich prácu. Možnosť spúšťania programov z viacerých zdrojových súborov ocenia nielen lektori, ale aj tí, ktorí radi experimentujú a vytváranú pri tom množstvo menších programov.
Osobne si myslím, že v tomto ohľade ide vývoj jazyka správnym smerom. Ak by sa v budúcnosti doplnil automatický manažment závislostí, mohla by sa Java využiť aj na skriptovania pomerne väčších programov.