JavaのThrowableクラスを直接使う機会は多くありません。しかし、Throwableクラスには、エラーや例外のコード作成でよく使われる機能やメソッドを継承して使うことができます。
Java学習者の方にとってはThrowableを知ることでエラーや例外の基本を学べますし、エンジニア・実務開発者の方にとってはエラーハンドリングの仕組みを深く知る良い機会です。
そこで本記事は、メソッドの使い方や禁止事項・ルールなどを含めたThrowableの基礎知識を解説します。
目次
閉じる
1.JavaのThrowableとは
JavaにはThrowableクラスというエラーや例外のすべてを包括するスーパークラスがあります。スーパークラスは「基底クラス」とも呼ばれているものです。JDKをインストールした際に、標準でライブラリとして入っており、例外やエラーを使うときに必要となります。
Throwableはクラスの下に「Error」や「Exception」のサブクラスがあることが特徴です。まずErrorはコードに問題が起きたときに返すクラス、Exceptionは例外が発生したときに使われるクラスです。
この2つは明確に区別されており、Errorはプログラムにとって致命的で修復不可能なエラーです。一方、Exceptionは対処できる例外を指します。Exceptionにはさらに細分化した「RuntimeException」もあります。
特にErrorは、自分でアプリケーションにthrowすることは推奨されません。このErrorの利用はあくまでもコンパイルや実行時にJava環境(JVMやシステム)が自動でthrowして(投げて)知らせます。
また、Errorはtry-catchなどで事前に修復を試みることは基本的に不可です。Errorが出るときはコードやメモリなど根本的な設計見直しが要求されます。
よくあるエラーや例外
以下はよくあるエラー(Error)や例外(Exception)の一覧です。
Errorのクラス:
OutOfMemoryError
StackOverflowError
NoClassDefFoundError
ExceptionInInitializerError
Exceptionのクラス:
【チェック例外】
IOException
SQLException
ClassNotFoundException
【非チェック例外】
NullPointerException
ArrayIndexOutOfBoundsException
IllegalArgumentException
ArithmeticException
NumberFormatException
例えば、Errorは、メモリを超過して使えなくなる「OutOfMemoryError」が代表的です。コードを正しく書いてもメモリに問題があれば出ます。
それに対してExceptionは、ファイル読み込み失敗時の「IOException」や、nullの例外で起きる「NullPointerException」などが知られています。
そして、クラスのチェックの有無(「チェック例外」か「非チェック例外」か)は、事前の例外チェックをするクラス区分のことです。try-catchなどの対策がない場合に、チェック例外のExceptionクラスでは、「コンパイルエラー」として返します。
逆に、非チェック例外のExceptionクラスはそれがなく、実行時に例外エラーが発生した場合に止まる(処理落ちや停止)だけです。Javaでは非チェック例外としてNullPointerExceptionが有名で、実際の開発現場ではデバッグ時に、このNullが例外を意図せず発生させてバグなどでエンジニアを悩ませることもあります。
2.JavaのThrowableの使い方
JavaのThrowableは、そのまま使うことは基本的にせず、サブクラスに任せて使用します。特に直接newしたり、エラーのコードを自分で書いたりすることは基本的にNGです。そこで、基本的なキャッチに使うThrowableクラスの書き方や学習者向けの実践的なコードとしてExceptionの使い方を紹介します。
Throwableをキャッチに使う方法
Throwableは通常、スーパークラスをそのまま使うケースはほとんどなく、むしろ使ってしまうと問題が起きることがあります。そこで、数少ないThrowableを使う方法が以下のコード例です。
public class SafeApp { public static void main(String[] args) { try { runApplication(); } catch (Throwable t) {
System.err.println("エラー種別: " + t.getClass().getName()); System.err.println("エラーメッセージ: " + t.getMessage()); t.printStackTrace(); } }
System.out.println("アプリを実行中…");
System.out.println(numbers[5]); } } |
上記では、try-catchの形にして、catch (Throwable t)で例外をキャッチしています。この方法なら、Throwableを直接使う(「throw new Throwable」と書く)で、ログなどの「catch」に絞ることで、起きる問題を回避しやすいのです。
しかし、catch (Throwable t)の書き方は厳密には使用自体が推奨されないため、Java初心者の方は次のサブクラス(Exception)のみを使うようにしましょう。
【チェック例外クラス】IOException
Errorは通常書くことができないので飛ばして、次に、Exceptionの1つであるIOExceptionの書き方です。このIOExceptionは、ファイルがない場合に例外を返す処理として使われます。
IOExceptionの特徴はチェック例外クラスの特徴を持つ点です。チェック例外の場合は、例外を必ずtry-catchで捕捉した上で処理しなければなりません。
import java.io.*;
public static void main(String[] args) {
BufferedReader reader = new BufferedReader(new FileReader("存在しないファイル.txt")); String line = reader.readLine(); reader.close();
System.out.println("ファイルの読み込み中に問題が発生しました: " + e.getMessage());
} } |
実際にコードを書くと、先程書いた「catch (Throwable t)」 のスーパークラスの使用ではなく、catch ()の中が「IOException e」です。これはExceptionのサブクラスでキャッチしています。
【非チェック例外クラス】NullPointerException
今度は、非チェック例外クラスで頻出のNullPointerExceptionです。NullPointerExceptionは、例外を潰して処理することが目的ではなく、事前にnullチェックして予防することが基本となります。
そこで、null出ない場合に正常の処理を実行し、それ以外は例外を出力するメッセージにすることで予防する形にしたのが以下のコードです。
public class NullCheckExample { public static void main(String[] args) { String name = null;
System.out.println("文字数:" + name.length()); } else { System.out.println("名前が未設定です"); } } } |
事前にnullチェックで防ぐ場合、コード内にExceptionを書くのではなく、Exception自体が出ないようにすることがコード設計の基本となります。これが、コード内にif-elseで実行してExceptionを書かない理由です。
関連記事
Java superとは?メソッド呼び出しなど基本的な使い方や2つ上の親クラスの参照方法を解説
JavaのList初期化ガイド|初期化の方法や実装事例、addできない理由なども解説
3.JavaのThrowableで使用するメソッド
JavaのThrowableでは、スーパークラスで用意されているメソッドをサブクラスでも使用することができます。以下は、その中でもよく使われるメソッドです。代表的なメソッドの使い方と意味・役割の解説です。
getMessage()
まずは、ログのエラーメッセージの出力で使われるThrowableのgetMessage()メソッドです。
public class GetMessageExample { public static void main(String[] args) { try { throw new Exception("ファイルが見つかりません"); } catch (Exception e) { System.out.println("エラーメッセージ: " + e.getMessage()); } } } |
このコードでは、まずtryで「throw new Exception」を書いて例外を投げるように命令します。
次に、「catch (Exception e) 」で例外を補足します。そのメッセージ表示にはgetMessage()メソッドを使って「ファイルが見つかりません」というエラーを受け取る仕組みです。
例外のログは基本的に開発者が確認するもので、製品ユーザー向けのメッセージではないことに気をつけましょう。
printStackTrace()
次に、エラーの場所(行)を特定してデバッグに活用するprintStackTrace()メソッドです。
public class PrintStackTraceExample { public static void main(String[] args) { try { int result = 10 / 0; } catch (ArithmeticException e) { e.printStackTrace(); } } } |
上記のコード例では、例外発生のなかでも数字を0で割ろうとしてことで起きる算術計算エラーの「ArithmeticException」を発生させています。数学では数字を0で割ることはできないからです。
そのエラーを補足し、e.printStackTrace()でその情報詳細をコンソールに出す処理というわけです。
「java.lang.ArithmeticException: / by zero
at PrintStackTraceExample.main(PrintStackTraceExample.java:5)」
出力された上のメッセージは、例外の分類(算術計算の例外)と「by zero」が原因で5行目にエラーが出ていることを知らせています。
toString()
toString()メソッドは、文字列を使って返し、エラーメッセージを表示する際にその種類や内容まで取得することができます。
通常、toString()の使用は数値など別の型から変換する目的のメソッドです。それをThrowableクラスでエラー表示詳細を得るために使っています。
public class ToStringExample { public static void main(String[] args) { try { throw new IllegalArgumentException("引数が不正です"); } catch (Exception e) { System.out.println("toString(): " + e.toString()); } } } |
このコードを実行して出力すると「java.lang.IllegalArgumentException: 引数が不正です」となります。
これは投げた例外クラス名「IllegalArgumentException」とそのパッケージです。意味としては、メソッドに渡された引数が不正で、例外エラーが出たことを示します。
try-catchを使っていますが、IllegalArgumentExceptionは非チェック例外のため、必ずしもcatchで補足する必要はありません。この場合、ログに明確にメッセージを出す目的で使用しています。
getCause()
getCause()は、例外が発生した原因や、その原因が不明ならそれを返すメソッドです。
public class GetCauseExample { public static void main(String[] args) { try { Throwable cause = new NullPointerException("null 参照"); throw new Exception("処理に失敗しました", cause); } catch (Exception e) { System.out.println("原因: " + e.getCause()); } } } |
上記では、例外を捕捉して、そのNullPointerExceptionをコンソールに出力しています。この場合は、「原因: java.lang.NullPointerException: null 参照」という出力結果です。
メッセージの構成としては、例外名の「NullPointerException」とそのパッケージ名、例外の原因として渡されたメッセージを意味するものです。
このコード例では実際のNullPointerExceptionの発生自体は省略しています。しかし、この例外が起こりやすいものに「.length()」などが挙げられます。変数名に文字列格納せず、API設定をしない状態で、nullを参照してオブジェクトがないと判定されるからです。この点はNullPointerExceptionの例示のときに例外処理のコードを書いた理由と同じです。
ちなみに、上記のようなNullPointerExceptionの発生は、初心者だけでなく、実務経験者でもデバッグで修正が必要になる事例の1つです。
getStackTrace()
getStackTrace()は、呼び出した履歴を配列でログなどに残すために使われるStackTraceElement[]型のメソッドです。
標準でコンソールの例外で発生するエラーメッセージの出力に使われるprintStackTrace()メソッドが出力した結果を使用します。そのため、配列で1つずつ読み込む自由度の高いログ作成ができます。
public class GetStackTraceExample { public static void main(String[] args) { try { throw new RuntimeException("テスト例外"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { System.out.println(element); } } } } |
上記のコードでは、拡張for文で1つずつ要素を取り出し、メッセージを出力しています。また、「element.getLineNumber()」や「getMethodName()」のメソッドのように、必要な要素だけを選んで出力することも可能です。
getStackTrace()メソッドが返すのは、例外が出た場所となります。そのため、「GetStackTraceExample.main(GetStackTraceExample.java:4)」と出た場合は、GetStackTraceExampleクラスのmainメソッドで4行目にエラーが出ていることを知らせているのです。
もちろん、行番号ではなく、ファイル名などを指すこともあります。
fillInStackTrace()
fillInStackTrace()は内部で例外エラーをcatchして、外にそれを投げたいときなど、後で履歴情報を更新して現在を取得するためのメソッドです。新規取得ではなく、上書きすることで情報を変更しています。
このメソッドの説明は、専門的にいえば「実行スタック・トレースを埋め込む」と公式記載されています。「スタック・トレース」とは、エラーがおきた箇所の特定ができる呼出履歴の情報です。
時間が経つとその間にこの履歴情報が本来は増えるなどするため、fillInStackTrace()メソッドでそれをキャッチ前に更新するのです。
public class FillInStackTraceWithException { public static void main(String[] args) { try { Exception e = new Exception("最初の処理例外が発生しました"); e.fillInStackTrace(); throw e; } catch (Exception e) { e.printStackTrace(); } } } |
上記コードでは、new Exceptionで最初の例外が発生(自己作成)してから、その後にfillInStackTrace()で更新してcatchする形を取っています。
「java.lang.Exception: 最初の処理例外が発生しました
at FillInStackTraceWithException.main(FillInStackTraceWithException.java:5)」
実行すると、出力結果に例外発生の行数や原因箇所が出ます。このコードの場合、5行目が原因とわかる仕組みです。
4.JavaのThrowableの禁止事項やルール
JavaのThrowableやサブクラス、メソッドなどを使用するときの禁止事項やルールを厳選して以下に紹介します。
ループの中に例外を投げず、外に出す
1つ目は、ループの中にThrowableなどの例外処理を入れないことです。ループは処理が重くなるとパフォーマンスにも影響が出ます。
特に例外を投げることは、コストがかかるため、それをループに入れてしまうと多くのコストが発生するのです。具体的には、JVMの「スタック・トレース」というデバッグで履歴情報から文字生成する際のメモリ負担コストです。
少ない回数ならそこまで差は出ませんが、分岐や試行回数が多くなると処理の中断やプロセス速度の低下といった問題にもつながります。そのため、コードのベストプラクティスを考えるときにパフォーマンスやメモリの最適化は1つの大きなポイントです。
Throwableで完全に禁止されている書き方ではありませんが、最適化のためのルールとして覚えておきましょう。
try-catchに長いコードの処理を詰め込まない
2つ目は、try-catchの中に長いコードを詰め込んで実行することはできるだけ避けることです。
例外処理は本来、どこに例外が発生するかを想定して例外を補足することにあります。しかし、複数の例外や処理をまとめて入れてしまうと、どれに例外が発生したのかわかりにくくなるのです。
特に実務上は、デバッグ時の作業に余計な負担が発生し、可読性の低下を招くでしょう。そのため、必要な例外だけを絞り込んで、その部分だけtry-catchで補足するようにします。
上のクラスが下を兼ねる処理をする
3つ目は、例外処理の場合に「RuntimeException」の上位クラスを指定すると、その下位クラスである「ArithmeticException」や「ArrayIndexOutOfBoundsException」などにも対応可能なことです。
このルールを知っておけば、どちらの例外かわからないときに上位クラスでまとめて下位クラスの処理をできるようにブロックを書くことができます。
5.JavaのThrowableのよくある質問
ここでは、JavaのThrowableに関してよくある質問に回答します。
例外処理で使うfinallyとは?
finallyは、try-catch構文で出てくる最後の処理のことです。正確には「try-catch-finally構文」と呼ばれます。finallyは、try-catchで例外を捕捉してもしなくても仕様上、必ず実行されるため、記述が省略されることもあります。
例えば、ファイル入力処理ではこのfinallyにclose()メソッドを記述して閉じます。ちなみに、try-catch内にreturnがあっても実行が省略されることはありません。ただし、ErrorやExceptionの発生で実行自体が止まった場合は、finallyブロックの実行まで到達しないことはあります。
JavaのThrowableとExceptionの違いは?
JavaのThrowableとExceptionの違いは、クラスの違いのみです。ThrowableはJavaというプログラミング言語の基底となるスーパークラスで、実装には直接出てくることはほとんどありません。
一方、サブクラスとして継承されているExceptionの例外処理では、コード作成でも普段から開発者・エンジニアが具体的に書くクラスです。チェック例外クラスではそれぞれのExceptionの子クラスを使用して例外を処理することもあります。
そのため、基礎的な概念として登場することの多いThrowableクラスと実践的なExceptionクラスの特徴をもったクラス同士の区別が可能です。
JavaのThrowableにはcatchを使うべき?
JavaのThrowableをサブクラスで使用する場合、catchは必須ではありません。コード例のようにNullPointerExceptionではあえて例外を処理せず、問題を隠さないようにすることも大切です。
ただし、チェック例外クラスのみは、catchで例外を補足することは必須です。try-catchしないとコンパイルエラーが発生するため、コード内でcatchを使うべきです。
関連記事
Java初心者ガイド|Java学習のロードマップと効率良い勉強方法を解説
6.まとめ
今回は、Javaのエラーや例外のすべてを扱えるスーパークラス・Throwableの概要やコードの書き方、メソッドの使い方などを解説しました。例外やエラーを扱う場合、アプリケーションでコードに直接書くべきものとそうでないものがあり、禁止事項やルールを守って使う必要があります。
特にエラーやThrowableを直接書くことは控えて、例外のログ作成やチェックなどに活用するのがおすすめです。以上を参考に、Throwableクラスの理解を深めましょう。
本記事が皆様にとって少しでもお役に立てますと幸いです。
「フリーランスボード」は、数多くのフリーランスエージェントが掲載するITフリーランスエンジニア・ITフリーランス向けの案件・求人を一括検索できるサイトです。
開発環境、職種、単価、稼働形態、稼働日数など様々な条件から、あなたに最適なフリーランス案件・求人を簡単に見つけることができます。
単価アップを目指す方や、自分の得意なスキルを活かせる案件に参画したい方は、ぜひ「フリーランスボード」をご利用ください。
自身に最適なフリーランスエージェントを探したい方はこちらよりご確認いただけます。