そもそもなに?
Javaプログラムのエントリポイントは、public static void main(String[] args)
と相場が決まっています。いっぽうでツイッター界隈ではJava 21の最新機能として必ずと言っていい程「今後はstatic要らないんだよ」これが出てきます。
短期間に二度三度出てきたので、気になって調べてみました。すごく気になったのは
- static の記述自体の廃止
これは目も当てらません。ちょっとjava使うの辞めたい気持ちになります。なぜなら、javaバイトコード仕様自体が変更になるということは過去資産が使えなくなる可能性や、今後のマイグレーション案件に多大な影響を与えるからです。。ほんとまずい。。
調べてみましょう
こういう時は1次情報をきちんと見ないといけませんね。JDKの一次情報といえば
OpenJDKの公式サイト ですね。みてみましょう。FeaturesからJEP 445: Unnamed Classes and Instance Main Methodsを選択します。
ありました。てか、タイトルから「Main Methods」となってますね。どうやら 対象はメインメソッドだけ ということらしいですね。ほッ
では気を取り直して、Goals を確認しましょう。こんな感じのことが書かれています。
-
教育者がプログラミングの概念を徐々に導入できるように、Javaへのスムーズな導入手段を提供します。
-
基本的なプログラムを簡潔に書くことができ、スキルの成長に合わせてコードを優雅に成長させることができるようにします。
-
スクリプトやコマンドラインユーティリティなど、簡単なプログラムを書く際の負担を軽減させる。
-
Javaの初心者向け方言を別に導入しないこと。
-
生徒のプログラムは、あらゆるJavaプログラムをコンパイルし実行するのと同じツールでコンパイルし実行する必要があるため、初心者向けのツールチェインを別に導入しない。
要は 現状、初心者が勉強するには不便すぎるので、書いたらすぐに動く様にしましょう。 ってことですかね。あとは、通常の開発手法を学んでもらいたいから初心者用の特別なやり方はだめだよねというとです。
なのでJDKの機能の一部として提供する必要があるんですね。そんなにきついかな
final class sample1{
public static void main(String[] args){
System.out.println("hoge");
}
}
いろいろありますね。
- 全ての処理はクラスに書かなくてはいけない
- Java VMが直接実行するプログラムは public static void mainにしなければならない
- 引数の型は String[]でなければいけない。
うん。めんどくさいといえばめんどくさい。Javaが長いヒトには違和感ないですが。。
じゃぁ新しい書き方は
void main(String[] args){
System.out.println("hoge");
}
おお。シンプルになる。
実際にやってみよう
この機能を実現するための実現方式をいくつか考えます
- static を廃止して、すべてをクラスメソッドとして扱う
- すべてのクラスメソッドをstaticとして扱い、直接呼出し可能にする
- コンパイル時にvoid main をキーに必要な情報を追加する。
- クラス定義を追加
- void main -> public static void main(String[] args)に変換
さて、javaという言語は後方互換の維持のために、Javaバイトコードにあまり手を入れないことで知られています。そうすると1,2 はちょっと影響が大き過ぎます。根幹の設計をいじる話になるのです。3が最有力候補ですが、どうもvoid mainを置換するとして、うまくできないケースがありそうです。そのあたりをどう超えているのか、実際に使ってみて確認しましょう。
JDK 21 インストール
さて、WindowsでJDK21を利用したいので。。。いつもの様にwinget search JDK で・・・・あれ?ないんですね。執筆時(6/18)まだEA(Early Access)扱いなので、そりゃまぁMSのストアにはないですよね。じゃぁ、どうするか。公式からとってきます。
これをダウンロードして。解凍して。jdk21フォルダを任意(本記事ではc:\dev\workspace\java\jdk)フォルダに配置します。
ソースコードの作成
任意のディレクトリに先ほどのソースコードを作成します。(以下、再掲)
final class sample1{
public static void main(String[] args){
System.out.println("hoge");
}
}
void main(String[] args){
System.out.println("hoge");
}
この記事では c:\dev\workspace\java\21\445に作成します。この作業はIDEではなくテキストエディタで実施することをお勧めします。
コンパイルと実行
ではそれぞれコンパイルと実行してみましょう。コマンドプロンプトを起動して
set PATH=c:\dev\workspace\java\jdk\jdk-21\bin
java --version
以下のようなバージョン情報が出力されます。
引き続き、コンパイルと実行です。
cd c:\dev\workspace\java\21\445
javac sample1.java
java sample1
→hogeと出力されます。
はい。通常版は難なくどうさしました。では、新しい表記は本当に使えるのでしょうか。
cd c:\dev\workspace\java\21\445
javac sample2.java
あれ?エラーです。
sample2.java:4: エラー: unnamed classesはプレビュー機能であり、デフォルトで無効になっています。
}
^
(unnamed classesを有効にするには--enable-previewを使用します)
エラー1個
まぁ、javacさんがおっしゃっている通りなんです。javaってホント便利ね。
気を取り直して
javac --enable-preview -source 21 sample2.java
java --enable-preview sample2
お、期待通り出力されました。ほんとにmainメソッドに「もうclassは必要ないの?」ってことなんでしょうか。正解は、ユーザープログラマが書く必要はなくなったです。
え?どういうこと?
慌てないでください。逆アセンブルすればわかります。
逆アセンブル結果とその考察
さきほどのsample1とsample2の.class(Javaバイトコード)を逆アセンブルしてみましょう。今回は簡易的にjavapを利用します。
cd c:\dev\workspace\java\21\445
javap -s -constants sample1
javap -s -constants sample2
出力結果です。
比べてみましょう。まずは、クラス定義から。
sample1には当然あります。sample2には記載していないので、クラス定義なんてあるはず・・・・あ・・・あった。
はいあるんです。mainメソッドだけ書いてもコンパイルはとおります。でもそれはコンパイラが勝手にmainメソッドを見つけるとファイル名から適当にクラス名を推定して足りないクラス定義とデフォルトコンストラクタを作ってくれているんです。あ、これ、わりとjavaではやりがちな機能追加方法なのでおぼえておいて損はないです。
じゃぁ、あれだな。void mainって書いてるだけど。public static void mainに書き換えてるんでしょ?そう思ってた時期が僕にもありました。
あれ?こっちは、勝手に変わってないぞ。。。でもどうしてるの?このままじゃ起動できないじゃない?
実はこれ、java VMのプログラムローディングプロトコルに変更が加えられていたのでした。JEPを再び参照しましょう。
ありますね。ちょっと英文だとわかりにくいので内容を見てみましょう
- 起動対象のクラスのmainメソッドにはpublic,protected,defaultの修飾子の記載を許容する
- 起動対象のクラスにstatic main(String[] args )なメソッドがなく、static main()があれば、そちらを起動する。(もちろん static main(String[] args)があればそちらが起動される)
- 起動対象のクラスにstaticなmainクラスがなく且つ、引数なしの呼び出し可能な(= non privateな)コンストラクタがあって、呼び出し可能なmainメソッドがある場合、インスタンスを生成する。main(Stirng[] args)とmain()が同居するようなケースでは、引数ありのメソッドが優先される。
記載から許容される記述方と呼び出し優先順は以下の通りです。(表の簡易化のためアクセス修飾子については未記載)
No. | 記載方法 | インスタンス化 | 備考 |
---|---|---|---|
1 | static void main(String[] args) | 無 | 仮引数の名称に制限なし |
2 | static void main() | 無 | |
3 | void main(String[] args) | 有 | 仮引数の名称に制限なし、デフォルトコンストラクタが存在することが利用の条件 |
4 | void main() | 有 | デフォルトコンストラクタが存在することが利用の条件 |
つまり、いままでがちがちにpublic static void main(String[] v)を呼ぶことにしていたけど、いい感じに緩く呼び出すようにしましたよ。サービスよくやることにしました。って意味ですね。
おお。全部コンパイラでやるのではなく、コンパイラ+JavaVMの実装仕様変更の合わせ技1本での実現なんですね。ほーん。これなら、java本来の機能などに影響を与える事は少なそうです。(だから実行時にもエントリポイントの決定等の動作が必要なため--enable-preview を指定しないと動かないんですね。)
まとめ
- JEP445で確かにmainの表現の仕方の幅は広がったし。シンプルに書けるよ
- staticがなくなるわけはないよ
- public static void main(String[] args)も今まで通り利用可能だよ
- ターゲットは初学者とその人たちのためのテキストの改善だよ
- 実現にはjavacの仕様とjvmの仕様を変更して、うまいこと対応しているよ。
- javaの機能修正について欲しい情報は大抵JEPにあるよ。