本投稿は、Schooの2016年末特別企画「Schoo advent calendar 2016」の記事になります。
私はSchooでマスタープラン事業を中心にアシスタント業務をしております。
今回はJavaのHelloWorldを言語仕様を参照しながら解説します。なぜ「public static void main(String[] args){...}」なのかみたいな話です。
class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
なぜpublic static void main(String[] args){...}なのかは仕様だから
メソッドmainは、public、 static、voidとして宣言されていなければならない。また、文字列の配列である引数を1つ受け取らなければならない。
引用元: Java言語仕様 第3版 12.1.4 Test.mainの起動 P.277
というようなことが書かれているので、従うより仕方がない。どんなプログラム言語でもエントリポイントと呼ばれる開始点がないと始まれない。言語によって様々ではあるがエントリポイントの仕様が決まっていてJavaの場合はこうなっている。
これで終わってもよいのですが、こうなっている理由を考えるといろいろJavaの世界がみえてくるので少しお付き合いください。
クラス
まずはクラス宣言部分の話。
class Test {
…
}
「class」キーワードがあって「Test」というクラスですよということが宣言されている。
Javaはオブジェクト指向がベースだから基本的な構成単位がクラスであることが想像できる。これはmain()メソッドも例外として扱われないので、main()メソッドを実行する場合は必ず何らかのクラスに属すことになる。
クラス名については規約に反していなければ何でもいい。結局javaコマンド打つときに指定をすることになるのでmain()メソッド実行におけるクラス名に関する制限はない。むしろクラス名は何でもよいのだから、あらゆるクラスにmain()メソッドを書くことが許されている。たまに勘違いしてプロジェクトに1つでなければいけないみたなことを考えている人がいるが、どのクラスでも書いていい。例えば、テストコードを一時的に書くときなど。(JUnitの方がよいけれど)
また「public」がないと思われる人もいるかもしれないが、マストではない。
ここでクラス宣言をもう少し掘り下げてみる。
クラス宣言(ClassDeclaraion)とは、新たな参照型を宣言するものである。クラス宣言には、通常のクラス宣言(normal class declaration)と列挙宣言(enum declaration)の2種類がある。
引用元: Java言語仕様 第3版 8.1 クラス宣言 P.194
「新たな参照型を宣言するものである」という部分が最も重要で、クラスとは参照型であることを理解していないとポリモーフィズムあたりが多分理解できない。また「class」or「enum」であるということなので、enumも実際は特殊なクラスということ。enumはクラスとは別な概念にみえても、クラスの特徴をいろいろと兼ね備えているということはおさえなくてはいけない。
メソッド
続いてメソッド宣言の部分。
public static void main(String[] args) {
…
}
メソッド自体の仕様はこんな感じ。
メソッド(method)によって、一定個数の引数を値として引き渡して起動を行うことのできる、実行可能コードが宣言される。
引用元: Java言語仕様 第3版 8.4 メソッド宣言 P.186
アクセス修飾子
アクセス修飾子は「public」でなければならない。
「public」にしなればならない理由は、何となく想像できる。JVMが引数で指定されたクラスのmain()メソッドを実行するので、明らかに外側から起動されるということを考えると「public」がしっくりくる。「public」指定はまあ必要かと思える。
メソッドのアクセス修飾子は「public」「(無指定)」「protected」「private」があり、これらを選択することによって振る舞いの情報隠蔽をコントロールすることをおさえておきたい。
static
「static」でなければならない。これは誰がmain()メソッドをインスタンス化するのかみたいなこと考えると、main()メソッドはstaticメソッドでなければならないことは必然ということがわかる。
staticとして宣言されたメソッドはクラス・メソッド(class method)と呼ばれる。クラス・メソッドは、常に特定のオブジェクトへの参照なしで起動される。
引用元: Java言語仕様 第3版 8.4.3.2 staticメソッド P.193
Javaでインスタンスを作成する方法としては「newする」or「リフレクションにより作成する」が考えられる。ただしこれらは元々インスタンス化されたオブジェクトによって行われることが前提。そうなるとまだどのインスタンスも存在していない初期状態の場合はどうなるのかということ。
そこでJavaではstaticが存在している。仕様に書かれているように「常に特定のオブジェクトへの参照なしで起動される」ことができるのがstaticなのである。なので誰もnewしてくれるオブジェクトが存在しない状態で何らかの起動が行われるには、そのまま起動できるstaticメソッドであることが必要なのである。
このことが理解できていればJavaのメモリモデルをほぼ把握できていると思われる。Javaのクラスは基本的にはオブジェクトの設計図でありnewしたタイミングでインスタンス化されメモリに展開される(リフレクションもありますが)。ただこれとは別にstaticも存在するということ。
このstaticの概念がプログラム初心者に何となく説明しにくい。一生懸命「new」する概念を伝えるのに「staticみたなものもあります」といってしまうと...って感じ。また関数系言語経験者が全てをstaticにするという悪用に走る。
戻り型
戻り型は「void」でなければならない。Javaにおけるmain()メソッドは何かを返すことをしない。
メソッドの戻り型には、該当するメソッドが値を返す場合にはその値の方を宣言し、そうでない場合はvoidを記述する
引用元: Java言語仕様 第3版 8.4.5 メソッドの戻り型 P.196
何かを返すことをしないようなので、そうなるとvoidってことになる。どうしてもリターンコードを返したい場合はSystem.exit()みたいな方法がある。
メソッド名
メソッド名は「main」でなければならない。決まり事なのでさすがにこれは従うしかない。
これをたまに「mein」みたいに打ち間違えるとコンパイルエラーにならずに起動できないだけになってしまう。慣れている人はいいが初心者が特にメモ帳だけでやらされると打ちミスをしてハマりやすい。
通常のメソッド名は規約に反していなければ何でもよい。
引数
「また、文字列の配列である引数を1つ受け取らなければならない。」とあるので
「String[] args」が必須。これがなければいけないし、これ以外の引数も受け取れない。ちなみに「String... args」と記述するのは可。
これは起動時に何らの情報をもらうために必要。アプリケーションのモードを指定する場合などを想定するとうれしい存在。
さいごに
こんなことを踏まえた上で初心者に解説する。Java SE 8版の言語仕様の日本語版が出版されて欲しい。