Javaについて徹底解説!

Javaの実行に欠かせないmainメソッド、その仕組みからしっかり解説

大石 英人

開発エンジニア/Java20年/Java GOLD/リーダー/ボールド歴2年

JavaのmainメソッドはJavaで処理を実行する時の入り口となるもので、いわゆるエントリーポイントです。mainメソッドに書かれた処理からJavaの実行が始まります。

mainメソッドがどういうものかは、Javaの仕様で修飾子や名前、引数がガッチリ決められています。でも、普通のメソッドあることに変わりはなく、単にJavaを使う時の約束事に過ぎないのです。

そして、Javaを学び始める一番最初に、mainメソッドはJavaを動かすための「おまじない」だと説明された方は多いでしょう。でも、もう少し深くmainメソッドを理解して、おまじないの状態から一歩先に進んでみませんか?

この記事では、Javamainメソッドについてより広く深い知識が得られるよう、初心者向けに分かりやすく解説していきます。

※この記事のサンプルは、Java 12の環境で動作確認しています


1.Javaのmainメソッド入門

1-1.mainメソッドはJavaのエントリーポイント

一般的なプログラミング言語では、プログラムの実行がどこから・どのように始まるかは、大きく以下の二種類に分かれます。

  1. プログラミング言語の仕様で決められた関数・プロシージャ・メソッドから始まる
  2. プログラムの先頭行から最後の行まで、順番に実行されていく

Javaは、1.のプログラミング言語です。1.の仲間として、C言語、C#GoKotlinなどがあります。2.はスクリプト系のプログラミング言語が多く、PerlPythonRubyPHP、シェルスクリプトなどがその仲間です。

この仕様で決められた関数・プロシージャ・メソッドをエントリーポイント(entry point)とも呼び、取り得る名前や引数、修飾子などが決められています。

そして、Javaでは「mainメソッド」がエントリーポイントです。mainメソッドはどういう形式のものかが、Javaの仕様できちんと決められています。

1-2.mainメソッドの5つのルール

Javaのmainメソッドは、以下の条件をすべて満たしているメソッドです。

  • アクセス修飾子はpublicである
  • staticメソッドである
  • メソッドの戻り値はvoidである
  • メソッド名は“main”である(すべて小文字)
  • メソッドの引数は、Stringの配列(あるいはStringの可変長引数)”のみである引数の変数名は何でもよい

そして、mainメソッドの条件とは別のことですが、Javaでのメソッドは、必ず何かのクラスなどに属していなければなりません。

ですから、mainメソッドを持つクラスは、普通は以下のような感じになります。もう見慣れている方も多いですよね。ちなみに、mainメソッドの引数として良く使われる変数名の“args”は、引数を意味する英単語argumentsを短く省略したものです。

class Main {
	// Javaのmainメソッドの基本形
	public static void main(String[] args) {
		// ...何かの処理がある
	}
}
class Main {
	// これでもmainメソッドとしてはOK
	// 引数をStringの可変長引数として、変数名も普通のものとは変えている
	public static void main(String... hikisu) {
		// ...何かの処理がある
	}
}

なお、前述した以外のもの、例えばfinalsynchronizedであったり、throwsmainメソッドにあっても、mainメソッドとしての条件には実は影響しません。

class Main {
	// さらにこれでも、mainメソッドとしてはOK
	// publicでstaticで戻り値がvoid、mainという名前で引数がString[]だけなメソッドであればいい
	@SuppressWarnings("unchecked")
	public final synchronized strictfp static void main(String[] args) throws Exception {
		// ...何かの処理がある
	}
}

もし、mainメソッドが別の引数を持つメソッドでオーバーロードされていても、実行時には引数がString[]の方が呼び出されます。

class Main {
	// こちらが呼び出される
	public static void main(String[] args) {
		System.out.println("main method(String[]).");
	}

	// こちらは呼び出されない
	public static void main(Object[] args) {
		System.out.println("main method(Object[]).");
	}
}

1-2-1.mainメソッドの条件はしっかりとチェックされる

先にお伝えしたmainメソッドの条件は、Javaの実行時にきっちりチェックされます。

例えば、以下のようなものはJavaのメソッドとしては文法どおりなのでコンパイルはできますが、mainメソッドとしては認識されませんよ。

class Main {
	// アクセス修飾子がpublicではない
	static void main(String[] args) {
	}
}
class Main {
	// メソッドがstaticではない
	public void main(String[] args) {
	}
}
class Main {
	// メソッドの戻り値がvoidではない
	public static int main(String[] args) {
	}
}
class Main {
	// メソッド名が"main"ではない("main" != "Main")
	public static void Main(String[] args) {
	}
}
class Main {
	// 引数がStringの配列ではない
	public static void main(String args) {
	}
}
class Main {
	// 引数がStringの配列だけではない(intが余分)
	public static void main(String[] args, int intValue) {
	}
}

1-2-2.【参考】mainメソッドは普通のクラスになくてもいい

mainメソッドが属する先が普通のクラス(class)でなければならないとは、実はJavaの仕様のどこにも書いてはありません。mainメソッドに必要なのは、public static void main(String[])なメソッドであることだけです。

ですので、試しに抽象クラス、インターフェイス、Enummainメソッドを持たせてみても、実はちゃんと動かせてしまいます。ですので、mainメソッドの5つの条件は、エントリーポイントとしての単なる約束事に過ぎないのだと、お分かりいただけるでしょうか?

// 抽象クラスでもいいし、
abstract class MainAbstractClass {
	public static void main(String[] args) {
		System.out.println("抽象クラスのmainメソッドです");
	}
}
// インターフェイスでもいいし、
// Java 8以降
interface MainInterface {
	public static void main(String[] args) {
		System.out.println("インターフェイスのmainメソッドです");
	}
}
// Enumでもいい!!
enum MainEnum {
	DUMMY;

	public static void main(String[] args) {
		System.out.println("Enumのmainメソッドです");
	}
}
C:\>java MainAbstractClass
抽象クラスのmainメソッドです

C:\>java MainInterface
インターフェイスのmainメソッドです

C:\>java MainEnum
Enumのmainメソッドです

1-3.Javaはmainメソッドを探して実行する

Javaのプログラムを実行する時は、以下のように実行するクラスを指定します。すると、指定されたクラスにmainメソッドがあるかをJavaが調べて、あればそこから実行を始めます。これは、Eclipseのような統合開発環境で実行する時も、裏でやっていることは同じです。

java Main

そして、指定されたクラスにmainメソッドがなければ、以下のようなエラーが出てしまいます。mainメソッドがない(Main method not found)と言っていますね。

C:\>java Main
Error: Main method not found in class Main, please define the main method as:
   public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

これは、例えばjarファイルの中にあるクラスを実行する時でも同じです。jarファイルの中に仕様どおりのmainメソッドを持つ別のクラスがあったとしても、指定したクラスにきちんとしたmainメソッドがなければ駄目なのです。

1-4.引数のString配列にパラメータが入る

プログラムへは処理に使うパラメータ(プログラムへの引数)を渡したいですよね。mainメソッドのString配列には、Java実行時にコマンドラインで指定されたパラメータの文字列が設定されています。

class Main {
	public static void main(String[] args) {
		System.out.println("パラメータの数は" + args.length + "です");
		System.out.println(java.util.Arrays.toString(args));
	}
}

パラメータとしては空白で区切られた単位でString配列に設定されますが、クォーテーションなどでくくると、空白などもまとめて一つの文字列にできます。ただし、どういう記述をすればいいかは、コマンドプロンプトやUnixでのシェルの文法に依存します。

そして、mainメソッドの引数のString配列は、パラメータが指定されていなくてもnullにはならず、長さ0の配列になります。

なお、数字をパラメータとして渡しても全てStringになりますので、数字として扱いたいならプログラム側で数値として変換しなければなりません。

C:\>java Main
パラメータの数は0です
[]

C:\>java Main aaa bbb 123 "ccc ddd"
パラメータの数は4です
[aaa, bbb, 123, ccc ddd]

1-4-1.特別なパラメータはmainメソッドの引数には設定されない

javaコマンド(Java仮想マシン)向けの特別なパラメータは、mainメソッドのString配列には設定されません。これらは、javaコマンドと実行するクラス名の間で指定するものです。以下の例では、“java”“Main”の間にある「-dsa -Dxxx=test -Xmx100m -XX:-Inline」です。

C:\>java -dsa -Dxxx=test -Xmx100m -XX:-Inline Main
パラメータの数は0です
[]

ただし、実行するクラス名の後ろに指定したならプログラムへのパラメータになりますので、mainメソッドの引数から参照できます。そして、javaコマンドへのパラメータとしては使われません。これは地味に間違えやすいので注意しましょう。

C:\>java Main -dsa -Dxxx=test -Xmx100m -XX:-Inline
パラメータの数は4です
[-dsa, -Dxxx=test, -Xmx100m, -XX:-Inline]

1-5.mainメソッドの終わらせ方

mainメソッドが終わるということは、(原則として)Javaのプログラムとしての実行も終わるということです。終わらせ方はいくつかありますので、順に紹介していきます。

1-5-1.returnでmainメソッドを終わらせる

mainメソッドはプログラム中で一番最初に動くものだとは言え、普通のメソッドです。ですので、mainメソッドの最後までたどり着けばmainメソッドは終わりますし、途中でreturnすれば、途中で終わらせられます。

class Main {
	public static void main(String[] args) {
		System.out.println("for文を始めます");

		for (int i = 0; i < 10; i++) {
			System.out.println("i = " + i);

			if (i == 5) {
				// for文の途中でもmainメソッドからreturnすれば、
				// Javaプログラムの実行を終わらせられる
				System.out.println("mainメソッドからreturnします");
				return;
			}
		}

		System.out.println("for文が終わりました"); // これは実行されないまま、Javaの実行が終わる
	}
}
C:\>java Main
for文を始めます
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
mainメソッドからreturnします

C:\>echo %ERRORLEVEL%
0

ですが、単純にreturnした場合は、プログラムからの戻り値を指定できません。上の例では、プログラムからの戻り値が設定される変数“ERRORLEVEL”の内容が0ですが、これはJavaの中で戻り値を指定せず、プログラム実行中にエラーが起きなかった場合のデフォルトです。

プログラム中でエラーが起きた場合などは、戻り値をエラーの種類を表す何かの数字にしたいことはあります。その場合は、後述するSystem.exitRuntime.exitを使って、数字を指定します。

あと、スレッドというものを使ったプログラミングをしている時は、mainメソッドが終わってもJavaのプログラムが終わらないことがあります。これについてはmainメソッドの戻り値がvoidであることにも関係しますので、詳しくは後述します。

1-5-2.System.exit、Runtime.exit、Runtime.haltで、戻り値を指定して終わらせる

mainメソッドを終わらせるreturnの他に、Javaプログラムを終了させるなら、System.exit(int)、Runtime.exit(int)、Runtime.halt(int)が使えます。これらはmainメソッドの外でも使うことができ、実行した時点ですぐにJavaプログラム全体が終了します。

しかも、これらのクラス・メソッドを使うと、プログラムからの戻り値を整数(int)で指定できます。

  • System.exit(int):Rutime.exit(int)と同じものです
  • Runtime.exit(int):Javaプログラムを終了しますが、終了時処理がある場合は実行します
  • Runtime.halt(int)Javaプログラムをすぐに終了し、終了時処理があっても実行しません
class Main {
	public static void main(String[] args) {
		System.out.println("System.exit(255)を実行します");
		System.exit(255);
		System.out.println("System.exitのあとです"); // これは実行されない
	}
}
C:\>java Main
System.exit(255)を実行します

C:\>echo %ERRORLEVEL%
255
class Main {
	public static void main(String[] args) {
		System.out.println("Runtime.getRuntime().halt(65535)を実行します");
		Runtime.getRuntime().halt(65535);
		System.out.println("Runtime.getRuntime().haltのあとです"); // これは実行されない
	}
}
C:\>java Main
Runtime.getRuntime().halt(65535)を実行します

C:\>echo %ERRORLEVEL%
65535

なお、Runtime.exitとhaltの違いは、例えば以下のようなJavaの終了時に行いたい処理を実行するかどうかです(exitは実行し、haltは実行しない)。ただ、この動きの詳細はこの記事の範囲を超えますので、興味があれば「java addShutdownHook」などで検索してみてください。

class Main {
	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(
			new Thread(() -> System.out.println("終了時の処理を実行します")));
		Runtime.getRuntime().exit(0);
	}
}
C:\>java Main
終了時の処理を実行します

C:\>
class Main {
	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(
			new Thread(() -> System.out.println("終了時の処理を実行します")));
		Runtime.getRuntime().halt(0);
	}
}
C:\>java Main

C:\>

2.【発展】なぜ”public static void main(String[])”なのか?

ここまで説明してきたとおり、Javamainメソッドはpublic static void main(String[])でなければなりません。そのようにJava仕様で決められているとだけ覚えていても、もちろん実用上は構いません。

でも、ここまで読み進めていただいた方へは、せっかくですから「Javamainメソッドはなぜそういう決まりなの?」の観点から、mainメソッドに関する話題をお伝えしてみましょう。

2-1.なぜpublicなのか?

mainメソッドがpublicなのは「仕様でそう決まっている」だけです。mainメソッドをprivateprotectedにもできてしまうと、アクセス修飾子を何にすべきか混乱や議論が生じるので、仕様でpublicとするようあらかじめ決めてあるだけなのかもしれません。

さらになぜを進めるなら、Java仮想マシンにとってmainメソッドがpublicであるべき理由はないからです。mainメソッドがprotectedでもprivateでも、そういうメソッドがあることはクラスファイルを調べればJava仮想マシンはわかりますし、そのメソッドを呼び出すこともやろうと思えば当然できるからです。

普通のJavaクラスからでも、標準APIのリフレクションを使えば、以下のようにprivatestaticメソッドを別のクラスから呼び出せます。ということは、Javaそのものを動かしているJava仮想マシンが、単なるprivatestaticメソッドを呼び出せないなどとは、到底思えませんよね。

それに実際のところ、ずっと昔のバージョンのJavaでは、privatemainメソッドでも実行できてしまうことが、仕様どおりでない動きのJava仮想マシンのバグとして、開発中に顔を出していたくらいなのです。

class Main {
	// privateな以外は、mainメソッドの条件を満たすメソッド
	private static void main(String[] args) {
		System.out.println("main method.");
	}
}
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

// このクラスはMainと同じパッケージにあるとする
class CallMain {
	public static void main(String[] args) throws Exception {
		// Mainクラスで宣言されているメソッドの一覧を取得して、
		Method[] methods = Main.class.getDeclaredMethods();

		// static void main(String[])なメソッドを探して実行する
		// この時には、privateなメソッドでも呼び出せる!!
		for (Method method : methods) {
			if (Modifier.isStatic(method.getModifiers()) && method.getReturnType().equals(Void.TYPE)
					&& method.getName().equals("main")
					&& method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(String[].class)) {
				method.setAccessible(true);
				method.invoke(null, (Object) args);
				break;
			}
		}
	}
}

2-2.なぜstaticなのか?

mainメソッドがstaticメソッドでなければならないということは、逆に言えばインスタンスメソッドでは駄目だということです。その理由は、「クラスの詳細を知らなくても、絶対確実にインスタンスを生成できる方法がJavaにはない」からです。

前述のとおり、Java仮想マシンの観点ではアクセス修飾子はないも同然ですが、インスタンスの生成はクラスが持つコンストラクタだけしか使えません。例えば、引数のあるコンストラクタしかないクラスなら、newする時にコンストラクタの引数に何を指定すればいいのか、Java仮想マシンは分からないですよね。

class Main {
	// このインスタンスメソッドを呼び出すには、インスタンスを作らなければならないが…
	public void main(String[] args) {
		System.out.println("main method.");
	}

	// このクラスのコンストラクタはこれ一つしかない。
	// 引数のintへ何を指定すべきかは、このクラスを作ったプログラマしかわからない!!
	Main(int arg) {
	}
}

ですから、mainメソッドはstaticメソッドでなければならないのです。もちろん、staticメソッドを呼び出すにしてもクラスの読み込みが正常に終わらなければなりません。それでも、インスタンスを生成することに比べれば、制約がはるかに緩いのはお分かりいただけるでしょうか。

class Main {
	static {
		// クラスの読み込み時に必ずRuntimeExceptionが発生する
		// だから、このクラスの読み込みは絶対に成功しない!!
		if (true) {
			throw new RuntimeException("RuntimeException in static initializer!!");
		}
	}

	public static void main(String[] args) {
		System.out.println("main method.");
	}
}
C:\>java Main
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: RuntimeException in static initializer!!
        at Main.<clinit>(Main.java:4)

2-3.なぜvoidなのか?

mainメソッドの戻り値がvoidなのは、スレッドを使ったプログラミングをしている場合は、mainメソッドが終わってもJava仮想マシンそのものの実行は終了しないことがあるからとも考えられます。

例えば、以下のようにmainメソッドの中でスレッドを生成して実行してみます。mainメソッドはすぐ終わりますが、その後でも生成したスレッドが動いていることがわかります。Java仮想マシンが終わるのは、実行しているスレッドが全て終わった時なのです。

class Main {
	public static void main(String[] args) {
		System.out.println("mainメソッドが始まりました");
	
		// 10秒だけ待つだけのRunnable
		Runnable r = new Runnable() {
			public void run() {
				System.out.println("スレッドの実行が始まりました");

				try {
					Thread.sleep(10000); // 10秒待つ
				} catch (Exception e) {
				}

				System.out.println("スレッドの実行を終わります");
			}
		};

		System.out.println("別スレッドを開始します");
		new Thread(r).start();
		System.out.println("mainメソッドが終わりました");
	}
}
C:\>java Main
mainメソッドが始まりました
別スレッドを開始します
mainメソッドが終わりました
スレッドの実行が始まりました
スレッドの実行を終わります

ですから、mainメソッドの戻り値としてプログラム全体の戻り値を指定できてはまずいケースがあります。なぜなら、スレッドでの実行結果を戻り値にしたいことがあるからです。そのために、System.exit(int)Runtime.exit(int)Runtime.halt(int)があるのです。

2-4.なぜ“main”なのか?

mainメソッドの名前が“main”なのは、JavaC言語の流れをくむプログラミング言語だからでしょう。Javaのプログラミング言語としての文法はC言語が基になっていますので、C言語の経験者もとっつきやすいよう、C言語のエントリーポイントであるmain関数と同じ“main”にしたのだと考えられます。

例:C言語のmain関数

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Hello, world.\n");
    return 0;
}

なお、JavaC言語以外のプログラミング言語でも、mainあるいはそれに近い言葉は大変よく使われます。おおよそ、プログラムのエントリーポイントに関連することなので、覚えておけばいいことがあるかもしれませんよ。

例:C#のmainメソッドは”Main”という名前

using System;
namespace HelloWorld
{
    class Hello 
    {
        static void Main() 
        {
            Console.WriteLine("Hello, world.");
        }
    }
}

 

例:Pythonではmain関数そのものはないが、”__main__”というものが使える

def main():
    print("Hello, world.")

if __name__ == "__main__":
    main()

2-5.なぜ引数はString[]だけなのか?

mainメソッドの引数がStringの配列だけなのは、これまたJavaC言語の仕様が基になっているからとも言えるでしょう。それに、mainメソッドのオーバーロードを許した場合、引数違いのどのメソッドを呼べばいいのか、Java仮想マシンの立場で考えると簡単には決められないですよね。

C言語のmain関数の引数は、プログラムへのパラメータの数と、文字列を格納しているchar配列へのポインタです。でも、Javaにはポインタはありませんし、配列の要素数は配列のlengthで分かるので、String配列だけでC言語とほぼ同じ意味になりますし、それ以上の情報はいりません。

Stringであるのは、パラメータとしてもっとも扱いやすいからということもあるでしょう。何番目のパラメータが何の型かは、Javaで作られるすべてのプログラムで同じにはできませんから、一番無難で扱いやすいな文字列にしておいて、必要ならプログラム側で都度変換をしてください、ということです。

2-5-1.エントリーポイントが選べるプログラミング言語もある

他のプログラミング言語では、Javaとは違ってエントリーポイントとする関数・メソッドが選べるものは多いです。そのようなものだと、引数や戻り値を必要・不要のどちらにするかプログラマが選べたりします。

例えば、C# 7.0なら以下のどれかであればOKです。なんと、C#ではエントリーポイントからの戻り値もintvoidから選べるようですね。C言語も、基本的には三つのパターンからどれかを選びます(二番目と三番目は実質的には同じです)

static int Main()
static int Main(string[] args)
static void Main()
static void Main(string[] args)
int main(void)
int main(int argc, char *argv[])
int main(int argc, char **argv)

ですので、使っているプログラミング言語のエントリーポイントについてきちんと学んでおけば、いざと言う時に便利に使えるかもしれませんよ。


3.まとめ

この記事では、Javaプログラムのエントリーポイントであるmainメソッドについてお伝えしてきました。mainメソッドには5つのルールがあります。きちんと覚えておられますか? “public static void main(String[])”ですので、お忘れなく。

Javaの初心者へはおまじないと説明されることが多いmainメソッドですが、Javaのプログラミング言語としての源流や、文法・実行上の制限などから大きな影響を受けています。その理由を考えて納得すれば、Javaをもう少し深く理解するためのきっかけが得られると思いますよ。

私たちは、全てのエンジニアに市場価値を高め自身の望む理想のキャリアを歩んでいただきたいと考えています。もし、今あなたが転職を検討しているのであればこちらの記事をご一読ください。理想のキャリアを実現するためのヒントが見つかるはずです。

『技術力』と『人間力』を高め定年まで働けるエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって、エンジニアとしてだけでなくビジネスパーソンとして高い水準を目指し、社会や顧客に必要とされることで、関わる人々に感動を与える集団であろうと思っています。

  • 定年までIT業界で働くためのスキルが身につく「感動大学」と「技術勉強会」!
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な「人事評価制度」!
  • 理想のエンジニア像に近づくためのよきアドバイザー「専任コーチ制度」!
  • 稼動確認の徹底により実現できる平均残業時間17時間の働きやすい環境!

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。

コメント

文系IT未経験歓迎!【23卒対象】新卒採用のエントリーを受付中!
文系IT未経験歓迎!
【23卒対象】新卒採用のエントリーを受付中!