プログラミングをするうえで、エラーとの格闘は避けては通れません。
しかし、Javaのエラーは色々あり、その対処方法も様々…
Javaそのものの仕組みが絡む部分もあり、私もかなり苦戦した部分です。
そこで、今回はJavaのエラーまわりを解説していきましょう。
タイトルにも入れた「コンパイルエラー」、「実行時エラー」について解説していきます。
それぞれの概要と具体例、出た時の対処方法まで見ていきましょう。
ひとことで結論:出るタイミングと、何に対してのエラーかが違う!
先に、一言で結論を言ってしまいましょう。
コンパイルエラーは、コンパイル時に出る、ソースコードの間違いに対して出るエラーです。
実行時エラーは、実行時に出る、プログラムの動作がおかしいことに対して出るエラーです。
まずはこのおおまかな認識を頭に入れつつ、読み進めてもらえればと思います。
Javaの仕組み -コンパイルと実行-
まず、今回の話を理解するために必要な知識があるため、先に解説しておきましょう。
そもそもの、Javaで書かれたプログラムがどう動くかのお話です。
eclipseなどのIDEを使わない場合、ソースコードを拡張子.javaで保存します。
これは、そのままでは機械が読み取れず、一度別の形式へ変換する必要があります。
そこで、.javaファイルに対して、javac
というコマンドで一度別のファイルを生成します。
その生成したファイルに対してjava
コマンドを使うことで、プログラムが実行できます。
このうち、javac
で機械が読み取れるファイルを生成するのが、コンパイルです。
内部でやっていることは大きく2つ。
1つは、人間にわかりやすいソースコードという形から、機械が実行できる形式に変換すること。
もう1つが、文法などのチェックを行うことです。
以上の内容を踏まえたうえで、早速本編に入りましょう。
コンパイルエラー
まずコンパイルエラーから。
読んで字のごとくですが、コンパイルエラーはコンパイルのときに発生するエラーです。
コマンドであれば、上で書いたjavac
コマンドの実行時に発生します。
eclipseだと、エラーのところに赤線が引かれるのでわかりやすいですね。
コンパイルエラーの内容と例
コンパイルエラーは、ソースコードの間違い…主に文法やスペルの間違いです。
例えば…
- 変数名を間違えた
- 何か必要な記述が抜けてしまった
- カッコの対応がおかしい
などで発生します。
具体例を挙げてみましょう、以下のソースコードを見てください。
public class SampleClass {
public static void main (String[] args) {
System.out.prnitln("Hello World!");
}
}
このソースコードの3行目、よく見るとスペルミスがありますね。
println
とすべきところが、prnitln
となっています。
これでコンパイルすると…
(実行コマンド)> javac .\SampleClass.java
.\SampleClass.java:3: エラー: シンボルを見つけられません
System.out.prnitln("Hello World!");
^
シンボル: メソッド prnitln(String)
場所: タイプPrintStreamの変数 out
エラー1個
このように、メソッドがないよというコンパイルエラーが出てきます。
もう1つ、例を見てみましょう。
public class SampleClass {
public static void main (String[] args) {
printHello();
}
static printHello () {
System.out.println("Hello World!");
}
}
今度は、メソッドの定義について、戻り値の型が抜けてます。
これでコンパイルすると…
(実行コマンド)> javac .\SampleClass.java
.\SampleClass.java:6: エラー: 無効なメソッド宣言です。戻り値の型が必要です。
static printHello () {
^
エラー1個
今度はそのまま、戻り値が無いよと教えてくれましたね。
これがコンパイルエラーです。
ちなみに、発展なのでここでは解説しませんが、コンパイルの中では様々な解析を行っています。
気になったら字句解析、構文解析、意味解析などで調べてみましょう。
コンパイルエラーの対処法
では、コンパイルエラーが出たらどうするか。
基本的には、以下の流れで解決していきます。
- エラーが出たポイントと原因を確認する
- 本来、何が正しいかを判断する
- 正しい内容に直す
先ほど挙げた具体例それぞれで、この対処法を実際にやってみましょう。
まず1つ目、ソースコードとエラー内容を再掲します。
public class SampleClass {
public static void main (String[] args) {
System.out.prnitln("Hello World!");
}
}
(実行コマンド)> javac .\SampleClass.java
.\SampleClass.java:3: エラー: シンボルを見つけられません
System.out.prnitln("Hello World!");
^
シンボル: メソッド prnitln(String)
場所: タイプPrintStreamの変数 out
エラー1個
コンパイルエラーの内容を見ると、先頭にファイル名と数字があります。
この数字が、コンパイルエラーの出た行番号です。
今回、3行目で「シンボルが見つからない」という内容のエラーが出たよ、となります。
その詳細が下の部分で、System.out
の次あたりが怪しいぞと提示してくれています。
実際、そこにスペルミスがあるので、しっかり確認して直しましょう。
2つ目、同じくソースコードと実行結果を再掲します。
public class SampleClass {
public static void main (String[] args) {
printHello();
}
static printHello () {
System.out.println("Hello World!");
}
}
(実行コマンド)> javac .\SampleClass.java
.\SampleClass.java:6: エラー: 無効なメソッド宣言です。戻り値の型が必要です。
static printHello () {
^
エラー1個
まずは同じくファイル名とソースコードの何行目かを確認します。
今回はダイレクトに問題点を指摘してくれているので、適切な戻り値の型を入れて修正完了です。
ちなみに、上の2つはエラーが出た部分を直す必要がありました。
しかし、エラーが出た個所が絶対に間違っているとは限りません。
例えば、以下の例を見てみましょう。
public class SampleClass {
public static void main(String args[]){
testMethod();
}
private static void testMethod(int n){
// do something
}
}
メソッドの定義にはint型を1つ引数として受け取るよう記載していますが、呼び出しで引数を渡していません。
これでコンパイルすると、関数を呼び出す3行目側でエラーが出ます。
(実行コマンド)> javac .\SampleClass.java
.\SampleClass.java:3: エラー: クラス SampleClassのメソッド testMethodは指定された型に適用できません。
testMethod();
^
期待値: int
検出値: 引数がありません
理由: 実引数リストと仮引数リストの長さが異なります
エラー1個
これで、呼び出した3行目が絶対に間違っているかというと、そんなことはありません。
メソッドtestMethodで引数nが使われていなくて、その定義(6行目)を修正する可能性もあります。
このように、「エラーが起こった場所が間違っている」という先入観が解決を邪魔することもあるので、気を付けましょう。
実行時エラー
次に実行時エラー、こちらも読んで字のごとく、プログラムを実行したときに出るエラーを指します。
コンパイルは通っているので文法の誤りはないのですが、動作に問題がある場合に発生します。
…だけで説明が済めばよかったのですが、ここの複雑さがなかなかに厄介でしょう。
実行時エラーには、いくつかの種類があります。
実行時エラーの種類
実行時エラーは、まず大きく2つに分けることができます。
1つ目が狭義のエラー、ややこしいので本記事ではErrorと英語で表記しましょう。
2つ目が例外、こちらがさらに非チェック例外、チェック例外の2つに分かれます。
- 実行時エラー
- Error
- 例外
- 非チェック例外
- チェック例外
順番に、内訳を見ていきます。
Error
Errorは、一言で言うと起こってはいけない致命的な問題です。
どう致命的かというと、具体的なエラーの内容を見て貰えば分かると思います。
ということで、2つほど具体的なErrorを見てみましょう。
まずOutOfMemoryError、これはプログラムを動かすためのデータ領域が不足したときに発生します。
プログラムのためのデータがもう置けないので、プログラムもそこで止まります。
次にStackOverflowError、こちらはメソッドに関係するお話ですね。
メソッドを呼ぶとき、呼ぶ元で持っていた変数などの情報を、あとで取り出すために一度スタックという仕組みで保存しておきます。
もちろん、そのメソッドの中でまたメソッドを呼ぶこともあり、その時はさらにスタックで保存をしていきます。
スタックは片方がふさがれている筒をイメージしてもらうとわかりやすく、上から入れれて、上から取り出せる仕組みになっています。
この筒、当然ですが入れれるデータの量に限界があります。
その限界を超えて、なおメソッドを呼び出そうとすると、このStackOverflowErrorの発生、となるわけですね。
そんなことにはならんやろと思うかもしれませんが、再帰(自身を呼び出すメソッド)で終了条件を忘れるとよくなります。
これも、プログラムの動作に必要な処理ができなくなるため、プログラムが停止します。
他にもいくつか種類はありますが、ようは起きた時点でプログラムが終了してしまうような、重大なエラーがこのErrorです。
原則、Errorは起こさせないコーディングをして、そもそも発生させないようにします。
…一応、発生したらどうなるかを示すため、サンプルソースと実行結果を記載します。
が、Errorが起こるソースコードなので、実行しないようにしてください。
public class SampleClass {
public static void main(String args[]) {
int res = fibonacci(5);
System.out.println("res : " + res);
}
private static int fibonacci(int n) {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
(実行コマンド)> java SampleClass
Exception in thread "main" java.lang.StackOverflowError
at SampleClass.fibonacci(SampleClass.java:8)
at SampleClass.fibonacci(SampleClass.java:8)
at SampleClass.fibonacci(SampleClass.java:8)
(1000行ほど中略)
at SampleClass.fibonacci(SampleClass.java:8)
今回、終了条件を指定していない再帰で、StackOverflowErrorを起こしました。
ここでやろうとしていたのはフィボナッチ数列という数列の第n項を求めることです。
フィボナッチ数列は以下の条件で決まる数列です。
- 初項:1 (0とする場合もある)
- 第2項:1
- 第n項(n ≧ 3):第n-1項と第n-2項の和
具体的な数字でいうと、小さい順に1, 1, 2, 3, 5, 8, 13, 21, …と続いていきます。
よくプログラムで第n項を求める練習問題があったりするので、覚えておくといいでしょう。
ちなみに、正しく動作するものは以下の通りです。
public class SampleClass {
public static void main(String args[]) {
int res = fibonacci(5); // number input
System.out.println("res : " + res);
}
private static int fibonacci(int n) {
if(n < 2){
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
メソッドfibonacciの最初で、引数nが2未満なら1を返すとしているところが、終了条件です。
再帰を使うときは、この終了条件に気を付けましょう。
非チェック例外(RuntimeException)
ここから例外、Errorと対比させると対処可能な異常事態、といったイメージでしょう。
どういうことかというと、発生したときの処理である例外処理を書けるのが特徴ですね。
まずは非チェック例外(RuntimeException)を見ていきましょう。
こちらは、例外処理を書かなくてもいい例外になります。
書かなくてもいい、つまり書くことも可能ですが…非チェック例外は、Errorと同じくそもそも発生させないのが定石でしょう。
というのも、いくつか具体的に非チェック例外を挙げてみると…
- ArrayIndexOutOfBoundsException:配列の範囲外アクセスによる例外
→範囲外だったらアクセスしないよう組むべき - NullPointerException:nullのオブジェクトへアクセスすることによる例外
→nullだったらアクセスしないよう組むべき - ArithmeticException:ゼロ割など、計算エラーによる例外
→そういったエラーが出ないよう組むべき - などなど…
といった感じで、プログラムを書き換えることで、回避できるものが多いからです。
実際に、非チェック例外を起こしてみましょう。
まずは、配列の範囲外にアクセスしてみます。
public class SampleClass {
public static void main (String[] args) {
errorTest(0);
errorTest(1);
errorTest(2);
errorTest(3);
errorTest(4);
errorTest(5);
}
static void errorTest (int index) {
int[] array = new int[5];
array[index] = 10;
}
}
(実行コマンド)> java SampleClass
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at SampleClass.errorTest(SampleClass.java:13)
at SampleClass.main(SampleClass.java:8)
ソースコードを見てもらえれば分かりますが、メソッドerrorTestを、引数5で呼び出したときに、そのメソッド内でエラーが起こっています。
メソッド内で0から4の配列を用意し、その5番目にアクセスしているので、範囲外になるというわけですね。
メッセージがさっきは日本語だったのに今度は英語になっていますが、実はよりわかりやすくなっています。
後で、エラー内容も詳しく見ていきましょう。
もう1つ、非チェック例外を起こしてみます。
public class SampleClass {
public static void main (String[] args) {
String test = null;
System.out.println(test.length());
}
}
(実行コマンド)> java SampleClass
Exception in thread "main" java.lang.NullPointerException
at SampleClass.main(SampleClass.java:4)
直接nullを代入したオブジェクトのメソッドを呼び出そうとしていて、当然エラーが出ます。
どうでもいいですが、よく言うぬるぽは、このNullPointerExceptionのことです。
そのあとのガッは何なんだろう…?
チェック例外(Exception)
非チェック例外が、例外処理を書かなくてもいい例外でした。
それに対し、チェック例外は、例外処理を書かなければいけない例外です。
例外処理があるかをチェックしているから、チェック例外と呼ばれています。
チェックしているのはコンパイルのとき、つまり書き忘れるとコンパイルエラーが出るので注意しましょう。
具体的に、チェック例外を挙げてみます。
- FileNotFoundException:ファイルを開こうとしたけど無かったときに出る例外
- IOException:なんらかの入出力処理が失敗したときに出る例外
- SQLException:データベースに関するエラーが起こったときに出る例外
ここから、理解しやすくするためのイメージをお伝えします。
あくまで私の考えであって、これが正しいわけではないことに注意してください。
チェック例外は、基本的にプログラムやJavaの範囲外も影響します。
例えば、FileNotFoundExceptionはファイルの有無、つまりプログラムの外に影響されますよね。
他にも、SQLExcepionは接続するデータベースが落ちていたら発生することがあり、これもプログラムの外に原因があります。
そのため、プログラム内部だけでは解決することができません。
そして、プログラムから見れば実行するまでファイルの有無はわからないのですから、ファイルが無かったらどうしようかと考えるのは自然でしょう。
それによって処理が変わるのですから、むしろ考えておかないと困ります。
だから、起こった場合にどうするかを必ず書く必要がある、と考えています。
エラーになった場合のメッセージは、基本的にErrorや非チェック例外と同じです。
ちなみに、例外処理の書き方は別記事で解説するので、更新までしばしお待ちください。
実行時エラーの対処法
ここまで書いた通り、Errorと非チェック例外はそもそも発生しないような修正が必要となります。
そのための方針は以下の通り。
- どこを実行したタイミングでエラーが出たかを確認する
- その原因となった処理を特定する
- 何が正しい処理なのかを確認する
- 正しい処理になるよう修正する
コンパイルエラーと似てますが、エラー表記も変わるので詳細を見ていきましょう。
具体例で、配列の範囲外アクセスをもう一度貼っておきます。
public class SampleClass {
public static void main (String[] args) {
errorTest(0);
errorTest(1);
errorTest(2);
errorTest(3);
errorTest(4);
errorTest(5);
}
static void errorTest (int index) {
int[] array = new int[5];
array[index] = 10;
}
}
(実行コマンド)> java SampleClass
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at SampleClass.errorTest(SampleClass.java:13)
at SampleClass.main(SampleClass.java:8)
この実行結果の見方を説明していきます。
メッセージの1行目、Exception in thread "main"
の部分、これはエラーが発生したスレッドを表しています。
この一文の意味が分からない場合、気にする必要はありません。
ちょこっとだけ補足しておくと、スレッドはプログラムの処理を進める流れのようなもの。
意図的に作っていなければ、基本的にはmainの1つだけです。
その直後、今回は配列の範囲外アクセスを表すjava.lang.ArrayIndexOutOfBoundsException
の表記と、そのうしろに数字が書かれています。
配列の範囲外アクセスでエラーが出たときは、いくつでアクセスしてエラーが起きたかも表示してくれます。
今回で言えば、5でアクセスしたときにエラーが出たよということですね。
そのほか、発生したエラーによって、ここの内容が変わります。
その下から、どこでエラーが起きたかを表しています。
atの後ろにクラス名.メソッド名(ファイル名:行数)
という表記が複数行書かれています。
1つ目はSampleClassのerrorTestメソッド、SampleClass.javaファイルの13行目で起こっていますね。
この1つ目が、実際にエラーが発生した行番号になります。
2つ目、これは1つ目がどこから呼ばれているかを表していて、読み方は全く同じ。
今回はこれで終わりですが、さらに他から呼ばれていれば、どんどん記載が長くなっていきます。
つまり、at行の先頭がエラーの出たポイントを指し、2行目が1行目を呼んでるポイントを指し、3行目が2行目を呼んでいるポイントを指し…と続いています。
これを追っていくことで、どんな処理を通ってそこに着いたかを確認することができるのです。
さて、今回はerrorTestメソッドの10を代入している部分でエラーが発生しました。
そのときの添え字アクセスは5で行っており、呼び出し元はファイル8行目のerrorTest(5);
です。
この場合、原因となっている個所は、errorTest(5);
ですね。
どうやって修正するかは、状況によって変わります。
配列の用意する範囲が狭かったのか、メソッドを5で呼び出すのがまずかったのか、メソッド内でチェックを入れるべきだったのか…
そこは、状況に合わせて判断してください。
チェック例外は、そもそもソースコード内の修正が不要な場合もあります。
どこでエラーが起こっているか、何が原因なのかの調査は同じですが、そこから先の対応はプログラムの外かもしれないので、これも何が正しいのかを判断するようにしてください。
まとめ
今回は、コンパイルエラーと実行時エラーの違いを解説してきました。
ざっと、要点だけ振り返ってみましょう。
それぞれ名前の通り、コンパイルエラーはコンパイル時に、実行時エラーは実行時に出るエラーのことでした。
コンパイルエラーはソースコードの間違い、主に文法やスペルの間違いによって発生します。
実行時エラーはプログラムの動作に問題があった場合に発生するエラーで、さらに細分化できて…
- Error:発生したらプログラムが終了するような、重大なエラー
- 例外
- 非チェック例外:例外処理の記載が不要な例外
- チェック例外:例外処理の記載が必須な例外
このような形に分けることができます。
Errorや非チェック例外は起こさせない方針であるのに対し、チェック例外は発生したらどうするかを考える必要があります。
発生した場合の修正について、両者とも発生ヶ所の特定、原因の特定、修正方法の確認、修正という流れになります。
が、傾向として、コンパイルエラーはエラー発生ヶ所と原因ヶ所が同じ場合が多く、実行時エラーは異なる場合が多いでしょう。
あくまで傾向で、この限りではないので注意してください。
このエラーまわりは複雑で、しっかり対応しないとプログラムが止まる危険性もあります。
慣れないうちは大変ですが、避けては通れません。
1つずつ、確実に身に付けていきましょう。
独学が難しいと思ったら、スクールを検討しよう
さて、ここまでの内容を独学でやるのはなかなか難しいと思いませんか?
特に、エラーまわりはただでさえ複雑なのに加えて、しっかり対応しないとプログラムが上手く動作しません。
また、例外処理も含めて、ベストプラクティスまで把握するのは相当難しいでしょう。
特に、2言語目以降ならまだいいですが、プログラミング初挑戦だと余計に意味不明だと思います。
そこで、目的にもよりますが、本格的に勉強したいならやはりスクールに通うことをオススメします。
今回はJavaの内容を解説してきましたが、どの言語でもしっかり学ぶなら人に教えてもらうのがベターでしょう。
何より、困った時に質問できるのが最大のメリットです。
スクールによっても扱う内容から特徴まで色々異なるので、まずは無料カウンセリングで雰囲気を知り、自分の求めるスクールを選べるようにしてください。
いくつか、具体的なスクールをご紹介します。
スクール名 | 扱う内容 | 無料カウンセリング | 形式 | ポイント |
---|---|---|---|---|
テックアカデミー TechAcademy [テックアカデミー] | Webデザイン PHP/Laravel WordPress Ruby on Rails Java Python などなど | 〇 | オンライン、教材での学習 | 転職はもちろん、副業を目指すコースもあり 副業は実際に案件を受け、納品するところまでサポートしてくれる 95%が初心者、かつ30代以上が70%近く、年齢も関係なし |
ウズウズカレッジ(Javaコース) 【UZUZ Java】 | Java ※他コースもあり | 〇 | オンライン、動画での学習 | Javaシルバーという資格の取得も可能 質問も常時可能、転職のサポートまで行ってくれる 今後IT業界に転職したい場合にオススメ |
忍者CODE 【忍者CODE】 | Web製作 Webデザイン Ruby Python Java Shopify | 〇 | オンライン、動画での学習 | 独学コースから転職コースまで幅広い品ぞろえ もちろん、全てのコースで質問は可能 自分のペースで進めたい、でも質問はしたいという人にオススメ |
Winスクール 【Winスクール】 | Java Python C言語 PHP Ruby JavaScript などなど | 〇 | オンライン、対面選択可能 | プログラミングだけでなく、資格取得やグラフィック系、データ分析まで幅広いコースあり 特に資格は一度での合格率が99% ほとんどが未経験からのスタート、就職や転職までサポート |
スクールはこれに限らず、扱う内容や目的によって、多岐にわたります。
是非、通うのならご自身の目的、特徴にあったスクールを選びましょう。
コメント