派遣で働くエンジニアのスキルアップを応援するサイト

PRODUCED BY RECRUIT

知っておきたいJavaの話:どうやって実行する?

人気Java著者が解説する、知っておきたいJavaの話。今回は、「Javaのプログラムをどうやって実行するか」。入門書などに書かれている“プログラムを実行するには、コンパイルが必要”の言葉。けれども、今では必ずしも当てはまらないのだとか。最近のJava事情を追えていないという方に気になるトピックをお届けします。

▼今回解説する実行方法

【筆者】きしだ なおき さん
九州芸術工科大学 芸術工学部 音響設計学科を満期退学後、フリーランスでの活動を経て、2015 年から LINE Fukuoka株式会社勤務。著書に、『プロになるJava 』(共著、技術評論社)、『みんなのJava OpenJDKから始まる大変革期! 』(共著、技術評論社)、『創るJava[改訂第3版]』(マイナビ)など。

まずは、基本になる実行形態

今回は次のようなSwingのコードを実行します。コマンドラインでテキストを表示するだけだと面白くないですからね。

java
import javax.swing.*;

public class Hello {
  public static void main(String[] args) {
    var f = new JFrame("Hello");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(400, 300);
    var b = new JButton("World");
    b.addActionListener(ev -> f.dispose());
    f.add(b);
    f.setVisible(true);
  }
}

ほとんど全てのJavaの教科書や入門サイトには「Javaのプログラムを実行するにはコンパイルが必要」ということが書かれていますね。そしてこのとき、プログラムのソースコードはpublicなクラス名に拡張子javaをつけて保存することになっています。

今回のプログラムでのpublicなクラスはHelloなのでHello.javaというファイル名で保存する必要があります。

▼保存したソースコードをコンパイルするにはjavacコマンドを使用する

> javac Hello.java

▼コンパイルされてHello.classというファイルができる

> dir
2023/04/12  02:51             1,370 Hello.class
2023/04/12  02:03               360 Hello.java

javaコマンドを使ってプログラムが実行できるように。このとき指定するのは、ファイル名ではなくクラス名なので、拡張子classは不要

> java Hello

▼うまく実行されると次のような画面が表示される

ChatGPTに聞いてみる、という手段。1

SwingはJava標準のGUIツールキットです。馴染みがない人も多いかもしれませんが、ChatGPTは比較的正しくSwingのコードを生成します。「ボタンを押すと今日の日付を表示するSwingアプリを書いて」などとお願いすると、ちゃんと動くコードを生成してくれるので、興味のある人はいろいろ試してみてください。

コンパイル操作不要?ソースファイル1つだけで完結するプログラムの実行

まずは、Javaのプログラムを実行するために、コンパイルをして実行という手順をとりました。

けれども、Java 11からはソースファイル1つだけで完結するプログラムの場合はjavacによるコンパイルを行わなくても直接javaコマンドでJavaプログラムが実行できるようになっています。

▼つまり、次のようなコマンドでプログラムがされる

> java Hello.java

実際にはjavaコマンド内でjavacが呼び出されるような動作になっているので、コンパイルが不要になったわけではないのですが、コンパイル操作は不要になって、プログラムの実行が簡単になっています。

JShellを使って、スクリプトっぽく実行していく

コンパイル操作が不要になったとはいえ、ファイルの形でプログラムを書いておくことに変わりはありません。

ただ、そうすると少し動きを確認したいだけでもファイルを作る必要があって不便です。そこでJava 9からJShellというツールが導入されて、入力したコードを一行ずつ実行して動作を確認できるようになっています。

jshellコマンドで起動することができる

> jshell
|  JShellへようこそ -- バージョン20
|  概要については、次を入力してください: /help intro

jshell> 4 + 3
$1 ==> 7

jshell> java.time.LocalDateTime.now()
$2 ==> 2023-04-14T07:01:39.459500500

JShellには初期スクリプトを読み込む機能がありますが、これを利用してプログラムを実行することができます。

▼次のようなファイルを作成。拡張子は何でも構わないため、今回はHello.jscriptという名前で保存

java
import javax.swing.*
var f = new JFrame("Hello")
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
f.setSize(400,300)
var b = new JButton("World")
b.addActionListener(ev -> f.dispose())
f.add(b)
f.setVisible(true)

▼次のようにして実行することができる

> jshell Hello.jscript

ただし、JFrameを表示するようなプログラムではウィンドウを閉じてもJShellが終わらないので、/exitコマンドでJShellを終わらせる必要があります。実用的に使えるものではありませんが、セミコロンも中カッコも必要なくJavaのプログラムが実行できるのは面白いですね。

内部的にもコンパイルなしで動かしたい!そんなときは、Groovyスクリプト

javaコマンドでソースファイルを直接実行する場合には内部でコンパイルが行われて実行されていました。JShellでも一行実行するごとにバイトコードへのコンパイルが行われています。

ここでさらに、内部的にもコンパイルなしで動かしたい、という人がいるかどうかはわかりませんが、Groovyという環境を使えば可能です。

https://groovy.apache.org/index.html

Groovyとは

GroovyはビルドシステムのGradleでのビルドスクリプトを記述する言語として使われているので、build.gradleを書いたことがあればGroovyスクリプトを書いたことになります。Groovy言語はJava言語をベースにした別の言語という扱いですが、Javaのコードはほとんどそのまま動くようになっています。

▼先ほどのHello.javaとまったく同じコードをworld.groovyとして保存

> copy Hello.java world.groovy

▼そうすると、次のようにgroovyコマンドで実行することができる

> groovy world.groovy

このことからわかるように、コンパイルが必要かどうかというのはツールの問題であって、言語の問題ではありません。もちろんコンパイルしやすい言語とコンパイルしにくい言語はありますが、コンパイルしやすい言語であればコンパイルせずに動かすことも難しくありません。

UNIX系OSでスクリプトをコマンドとして動かすShebang

ところでこのgroovyファイルは、そのままjavaコマンドでも実行できます。

ただし、拡張子が.javaではないソースファイルを実行するときには--sourceを指定してソースコード実行を強制する必要があります。

> java --source 20 world.groovy

ソースコードを直接実行するときには、publicなクラス名と同じ名前で拡張子を.javaにするというファイル名の規則に従わなくても問題ありません。そして、javaコマンドでソースコードを実行する場合、先頭行が#!で始まる場合にこの行を無視します。

そうすると、LinuxやmacOSなどUNIX系のOSでスクリプトをコマンドとして動かすShebangという仕組みが使えるようになります。

▼たとえば、次のようなファイルをgreetingというファイル名で保存

#! /usr/bin/java --source 20
public class Greeting {
  public static void main(String[] args) {
    System.out.println("Hello");
  }
}

▼ここで/usr/bin/javaは実際のjavaコマンドのパスである必要があるため、which javaコマンドで確認したパスを指定する必要がある次のようにchmodコマンドで実行権限を付与する

> chmod +x greeting

▼そうすることで、greetingというコマンドとしてJavaのソースファイルが実行可能に

> ./greeting
Hello

アイコンをダブルクリックするだけ!JARファイルでの実行

UNIX系ではShebangでコマンドのように利用できると書きましたが、ウィンドウシステムを使っている場合はアイコンのダブルクリックでプログラムを実行したいと思います。

拡張子.javaのファイルをjavaコマンドに関連づけることで単一ソースファイルのプログラムは実行できますが、.javaのファイルはIDEやエディタに関連づけるほうが適切です。ダブルクリックでのJavaプログラムの実行は、JARファイルを作成することで可能です。

▼jarコマンドで実行可能JARファイルを作成

> jar cfe hello.jar Hello -C . .

cfeの「c」はアーカイブの作成、「f」はJARファイルの指定、「e」はメインクラスの指定です。ファイルやクラスは指定したのと同じ順番で書く必要があるので、cefと指定した場合には「cef Hello hello.jar」のようになります。

▼次のようなJARファイルができる。このアイコンをダブルクリックすればプログラムが起動する

ChatGPTに聞いてみる、という手段。2

もしもJARファイルが関連づけられていない場合は、ちょっと面倒な手順が必要になります。こういう場合はChatGPTに聞くと「JARファイルを関連づけるためのレジストリの設定を教えて」というとレジストリエディタの使い方を教えてくれるはずです。

jarコマンドも使い方を把握するのが面倒ですが、これもChatGPTが教えてくれるはずです。

こういう記事で本質的ではないけど簡単な解説記事が見つからないという場合、簡単な説明が必要で、それでも結構な量になったりしていたのですが、今はChatGPTに任せることができて便利ですね。

GraalVMで叶えるネイティブ化

ここまででJavaプログラムの色々な動かし方を紹介しましたが、どれもJavaやGroovyなど特別な環境が必要になっています。

環境のインストールなく単一のファイルで動かせるようにならないものか、という要望は、GraalVMというOracleが中心に開発しているJava VMの機能として可能になっています。

https://www.graalvm.org/

GraalVMとは

GraalVMはGraalというJIT(Just-In-Time)コンパイラを中心としたJava技術です。

JavaはJavaプログラムからJavaバイトコードにコンパイルされて動きますが、Java VMの内部では実行時にJavaバイトコードをネイティブコードにコンパイルしながら動いています。このようなコンパイル方式をJITと呼びますが、Cのようにプログラムを動かす前に事前にコンパイルすることをAOT(Ahead-Of-Time)コンパイルといいます。

GraalVMではJITとしてGraalを使ったJava VMとしての動作もできますが、これをAOTコンパイラとして事前コンパイルできるようにしたのがGraalVMのNative Imageです。

残念ながらSwingのプログラムはLinux以外ではネイティブ化できないので、コマンドラインにメッセージを表示するプログラムを実行するとします。

▼次のようなコードをWakeupTime.javaとして保存

java
import java.lang.management.ManagementFactory;
public class WakeupTime {
  public static void main(String[] args) {
    System.out.println("Wakeup time is " +
        ManagementFactory.getRuntimeMXBean().getUptime());
  }
}

ManagementFactoryというめったに使わないクラスがありますが、ここではコマンド起動時からの時間を得るために使っています。

GraalVMの環境を構築すると、native-imageコマンドでJavaクラスファイルをネイティブコンパイルすることができます。ここでソースファイルからではなくクラスファイルからのコンパイルなので、ライブラリのJARファイルやKotlinなど別言語で書かれたコードもネイティブ化することができます。

▼javacコマンドでコンパイルしてnative-imageコマンドでネイティブ化

> javac WakeupTime.java

> native-image WakeupTime
========================================================================================================================
GraalVM Native Image: Generating 'wakeuptime' (executable)...
========================================================================================================================
 *** 中略 ***

------------------------------------------------------------------------------------------------------------------------
                        0.4s (1.7% of total time) in 20 GCs | Peak RSS: 2.70GB | CPU load: 7.66
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 C:\Users\naoki\wakeuptime.build_artifacts.txt (txt)
 C:\Users\naoki\wakeuptime.exe (executable)
========================================================================================================================
Finished generating 'wakeuptime' in 24.6s.

▼うまくネイティブ化できると、Windowsの場合はExeファイルができる

>dir *.exe
2023/04/12  02:38        12,005,376 wakeuptime.exe

12MBなので、処理内容の割には大きいファイルになっています。これはSubstrateVMという最低限の実行環境が含まれているためです。

▼実行してみる

> wakeuptime
Wakeup time is 0

0ミリ秒という結果が出ました。

▼クラスファイルでの実行やソースファイルでの実行も試してみると…

> java WakeupTime
Wakeup time is 65

> java WakeupTime.java
Wakeup time is 525

ソースファイルでの実行ではコンパイルが行われるのでその分遅くなっていることがわかりますが、コンパイル後のクラスファイルでの実行でも時間がかかっています。これはJava VMが起動して実際にプログラムの処理が行われるまでに時間がかかることを示しています。

Helloと表示するプログラムでも、Enterキーを教えてから一瞬の間をおいて表示が行われますね。一方でネイティブ化したものではEnterキーを押すのと同時にメッセージが表示されるようになります。

Javaはもともと起動が遅いのが欠点でしたが、サーバーサイドの場合はサーバーを一旦立ち上げれば起動の遅さは気にならないという状況が続いていました。

しかし最近はDockerのインスタンスが必要に応じて起動したり、サーバレスでリクエストごとにプログラムが呼び出されたりすることから、改めて起動速度が気になるようになってきています。そのため、GraalVMのNative Imageが重要視されるように。

GraalVMでネイティブ化するためにはVisual Studioをインストールして特別なコマンドプロンプトで実行する必要があります。自分で試したい方は、こちらに最低限の手順がまとまっているので参考にしてください。
「WindowsでGraalVMを動かす。 - 水まんじゅう2」
https://megascus.hatenablog.com/entry/2021/12/08/153020

ChatGPTに聞いてみる、という手段。3

ChatGPTは、無料版のGPT-3.5では「GraalVMのnative-imageをWindowsで動かすための環境設定を教えて」だけではVisual Studioのインストールについて教えてくれず、適切な手順ではありませんでした。有料のGPT-4だと比較的正しい手順を教えてくれましたが、これに頼って環境構築するのは難しいと思います。こういうニッチな分野には弱いですね。

Java21でできるようになる、mainメソッド簡略化

ところで、JShell用のスクリプトを見たときに、普通のプログラムもああいう風にスッキリ書きたいと思った人もいるのではないでしょうか。

9月にリリース予定のJava 21では、Javaのmainを簡単に書けるようにという仕様変更がお試し機能として導入される予定です。

▼この機能を使うと、次のように今回のプログラムが書けるように

java
import javax.swing.*;

void main() {
  var f = new JFrame("Hello");
  f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  f.setSize(400, 300);
  var b = new JButton("World");
  b.addActionListener(ev -> f.dispose());
  f.add(b);
  f.setVisible(true);
}

こちらは、実際にJava 21がリリースされたときに詳しく紹介したいと思います。

まとめ

Javaのコンパイルや実行といっても、いろいろなやりかたがあることがわかりました。また古い入門書で言われていたことは、今では必ずしも当てはまらなくなってきています。プログラムの実行という観点から、Javaがどういうものなのか改めて考えるきっかけになればと思います。

次回はJavaの実行環境について。お楽しみに。

▼これまでの「知っておきたいJavaの話」
Javaとは何か?