AIコーディングの質は「エラー処理」で決まる 「例外」構文をCopilotと深掘り解説AIアシスト時代のJavaプログラミング入門(11)

対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載。今回は、Javaにおけるエラー処理の方法である例外を学習します。チェック例外、非チェック例外の違い、例外の処理方法と発生方法などを、AIに聞きながら理解しましょう。

» 2026年02月12日 05時00分 公開

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「AIアシスト時代のJavaプログラミング入門」のインデックス

連載:AIアシスト時代のJavaプログラミング入門

本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。


今回のテーマはエラー処理

 対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載「AIアシスト時代のJavaプログラミング入門」前回までで、オブジェクト指向とその実用的な機能を学習してきました。今回はオブジェクト指向から少し離れて、実用的なプログラムには不可欠なエラー処理を学習します。

 エラー処理は、LLM(大規模言語モデル)にコードを書かせる「AIコーディング」でも非常に大事です。LLMはGitHubなどの公開コードを学習していますが、公開されているコードの多くは「動く」ことが目的であり、複雑なエラーハンドリングやレアケース(エッジケース)の考慮が省略されることが多いからです。実用的なコードを書く、または書かせる場合、エラー処理の理解は必須といえるでしょう。

 まずは、Javaにおけるエラー処理をざっくりと知りたいということで、「Javaのエラー処理について、思い切りざっくりと教えて。ひとまずコードは不要です。」という質問を、Visual Studio Code(以降、VS Code)からGitHub Copilot(以降、Copilot)に投げてみました(図1)。なお、モデルには今回もGPT-5 miniを使っていきます。

図1 Javaのエラー処理についての質問への回答 図1 Javaのエラー処理についての質問への回答

 基本概念、種類、構文というように概要をまとめてあるので、重要そうな冒頭の数項目から掘り下げていくのがよさそうです。以降、それぞれの項目を深めていきましょう。

例外とは

 図1の「基本概念」に示されているように、Javaにおけるエラー処理は「例外」(Exception)という仕組みで扱うようです。

・基本概念:エラーは「例外(Exception)」で扱い、発生を捕まえて適切に処理することでプログラムの異常停止を防ぐ。

 例外が起きることは「発生」といい、それは「捕まえる」もので、適切に処理することで、プログラムが異常停止することを防ぐわけですね。「発生」「捕まえる」など、例外については用語が若干特殊なようですから、慣れていきましょう。

 同じく、「種類」には例外に種類があることが示されています。

・種類:チェック例外(コンパイル時に宣言/処理が必須)と非チェック例外(RuntimeException系、通常プログラマーのミス)

 「チェック例外」と「非チェック例外」に分けられていて、前者はコンパイル時に対応が必須で、後者はプログラマーのミスであるとされています。

 「発生と伝播(でんぱ)」と「捕捉構文」には、具体的なJavaのキーワードが登場しています(「捕捉」は既出の「捕まえる」と同義ですね)。それぞれ、以下のような役割になるようです。

  • throw:例外を投げる
  • throws:例外を呼び出し元に伝播する
  • try:内部で危険な処理を行う
  • catch:例外の型ごとに処理する
  • finally:必ず実行される処理

 ここにも、「投げる」や「伝播」といった、これまでは見なかった用語が使われています。具体的な構文が出てきたので、次で具体的なコードを示してもらいながら、例外の仕組みや構文を深めていきましょう。

【補足】例外関連の用語

 本稿ではAIの回答にある用語を使っていきますが、他所の説明を読んだときに混乱しないように、それぞれ以下のように呼ばれることがあることを補足しておきます。

  • チェック例外、非チェック例外➡検査例外、非検査例外
  • 投げる➡スローする
  • 捕捉する➡キャッチする

こんな質問をしてみたい!

 そもそも、なぜJavaではエラー処理を「例外」と呼ぶのでしょうか。「そもそもJavaではなぜエラー処理を例外と呼ぶのか?」と聞いてみると、例外という言葉だけで難しそうと感じるイメージを緩和できるかもしれませんよ。

チェック例外と非チェック例外

 早速、例外がどのように発生して、どのように捕捉する(捕まえる)のかという流れを見ていきたいところですが、例外の種類(チェック例外と非チェック例外)をもう少し掘り下げてみましょう。チェックと非チェックの意味も分かりにくいですが、使い分けも気になるところです。「チェック例外と非チェック例外のみをざっくりと。まだコードは不要です。」と投げてみました(図2)。

図2 チェック例外と非チェック例外についての質問への回答 図2 チェック例外と非チェック例外についての質問への回答

 チェック例外と非チェック例外について示され、使い分けも大まかに示されました。チェック例外は、以下のようなものを指すようです。

  1. Exceptionクラスを継承する(RuntimeExceptionは継承しない)
  2. 捕捉するか、throwsが必須
  3. 入出力など呼び出し側が回復を試みられる可能性がある事象に向く

 1.の文言から分かるように、例外はクラスということです。具体的なクラス名はともかく、回復可能な事象の発生に何らかの対応が必須であるのが、チェック例外ということですね。チェック例外というのは、チェックが必要だからというように捉えてよさそうです。

 これに対し非チェック例外は、以下のようなものを指すようです。

  1. RunTimeExceptionクラスかErrorクラスを継承する
  2. 捕捉するなどの対応は不要
  3. プログラマーのミスや回復が困難な事象に向く

 回復不能な事象を指すので、対応は求められないのが非チェック例外ということです。非チェックというのは、チェック不要というように捉えられます。「ヌル参照や不正引数など」とあるようにプログラムの書き方の問題なので、例外で対処するというよりはプログラムを修正することが正しい対処法のようです。

 最後の「設計上の簡潔な指針」には上記をまとめた内容が記述されていますが、「具体的な型を使い」以降は具体的なコードが欲しいところです。次は、具体的なコードを示してもらいながら、まずは重要そうなチェック例外について見ていきましょう。

こんな質問をしてみたい!

 非チェック例外は、RunTimeExceptionクラスかErrorクラスを継承するとありますが、この違いは何でしょうか。これらのクラスの違いと、なぜ分かれているのかを聞いてみると、非チェック例外の意味を深掘りできるかもしれませんよ。

チェック例外

 ここまでは、あえてコードを示してもらってこなかったので、チェック例外を含むプロジェクトを作ってもらうことから始めます。これまで行ってきたようにAgentモードで、「basicプロジェクトを基にexception-basicプロジェクトを作り、既存の例外を使うチェック例外の簡単なサンプルコードを入れて。」と投げて一連の作業を実行してもらいました(図3)。「既存の例外を使う」としたのは、まずはJavaが標準で用意する例外を使い、その捕捉や対応についてのみ見たいためです。

図3 既存のチェック例外を使うコードを作成した結果 図3 既存のチェック例外を使うコードを作成した結果

 exception-basicプロジェクトのsrcフォルダには、以下の2つのソースファイルが作成されています(説明はAIの回答ママ)。

  1. App.java:エントリポイント(try/catchでIOExceptionを捕捉するエントリポイント)
  2. FileReaderService.java:IOExceptionをthrowsするサンプル

 関係としては、例外を投げるクラスが2.にあり、それを捕捉して対応するのは1.というようになっています。それぞれのコードについて見る前に、どのような結果が得られるのか、コンパイルして実行してみましょう。今回は、README.mdファイルに記載されている手順で行ってみます。毎回、手順が提示される場所や、コマンドの書式が変わってくるのが興味深いところです(図4)。

図4 既存のチェック例外を使うコードをコンパイル、実行した結果 図4 既存のチェック例外を使うコードをコンパイル、実行した結果

 例外を捕捉したことを示す「IOException を捕捉しました: ファイルを開けません: data.txt」が表示され、その後に「java.io.IOException: ファイルを開けません: data.txt」などとソースファイル名などが表示されています。これの意味は、続けてコードについて見ていく中で明らかにしていきましょう。

チェック例外を捕捉するコード

 コードの具体的な内容を見る前に、コードについて説明してもらいましょう。これまで通り、「生成されたファイルのコードを、専門用語を使って説明して。コードの説明以外は不要です。」と投げてみました(図5)。

図5 チェック例外のコードの説明 図5 チェック例外のコードの説明

 だいぶ簡潔な説明ですが、これを基に対応するコードに簡単な説明を入れてみました。まずはApp.javaです(図6)。

図6 生成されたApp.javaのコード 図6 生成されたApp.javaのコード

 ここでは、IOExceptionというJavaが標準で用意する例外クラスを使っています。架空のファイル読み込みサービスFileReaderServiceを別ファイルで用意し、そのreadFileメソッドを呼び出しています。注目すべきは、try〜catchというブロックです。これを中心に、以下、ポイントを解説します。

【補足】名前空間の取り込み

 冒頭には、import java.io.IOException;という文があります。これは、java.ioというところにあるIOExceptionを取り込んでくださいという命令です。取り込んだ名前は、最後のIOExceptionだけで使うことができます。詳しくは、後続の「モジュール」の回で解説しますが、ここでは外から定義済みの名前を持ってくると理解しておきましょう。

(1)危険な処理はtryブロックに入れる

 readFileメソッドは、AIの言い方をなぞれば危険な処理(ファイルが見つからない、開けない可能性がある)なので、このような処理はtryブロックの中に入れます。文字通り、メソッドの呼び出しを「トライ」するわけです。やや難しい用語ですが、「同期的例外ハンドリング」というようです。

(2)問題が起きたらcatchブロックに移る

 readFileメソッドが何の問題も起こさなければ、続くSystem.out.printlnメソッドが実行されるはずですが、図4を見ると、「読み取り成功」とは表示されていません。すると、何らかの問題が起きたということで、そのようなときにcatchブロックに移動します。catchとは、「捕まえる」という意味ですね。例外が発生したら、ひとまずcatchブロックに移るわけです。

(3)catchブロックでは捕まえる例外を指定する

 catchブロックには、引数があります。ここに、IOExceptionと書いてあるように、捕まえるのはIOExceptionのみです。図2にあった、「具体的な型を使い、乱暴にExceptionを投げたりcatch(Exception)で握りつぶすのは避ける。」を思い出してください。

 Exceptionは全てのチェック例外の親クラスなので(図7)、catchにそれを書いておけば全ての例外を捕捉できます。しかしながら、さまざまな例外が発生する可能性のあるケースでは、それぞれの例外について適切に対処するのが望ましいのはいうまでもありません。「握りつぶす」という表現には、面倒を避けるなという意図も込められていると理解して、「個別の例外に対応する」コードをしっかり書くべきだと覚えておきましょう。

図7 例外クラスの階層(Microsoft Copilotで生成) 図7 例外クラスの階層(Microsoft Copilotで生成)

【補足】スタックトレース

 catchブロックでは、printStackTraceメソッドが呼び出されています。これはスタックトレースを出力するメソッドで、診断目的で利用されます。図4においてソースファイル名などが表示されている部分が、printStackTraceメソッドによる出力です。どのような呼び出しでエラーに至ったのかを知る材料になりますが、開発者にとっては有用でもユーザーにとっては余計な情報です。このため実用的なアプリケーションでは、catchブロックにはさらに別の例外を投げて呼び出し元に処理を委ねるなどの処理を入れることが多いようです。

チェック例外を投げるコード

 次はFileReaderService.javaです(図8)。

図8 生成されたFileReaderService.javaのコード 図8 生成されたFileReaderService.javaのコード

 ここには、App.javaが利用する架空のファイル読み込みサービスが定義されています。実際に読み込みを行うコードはなく、故意に例外を発生させるコードとなっています。ここも、注目ポイントを中心に見ていきましょう。

(1)throwsで例外を投げることを伝える

 readFileメソッドの定義(シグネチャ)が、これまでと違います。末尾がthrows IOExceptionとなっているのは、このメソッドがIOException例外を投げる可能性のあることを呼び出し元に伝える役割を持っています。やや砕けた言い方をすると、「このメソッドはIOExceptionを投げるかもしれないので、呼び出す側で対処してください」という宣言になります。「例外伝播」というのは、例外が呼び出し元に伝わっていくということを指すようです。

 ここでも、図2にあった、「捕捉するか、throwsが必須」を思い出してください。例外が発生するケースでは、try〜catchで捕捉するか、メソッド定義にthrowsが必須となります。どういう意味かは、次で解説します。

(2)throwで例外を投げる

 readFileメソッドの中身はシンプルで、throw文が書かれているのみです。本来であれば、実際にファイル読み込みのコードを書くべきですが、ここでは単純化のために無条件に例外を発生させているようです。throwは、例外を発生させる文です(throwsと紛らわしいのですが、全く違う役割です)。

 例外はクラスであるので、このようにnewを使ってIOExceptionのインスタンスを生成して、投げているのです。「どこに?」という疑問が湧くのではないでしょうか。これに、メソッド定義のthrowsが関わってきます。

  • メソッド定義にthrowsがあれば、呼び出し元に投げられる(例外が伝播する)
  • throwsがなければ、そのメソッド内で例外を処理する必要があり、try〜catchが必須になる

 App.javaも合わせて見てみると、readFileメソッドはIOExceptionを投げることになっているので、それをmainメソッド(throwsがない)でtry〜catchしたわけです。このように、チェック例外は最終的には必ず誰かが対応することになっています(図9)。

図9 チェック例外 図9 チェック例外

こんな質問をしてみたい!

 ここではIOExceptionのみを扱っていましたが、複数種類の例外が発生するケースもあるはずです。そのような場合にthrowsやtry〜catchをどのように書けばいいのか聞いてみると、例外処理の柔軟性がより理解できるかもしれませんよ。

クリーンアップを含むチェック例外

 ここまでで、チェック例外の基本的なところはだいたいお伝えできたのではないかと思います。しかしながら、図1の解説に登場したキーワードで、finallyについてはまだ言及されていません。この図によると、「必ず実行(クリーンアップ用)」とあります。それがどういうことなのか、新たにプロジェクトを作ってもらい、検証しましょう。

 Agentモードにて、「basicプロジェクトを基にexception-finallyプロジェクトを作り、finallyを使うチェック例外の簡単なサンプルコードを入れて。」と投げて一連の作業を実行してもらいました(図10)。

図10 finallyを使うチェック例外のコードを作成した結果 図10 finallyを使うチェック例外のコードを作成した結果

 exception-finallyプロジェクトのsrcフォルダには、以下の3つのソースファイルが作成されています(説明はAIの回答ママ)。

  1. App.java:FileServiceを呼び出しIOExceptionをcatchするエントリポイント
  2. FileService.java:process()がIOExceptionをthrowsし、finallyでclose()を呼ぶ例
  3. ManualResource.java:手動で開閉するリソースの模擬クラス

 関係としては、例外を発生するクラスが2.と3.にあり、それを捕捉して対応するのは1.と2.というようになっています。exception-basicプロジェクトと同様のコンパイルと実行の手順となっているので、それに沿って実行した様子が図11です。

図11 finallyを使うコードをコンパイル、実行した結果 図11 finallyを使うコードをコンパイル、実行した結果

 例外を捕捉したことを示す「IOException を捕捉しました: 処理中にIOエラーが発生しました」とその後のスタックトレースは、exception-basicプロジェクトと同様です。ここでは、その前にある[ManualResource]から始まる3つの行に注目です。これが意味することは、続けてコードについて見ていく中で明らかにしていきましょう。

チェック例外を捕捉するコード

 コードの具体的な内容を見る前に、コードについて説明してもらいましょう(図12)。

図12 finallyを使うチェック例外のコードの説明 図12 finallyを使うチェック例外のコードの説明

 これを基に対応するコードに簡単な説明を入れてみました。まずはFileService.javaです(図13)。App.javaには目新しい要素は見当たらないので割愛します。

図13 生成されたFileService.javaのコード 図13 生成されたFileService.javaのコード

 このコードを見る前に、架空のリソース処理は、オープン(open)→使う(use)→クローズ(close)の流れになっている必要があることを押さえておきましょう。これは、ファイルやデータベースなどリソース全般に通じる考え方です。つまり、リソースはエラーが発生したからといって、そのままオープンしっぱなしというわけにはいかないということです。

(1)finallyブロックには必ず実行する処理を入れる

 exception-basicプロジェクトと異なるのは、finallyブロックの存在です。このようにfinallyブロックは省略できますが、記述した場合にはエラーの発生にかかわらず必ず実行される処理となります。上記の通り、リソースの利用後には閉じる処理が必須なので、このような処理(closeメソッド)をfinallyブロックに入れることで、安全確実にリソースを閉じる処理を行えるというわけです。文字通り、「最後に」というわけです。

 このようにfinallyを使うと、エラー処理にありがちな処理漏れを防ぐことができ、安全にリソースを利用することができるというわけです(図14)。

図14 finallyのあるチェック例外 図14 finallyのあるチェック例外

 なお、図12には「ディターミニスティック」という聞き慣れない表現がありましたが、「決定的」といった意味があり、実行されることが決定しているというように捉えてよさそうです。

(2)catchブロックは省略できる

 もうひとつ、このコードで注目すべきはcatchブロックがないことです。このような場合にはどのような振る舞いになるのでしょうか。FileService.javaではIllegalStateExceptionというJava標準の非チェック例外が発生する可能性があります。しかし、先ほども触れたように、非チェック例外は「対応しなくても良い例外」です。

 IllegalStateExceptionは「メソッドを使うタイミングを間違っている」という意味で、今回であればリソースが開いていないなどで、まだuseメソッドを呼べない場合に、この例外がスローされることになります。このような例外はプログラム側で修正すべきものなので、例外をキャッチしても意味がないわけですね。捕捉しなければならない例外がない場合には、catchブロックは省略できます。

こんな質問をしてみたい!

 FileService.javaでは、ManualResourceクラスのインスタンス生成をtryブロックの外で行っていました。一連のリソース操作をまとめてtryの中に入れてもよさそうなのに、わざわざ外に置く理由を聞いてみると、try〜catch〜finallyの各ブロックの関連がより理解できるかもしれませんよ。

確実にリソースをクローズするtry-with-resources

 finallyブロックを使うと、エラーの発生にかかわらずリソースを確実に閉じることができることを学習しました。しかしながら、リソースを扱う場合全般にいえることですが、エラーはあらゆる局面で発生する可能性があります。ということは、オープン、読み出し、書き込み、クローズのいずれも例外を発生する可能性があり、それを捕捉する必要があるということです。

 図13のprocessメソッドを見ると、ManualResourceクラスのインスタンス生成で例外が発生してもそれに対応できないばかりか、closeメソッドの呼び出しで仮に例外が出るとしたら、finallyブロックの中にさらにtryブロックを置くなど、定型的な処理であるにもかかわらず義務的に書かなければコードが増えてきます。

 そこで生きてくるのが、図1で登場しているtry-with-resourcesです。try〜catch〜finallyに書き方が似ており、役割も似ていますが、図にもあるように「リソース管理」に適しており、「自動クローズできて安全」です。

 早速例を示してもらいましょう。exception-finallyプロジェクトを基に、try-with-resourcesを使うように改変したプロジェクトexception-resourcesを作成してもらいます。Agentモードにて、「exception-finallyプロジェクトからexception-resourcesプロジェクトを複製し、try-with-resourcesを使うように改変して。」と投げてみました。結果として、exception-resourcesプロジェクトのsrcフォルダには、以下の3つのソースファイルが作成されています(説明はAIの回答ママ)。

  1. AutoResource.java:implements AutoCloseable
  2. FileService.java:try-with-resourcesでAutoResourceとBufferedReaderを管理
  3. App.java:FileService.readFirstLine()を呼びIOExceptionをcatch

 単純な改変を期待しましたが、ManualResourceクラスがAutoResourceクラスとなり、未知のBufferedReaderが使われるなど、FileService.javaにおける処理内容も大きく変わっているようです。AI支援で作業を進めているとこのようなことが起きますが、ここでは結果を受け入れて、解説を試みることにしましょう。

例外を捕捉し対応するコード

 コードの具体的な内容を見る前に、コードについて説明してもらいましょう(図15)。

図15 try-with-resourcesを使うコードの説明 図15 try-with-resourcesを使うコードの説明

 これを基に対応するコードに簡単な説明を入れてみました。まずはFileService.javaです(図16)。ここでも、App.javaには目新しい要素は見当たらないので割愛します。

図16 生成されたFileService.javaのコード 図16 生成されたFileService.javaのコード

 コードを見ると、try〜catch〜finallyと書く場合に比べ、ずいぶんとシンプルなことが分かります。

(1)tryブロックの引数にリソースの初期化文を書く

 これまではtryブロックに引数はありませんでしたが、try-with-resourcesでは引数にリソースの初期化文を書くことになっています。ただし、なんでも書けるわけではなく、

  AutoClosable(自動クローズ)インタフェースが実装されているクラスであること

が条件です。最初のうちは、自らリソースを扱うクラスを書くことは少ないかもしれませんが、try-with-resourcesの前提として覚えておきましょう。

 なお、try-with-resourcesには一度に複数の文を入れても構いません。その場合にはセミコロンで区切ります。この例では、AutoResourceに加えてBufferedReaderというJava標準のリソース(ファイルなどからの読み込みを行うクラス)も加えてくれています。リソースを2つにしたのは、クローズは初期化の逆順で行われることを示すためです。

(2)closeメソッドは明記しない

 コードを見るとcloseメソッドはどこにもありませんが、これはtryブロックから抜けるときに自動的に呼ばれます(コールバック)。これは、エラーが発生しようが、正常に終了しようが、必ず呼ばれます。これにより、リソースのクローズ漏れを防ぐことができるわけですね。この例ではtryブロックの最後にreturn文がありますが、それでも戻る前にcloseメソッドが呼び出されるので安全です。

【補足】抑制された例外

 前節で少し話題にしたクローズ時の例外は、suppressed exception(抑制された例外)として記録されます。これは、tryブロックで例外Aがスローされ、さらにクローズ時に例外Bが発生したときに、BがAに記録されることでBが発生したことの情報が失われないようにする仕組みです。情報は例外クラスのgetSuppressedメソッドで取得できます。

こんな質問をしてみたい!

 ここでは、try-with-resourcesを使った安全なクローズを示してもらいましたが、内部で例外が発生した場合の対応については特に触れられていませんでした。この方法を聞いてみると、try-with-resourcesの挙動に対する理解が、より深まるかもしれませんよ!

まとめ

 今回は、Javaにおけるエラー処理の方法である例外を、チェック例外、非チェック例外の違い、例外の処理方法、例外の発生方法などを、AIに聞きながら理解しました。

 次回は、クラスをまとめる方法としてのパッケージとモジュールを学習します。

筆者紹介

WINGSプロジェクト 山内直

WINGSプロジェクト所属のテクニカルライター。出版社秀和システムを経てフリーランスとして独立。ライター、編集者、開発者、講師業に従事。屋号は「たまデジ。」。

たまデジ。 | たまプラーザで生活、仕事する。(https://naosan.jp/

WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。

サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/
RSS(https://wings.msn.to/contents/rss.php
X: @WingsPro_info(https://x.com/WingsPro_info
Facebook(https://www.facebook.com/WINGSProject


Copyright © ITmedia, Inc. All Rights Reserved.

アイティメディアからのお知らせ

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2026
人に頼れない今こそ、本音で語るセキュリティ「モダナイズ」
4AI by @IT - AIを作り、動かし、守り、生かす
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。