1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向って何?RPGで理解する超入門 Part2 (例外処理)

1
Last updated at Posted at 2026-01-03

part2_episode02_title_banner.png

📚 この回で学ぶこと

学習時間: 約35分
難易度: ★★★☆☆(3/5)

学習目標

前回作成したセーブ・ロード機能を エラーに強いシステム に改良します!

第1回の問題点:

// 第1回のコード
Player player = SaveManager.load("save_slot1.txt");
// ⚠️ ファイルが存在しない場合、プログラムがクラッシュ!

第2回で解決

// エラー処理を追加
try {
    Player player = SaveManager.load("save_slot1.txt");
    System.out.println("✅ ロード成功!");
} catch (FileNotFoundException e) {
    System.out.println("❌ セーブデータが見つかりません。");
} catch (DataCorruptedException e) {
    System.out.println("⚠️ データが破損しています。");
}

📁 成果物のファイル構成

この回で作成する例外処理対応システムのファイル構成

src/
├── com/javaquest/
│   ├── app/
│   │   └── Main.java                    # メインテストクラス
│   ├── controller/
│   │   └── GameController.java          # ゲームコントローラー
│   ├── exception/
│   │   ├── DataCorruptedException.java  # データ破損例外
│   │   ├── InvalidLevelException.java   # 無効レベル例外
│   │   └── SaveLoadException.java       # セーブ・ロード例外
│   ├── manager/
│   │   └── SaveManager.java             # セーブ・ロードマネージャー
│   └── model/
│       └── Player.java                  # プレイヤーモデル
├── example/
│   └── FileReadExample.java             # ファイル読み込み例
├── practice/
│   ├── Practice01.java                  # NumberFormatException練習
│   └── Practice02.java                  # try-with-resources練習
└── test/
    ├── GameControllerTest.java          # GameControllerテスト
    └── InvalidLevelTest.java            # InvalidLevelExceptionテスト

パッケージ構成の意味:

  • com.javaquest.model → データクラス(Player等)
  • com.javaquest.exception → カスタム例外クラス
  • com.javaquest.manager → 管理クラス(SaveManager等)
  • com.javaquest → メインシステム
  • practice → 練習問題用ファイル

📚GitHub:成果物のサンプルはこちら

この回で身につくスキル

例外処理の基本 - try-catch-finally構文
例外の種類 - Checked例外とUnchecked例外
カスタム例外 - 独自の例外クラス設計
実務スキル - ユーザーフレンドリーなエラーメッセージ

基礎知識

1. 例外処理とは?

例外(Exception) = プログラム実行中に発生する 予期しないエラー

exception_handling_concept.png

❌ 例外処理がない場合

// ファイルが存在しない
FileReader reader = new FileReader("存在しないファイル.txt");
// 💥 プログラムがクラッシュ!
// Exception in thread "main" java.io.FileNotFoundException

結果: プログラムが 強制終了

✅ 例外処理がある場合

try {
    FileReader reader = new FileReader("存在しないファイル.txt");
    System.out.println("ファイルを読み込みました");
} catch (FileNotFoundException e) {
    System.out.println("❌ ファイルが見つかりません");
}

System.out.println("プログラムは続行します!");

結果: エラーメッセージを表示して プログラムは継続

2. try-catch-finally構文

try_catch_finally_flow.png

基本構文

try {
    // ①実行したい処理(例外が発生する可能性がある)
    
} catch (例外クラス e) {
    // ②例外が発生したときの処理
    
} finally {
    // ③必ず実行される処理(例外の有無に関わらず)
}

実行フロー

[正常時]
① try → ③ finally → 次の処理

[例外発生時]
① try(途中で中断)→ ② catch → ③ finally → 次の処理

実践例:ファイル読み込み(最新の書き方)

try-with-resources(Java 7以降の推奨書き方)

import java.io.*;

public class FileReadExample {
    public static void main(String[] args) {
        
        // try-with-resources: リソースを自動でクローズ
        try (BufferedReader reader = new BufferedReader(
                new FileReader("save_data.txt"))) {
            
            // ① ファイルを読み込む
            String line = reader.readLine();
            System.out.println("読み込んだデータ: " + line);
            // reader.close() は自動で呼ばれる!
            
        } catch (FileNotFoundException e) {
            // ② ファイルが見つからない
            System.out.println("ファイルが見つかりません");
            
        } catch (IOException e) {
            // ③ 読み込みエラー
            System.out.println("読み込みエラー");
        }
        // finally不要!try-with-resourcesが自動でクローズ
    }
}

try-with-resources の仕組み

メリット:

  • 自動クローズclose() を明示的に呼ぶ必要なし
  • コードが短い → finally ブロックが不要
  • 例外安全 → クローズ処理で例外が出ても安全

使用条件:

  • AutoCloseable インターフェースを実装したクラス
  • 例:BufferedReader, FileWriter, Scanner, Connection

構文:

try (リソース1; リソース2; ...) {
    // リソースを使った処理
} catch (例外クラス e) {
    // 例外処理
}
// リソースは自動的にクローズされる

3. 例外の種類

exception_types_comparison.png

Javaの例外は 2種類 に分類されます。

Checked例外(検査例外)

特徴

  • コンパイル時にチェックされる
  • 必ず try-catch で処理する必要がある
  • ファイル操作、ネットワーク処理など

代表的な例外

// FileNotFoundException
FileReader reader = new FileReader("file.txt"); // ❌ コンパイルエラー

// IOException
reader.readLine(); // ❌ コンパイルエラー

対処法: try-catch または throws で処理

// 方法1: try-catch
try {
    FileReader reader = new FileReader("file.txt");
} catch (FileNotFoundException e) {
    // 処理
}

// 方法2: throws(呼び出し元に処理を委譲)
public void readFile() throws FileNotFoundException {
    FileReader reader = new FileReader("file.txt");
}

⚡ Unchecked例外(非検査例外)

特徴

  • 実行時に発生
  • try-catchは 任意(必須ではない)
  • プログラムのバグに起因することが多い

代表的な例外

// NullPointerException
String text = null;
text.length(); // 💥 実行時エラー

// ArrayIndexOutOfBoundsException
int[] numbers = {1, 2, 3};
numbers[10]; // 💥 実行時エラー

// NumberFormatException
Integer.parseInt("abc"); // 💥 実行時エラー

例外の継承関係

Throwable(すべての例外の親)
├─ Error(システムエラー、処理不要)
│   └─ OutOfMemoryError
│
└─ Exception(例外の基底クラス)
    ├─ IOException(Checked例外)
    │   ├─ FileNotFoundException
    │   └─ EOFException
    │
    └─ RuntimeException(Unchecked例外)
        ├─ NullPointerException
        ├─ ArrayIndexOutOfBoundsException
        └─ NumberFormatException

4. カスタム例外の作成

独自の例外クラスを作成することで、意味のあるエラー を表現できます。

例:データ破損例外

// カスタム例外クラス
public class DataCorruptedException extends Exception {
    
    // コンストラクタ1: メッセージなし
    public DataCorruptedException() {
        super("データが破損しています");
    }
    
    // コンストラクタ2: カスタムメッセージ
    public DataCorruptedException(String message) {
        super(message);
    }
    
    // コンストラクタ3: 原因の例外を含む
    public DataCorruptedException(String message, Throwable cause) {
        super(message, cause);
    }
}

カスタム例外の設計ポイント

Exceptionを継承 → Checked例外(処理が必須)
RuntimeExceptionを継承 → Unchecked例外(処理は任意)

// Checked例外(推奨:ファイル破損など)
public class DataCorruptedException extends Exception { }

// Unchecked例外(ロジックエラー用)
public class InvalidPlayerDataException extends RuntimeException { }

実装演習

Step 1: Playerクラスを作成

第1回の Playerクラス をそのまま使用

Step 2: カスタム例外クラスを作成

第1回の SaveManager に対応する 2つの例外クラス を作成します。

DataCorruptedException.java

DataCorruptedException.java
/**
 * データ破損例外
 * セーブデータの形式が不正な場合にスローされる
 */
public class DataCorruptedException extends Exception {
    
    public DataCorruptedException() {
        super("セーブデータが破損しています");
    }
    
    public DataCorruptedException(String message) {
        super(message);
    }
    
    public DataCorruptedException(String message, Throwable cause) {
        super(message, cause);
    }
}

SaveLoadException.java

SaveLoadException.java
/**
 * セーブ・ロード汎用例外
 * セーブ・ロード処理全般のエラーを表す
 */
public class SaveLoadException extends Exception {
    
    public SaveLoadException(String message) {
        super(message);
    }
    
    public SaveLoadException(String message, Throwable cause) {
        super(message, cause);
    }
}

Step 3: SaveManager に例外処理を追加

第1回の SaveManager を改良します。

改良版 SaveManager.java

SaveManager.java
package com.javaquest.manager;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import com.javaquest.exception.DataCorruptedException;
import com.javaquest.exception.SaveLoadException;
import com.javaquest.model.Player;

/**
 * プレイヤーデータのセーブ・ロード機能を提供するマネージャークラス
 * <p>
 * このクラスはプレイヤーの状態をファイルに保存し、
 * 保存されたデータを読み込む機能を提供します。
 * データの整合性チェックと適切な例外処理を行います。
 * </p>
 * 
 * @author JavaQuest
 * @version 1.0
 */
public class SaveManager {
    
    /**
     * プレイヤーデータをファイルに保存します
     * <p>
     * プレイヤーの名前、HP、最大HP、レベルを指定されたファイルに
     * テキスト形式で保存します。各データは改行で区切られます。
     * </p>
     * 
     * @param player 保存するプレイヤーオブジェクト
     * @param fileName 保存先のファイル名
     * @throws SaveLoadException ファイルの書き込みに失敗した場合
     */
    public static void save(Player player, String fileName) 
            throws SaveLoadException {
        
        try (BufferedWriter writer = new BufferedWriter(
                new FileWriter(fileName))) {
            
            // データを1行ずつ書き込み
            writer.write(player.getName());
            writer.newLine();
            writer.write(String.valueOf(player.getHp()));
            writer.newLine();
            writer.write(String.valueOf(player.getMaxHp()));
            writer.newLine();
            writer.write(String.valueOf(player.getLevel()));
            writer.newLine();
            
            System.out.println("セーブ完了: " + fileName);
            
        } catch (IOException e) {
            // IOExceptionをラップして再スロー
            throw new SaveLoadException(
                "セーブに失敗しました: " + fileName, e);
        }
    }
    
    /**
     * ファイルからプレイヤーデータを読み込みます
     * <p>
     * 指定されたファイルからプレイヤーデータを読み込み、
     * データの妥当性をチェックしてPlayerオブジェクトを作成します。
     * データが不正な場合は適切な例外をスローします。
     * </p>
     * 
     * @param fileName 読み込むファイル名
     * @return 読み込んだプレイヤーデータを含むPlayerオブジェクト
     * @throws FileNotFoundException 指定されたファイルが存在しない場合
     * @throws DataCorruptedException データ形式が不正または値が無効な場合
     * @throws SaveLoadException ファイルの読み込みに失敗した場合
     */
    public static Player load(String fileName) 
            throws FileNotFoundException, DataCorruptedException, SaveLoadException {
        
        try (BufferedReader reader = new BufferedReader(
                new FileReader(fileName))) {
            
            // データを1行ずつ読み込み
            String name = reader.readLine();
            String hpStr = reader.readLine();
            String maxHpStr = reader.readLine();
            String levelStr = reader.readLine();
            
            // データ検証
            if (name == null || hpStr == null || 
                maxHpStr == null || levelStr == null) {
                throw new DataCorruptedException(
                    "データが不完全です: " + fileName);
            }
            
            try {
                // 整数値に変換
                int hp = Integer.parseInt(hpStr);
                int maxHp = Integer.parseInt(maxHpStr);
                int level = Integer.parseInt(levelStr);
                
                // 値の妥当性チェック
                if (hp < 0 || maxHp < 0 || level < 0) {
                    throw new DataCorruptedException(
                        "データに負の値が含まれています");
                }
                
                if (hp > maxHp) {
                    throw new DataCorruptedException(
                        "HPがMaxHPを超えています");
                }
                
                // Playerオブジェクトを作成
                Player player = new Player(name, maxHp, level);
                player.setHp(hp);
                
                System.out.println("ロード完了: " + fileName);
                return player;
                
            } catch (NumberFormatException e) {
                // 数値変換エラー
                throw new DataCorruptedException(
                    "数値データが不正です", e);
            }
            
        } catch (FileNotFoundException e) {
            // ファイル未検出は再スロー
            throw e;
            
        } catch (IOException e) {
            // その他のIOエラーをラップ
            throw new SaveLoadException(
                "ロードに失敗しました: " + fileName, e);
        }
    }
}

Step 4: 例外処理のテスト

Main.java(テスト用)

main.java
package com.javaquest.app;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

import com.javaquest.exception.DataCorruptedException;
import com.javaquest.exception.SaveLoadException;
import com.javaquest.manager.SaveManager;
import com.javaquest.model.Player;

/**
 * 例外処理システムのメインクラス
 * <p>
 * SaveManagerクラスの例外処理機能を検証するためのテストプログラムです。
 * 正常なセーブ・ロード、ファイル未検出、データ破損の3つのシナリオをテストします。
 * </p>
 * 
 * @author JavaQuest
 * @version 1.0
 */
public class Main {
    /**
     * プログラムのエントリーポイント
     * <p>
     * 例外処理システムの動作を検証するため、以下の3つのテストを実行します:
     * 1. 正常なセーブ・ロード処理
     * 2. 存在しないファイルの読み込み(FileNotFoundException)
     * 3. 破損したデータの読み込み(DataCorruptedException)
     * </p>
     * 
     * @param args コマンドライン引数(使用しません)
     */
    public static void main(String[] args) {
        
        System.out.println("=== 例外処理のテスト ===\n");
        
        // テスト1: 正常なセーブ・ロード
        testNormalSaveLoad();
        
        // テスト2: ファイルが存在しない
        testFileNotFound();
        
        // テスト3: データ破損
        testDataCorrupted();
    }
    
    /**
     * 正常なセーブ・ロード処理のテスト
     * <p>
     * プレイヤーオブジェクトを作成してファイルに保存し、
     * 同じファイルから正常に読み込めることを確認します。
     * </p>
     */
    private static void testNormalSaveLoad() {
        System.out.println("【テスト1】正常なセーブ・ロード");
        
        try {
            // プレイヤー作成
            Player player = new Player("勇者アレン", 120, 5);
            System.out.println("セーブ前: " + player.getName() + 
                             " HP:" + player.getHp() + "/" + player.getMaxHp());
            
            // セーブ
            SaveManager.save(player, "data\\save_test1.txt");
            
            // ロード
            Player loadedPlayer = SaveManager.load("data\\save_test1.txt");
            System.out.println("ロード後: " + loadedPlayer.getName() + 
                             " HP:" + loadedPlayer.getHp() + "/" + loadedPlayer.getMaxHp());
            
            System.out.println("テスト1成功\n");
            
        } catch (Exception e) {
            System.out.println("テスト1失敗: " + e.getMessage());
        }
    }
    
    /**
     * ファイル未検出例外のテスト
     * <p>
     * 存在しないファイルを読み込もうとした際に、
     * FileNotFoundExceptionが適切にスローされることを確認します。
     * </p>
     */
    private static void testFileNotFound() {
        System.out.println("【テスト2】ファイルが存在しない");
        
        try {
            Player player = SaveManager.load("存在しないファイル.txt");
            System.out.println("例外が発生しませんでした");
            
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFoundExceptionをキャッチ");
            System.out.println("   メッセージ: " + e.getMessage());
            
        } catch (DataCorruptedException e) {
            System.out.println("想定外の例外");
            
        } catch (SaveLoadException e) {
            System.out.println("想定外の例外");
        }
        
        System.out.println();
    }
    
    /**
     * データ破損例外のテスト
     * <p>
     * 意図的に破損したデータファイルを作成し、読み込み時に
     * DataCorruptedExceptionが適切にスローされることを確認します。
     * 数値フィールドに文字列を含むファイルを作成してテストします。
     * </p>
     */
    private static void testDataCorrupted() {
        System.out.println("【テスト3】データ破損");
        
        // 破損したファイルを作成(try-with-resources使用)
        try (BufferedWriter writer = new BufferedWriter( new FileWriter("data\\save_corrupted.txt"))) {
            writer.write("破損した勇者\n");
            writer.write("ABC\n");  // ← 数値ではない!
            writer.write("100\n");
            writer.write("5\n");
            // writer は自動的にクローズされる
        } catch (IOException e) {
            System.out.println("破損ファイル作成失敗");
            return;
        }
        
        try {
            Player player = SaveManager.load("data\\save_corrupted.txt");
            System.out.println("例外が発生しませんでした");
            
        } catch (FileNotFoundException e) {
            System.out.println("想定外の例外");
            
        } catch (DataCorruptedException e) {
            System.out.println("DataCorruptedExceptionをキャッチ");
            System.out.println("   メッセージ: " + e.getMessage());
            
        } catch (SaveLoadException e) {
            System.out.println("想定外の例外");
        }
        
        System.out.println();
    }
}

実行結果

=== 例外処理のテスト ===

【テスト1】正常なセーブ・ロード
セーブ前: 勇者アレン HP:120/120
セーブ完了: data\save_test1.txt
ロード完了: data\save_test1.txt
ロード後: 勇者アレン HP:120/120
テスト1成功

【テスト2】ファイルが存在しない
FileNotFoundExceptionをキャッチ
   メッセージ: 存在しないファイル.txt (指定されたファイルが見つかりません。)

【テスト3】データ破損
DataCorruptedExceptionをキャッチ
   メッセージ: 数値データが不正です

Step 5: ユーザーフレンドリーなエラーメッセージ

実際のゲームでは、プレイヤーに分かりやすいメッセージ を表示します。

GameController.java(例)

package com.javaquest.controller;

import java.io.FileNotFoundException;

import com.javaquest.exception.DataCorruptedException;
import com.javaquest.exception.SaveLoadException;
import com.javaquest.manager.SaveManager;
import com.javaquest.model.Player;

/**
 * ゲームのセーブ・ロード機能を管理するコントローラークラス
 * <p>
 * プレイヤーデータのセーブスロット管理と、ユーザーフレンドリーな
 * エラーメッセージ表示を提供します。SaveManagerクラスをラップし、
 * 例外処理を行って適切なフィードバックをユーザーに提供します。
 * </p>
 * 
 * @author JavaQuest
 * @version 1.0
 */
public class GameController {
    
    /**
     * 指定されたセーブスロットからプレイヤーデータを読み込みます
     * <p>
     * セーブスロット番号に基づいてファイル名を生成し、SaveManagerを使用して
     * プレイヤーデータを読み込みます。エラーが発生した場合は、ユーザーに
     * 分かりやすいメッセージを表示し、nullを返します。
     * </p>
     * 
     * @param slotNumber セーブスロット番号(1以上の整数)
     * @return 読み込みに成功した場合はPlayerオブジェクト、失敗した場合はnull
     */
    public Player loadGame(int slotNumber) {
        String fileName = "data\\save_slot" + slotNumber + ".txt";
        
        try {
            Player player = SaveManager.load(fileName);
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ✅ ロードに成功しました   ║");
            System.out.println("╚════════════════════════════╝");
            return player;
            
        } catch (FileNotFoundException e) {
            // ファイルが存在しない
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ❌ セーブデータが         ║");
            System.out.println("║     見つかりません         ║");
            System.out.println("╚════════════════════════════╝");
            System.out.println("💡 ヒント: 新しいゲームを始めてください");
            return null;
            
        } catch (DataCorruptedException e) {
            // データ破損
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ⚠️  セーブデータが         ║");
            System.out.println("║      破損しています         ║");
            System.out.println("╚════════════════════════════╝");
            System.out.println("💡 ヒント: 別のスロットを選択するか、");
            System.out.println("          新しいゲームを始めてください");
            return null;
            
        } catch (SaveLoadException e) {
            // その他のエラー
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ❌ ロードに失敗しました    ║");
            System.out.println("╚════════════════════════════╝");
            System.out.println("⚠️  エラー詳細: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * 指定されたセーブスロットにプレイヤーデータを保存します
     * <p>
     * セーブスロット番号に基づいてファイル名を生成し、SaveManagerを使用して
     * プレイヤーデータを保存します。エラーが発生した場合は、ユーザーに
     * 分かりやすいメッセージを表示し、falseを返します。
     * </p>
     * 
     * @param player 保存するプレイヤーオブジェクト
     * @param slotNumber セーブスロット番号(1以上の整数)
     * @return 保存に成功した場合はtrue、失敗した場合はfalse
     */
    public boolean saveGame(Player player, int slotNumber) {
        String fileName = "data\\save_slot" + slotNumber + ".txt";
        
        try {
            SaveManager.save(player, fileName);
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ✅ セーブに成功しました    ║");
            System.out.println("╚════════════════════════════╝");
            return true;
            
        } catch (SaveLoadException e) {
            System.out.println("╔════════════════════════════╗");
            System.out.println("║  ❌ セーブに失敗しました    ║");
            System.out.println("╚════════════════════════════╝");
            System.out.println("⚠️  エラー詳細: " + e.getMessage());
            return false;
        }
    }
}

動作確認クラス作成

GameControllerTest.java
package test;

import com.javaquest.controller.GameController;
import com.javaquest.model.Player;

/**
 * GameControllerの動作検証テストクラス
 * セーブ・ロード機能と例外処理をテストします。
 */
public class GameControllerTest {
    
    /**
     * GameControllerの動作テストを実行します
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        GameController controller = new GameController();
        
        // テスト用プレイヤー作成
        Player testPlayer = new Player("テストプレイヤー", 100, 5);
        testPlayer.setHp(80);
        
        System.out.println("=== GameController動作検証 ===\n");
        
        // 1. セーブテスト
        System.out.println("1. セーブテスト");
        boolean saveResult = controller.saveGame(testPlayer, 1);
        System.out.println("セーブ結果: " + (saveResult ? "成功" : "失敗"));
        System.out.println();
        
        // 2. ロードテスト(正常)
        System.out.println("2. ロードテスト(正常)");
        Player loadedPlayer = controller.loadGame(1);
        if (loadedPlayer != null) {
            System.out.println("ロードしたプレイヤー: " + loadedPlayer.getName());
            System.out.println("HP: " + loadedPlayer.getHp() + "/" + loadedPlayer.getMaxHp());
            System.out.println("レベル: " + loadedPlayer.getLevel());
        }
        System.out.println();
        
        // 3. ロードテスト(存在しないファイル)
        System.out.println("3. ロードテスト(存在しないファイル)");
        Player notFoundPlayer = controller.loadGame(99);
        System.out.println("結果: " + (notFoundPlayer == null ? "適切にnullが返された" : "予期しない結果"));
        System.out.println();
        
        System.out.println("=== 検証完了 ===");
    }
}

実行結果

=== GameController動作検証 ===

1. セーブテスト
セーブ完了: data\save_slot1.txt
╔════════════════════════════╗
║  ✅ セーブに成功しました    ║
╚════════════════════════════╝
セーブ結果: 成功

2. ロードテスト(正常)
ロード完了: data\save_slot1.txt
╔════════════════════════════╗
║  ✅ ロードに成功しました   ║
╚════════════════════════════╝
ロードしたプレイヤー: テストプレイヤー
HP: 80/100
レベル: 5

3. ロードテスト(存在しないファイル)
╔════════════════════════════╗
║  ❌ セーブデータが         ║
║     見つかりません         ║
╚════════════════════════════╝
💡 ヒント: 新しいゲームを始めてください
結果: 適切にnullが返された

=== 検証完了 ===

練習問題

練習1: 複数のcatch

問題: 以下のコードに適切な例外処理を追加してください。

public class Practice01 {
    public static void main(String[] args) {
        String[] data = {"100", "abc", "200"};
        
        for (String str : data) {
            // TODO: 例外処理を追加
            int number = Integer.parseInt(str);
            System.out.println("数値: " + number);
        }
    }
}
解答例
Practice01.java
package practice;

/**
 * NumberFormatExceptionの例外処理練習クラス
 * 文字列の数値変換時の例外ハンドリングを学習します。
 */
public class Practice01 {
    
    /**
     * 数値変換の例外処理テストを実行します
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        String[] data = {"100", "abc", "200"};
        
        for (String str : data) {
            try {
                int number = Integer.parseInt(str);
                System.out.println("数値: " + number);
                
            } catch (NumberFormatException e) {
                System.out.println("数値に変換できません: " + str);
            }
        }
    }
}

実行結果:

数値: 100
数値に変換できません: abc
数値: 200

練習2: try-with-resources で書き換え

問題: 以下の古い書き方を try-with-resources を使った最新の書き方に書き換えてください。

// ❌ 古い書き方(Java 6以前)
public class Practice02 {
    public static void main(String[] args) {
        BufferedReader reader = null;
        
        try {
            reader = new BufferedReader(new FileReader("test.txt"));
            String line = reader.readLine();
            System.out.println(line);
            
        } catch (IOException e) {
            System.out.println("エラー: " + e.getMessage());
            
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                System.out.println("クローズエラー");
            }
        }
    }
}

TODO: try-with-resources を使って簡潔に書き換えてください!

解答例
package practice;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * try-with-resources文の練習クラス
 * ファイル読み込みとリソースの自動クローズを学習します。
 */
public class Practice02 {
    
    /**
     * try-with-resources文を使用したファイル読み込みテストを実行します
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        
        // try-with-resources: 自動クローズ
        try (BufferedReader reader = new BufferedReader( new FileReader("data\\test.txt"))) {
            
            String line = reader.readLine();
            System.out.println(line);
            // reader.close() は自動で呼ばれる!
            
        } catch (IOException e) {
            System.out.println("エラー: " + e.getMessage());
        }
        // finally 不要!
    }
}

実行結果

数値: 100
数値に変換できません: abc
数値: 200

改善点:

  • finally ブロックが不要
  • コードが約40%短くなった
  • クローズ忘れの心配なし
  • 例外処理がシンプル

練習3: カスタム例外の作成

問題: InvalidLevelException を作成し、レベルが1未満の場合に例外をスローするメソッドを実装してください。

解答例

カスタム例外クラス

InvalidLevelException.java
package com.javaquest.exception;

/**
 * 無効なレベル値を示すカスタム例外クラス
 * <p>
 * プレイヤーのレベルが不正な値(例:負の値、上限を超えた値)に
 * 設定されようとした場合にスローされます。
 * ゲームのビジネスロジックにおけるレベルの制約を強制するために使用します。
 * </p>
 * 
 * @author JavaQuest
 * @version 1.0
 * @see com.javaquest.model.Player
 */
public class InvalidLevelException extends Exception {
    /**
     * 指定されたメッセージでInvalidLevelExceptionを作成します
     * <p>
     * 無効なレベル値の詳細や、期待される値の範囲を示すメッセージを
     * 指定して例外を作成します。
     * </p>
     * 
     * @param message 例外の詳細メッセージ(無効なレベル値と期待値の情報を含む)
     */
    public InvalidLevelException(String message) {
        super(message);
    }
}

Playerクラスにコンストラクタを追加

// Playerクラス
public class Player {
    private String name;
    private int level;
    
    public Player(String name, int level) throws InvalidLevelException {
        if (level < 1) {
            throw new InvalidLevelException(
                "レベルは1以上である必要があります: " + level);
        }
        this.name = name;
        this.level = level;
    }
    
    // getter/setter...
}

テストクラス

InvalidLevelTest.java
public class InvalidLevelTest {
    public static void main(String[] args) {
        try {
            Player player1 = new Player("勇者", 5);
            System.out.println("player1作成成功");
            
            Player player2 = new Player("魔王", -1);
            System.out.println("この行は実行されません");
            
        } catch (InvalidLevelException e) {
            System.out.println("エラー: " + e.getMessage());
        }
    }
}

実行結果:

player1作成成功
エラー: レベルは1以上である必要があります: -1

まとめ

今回学んだこと

1. 例外処理の基本

try {
    // 例外が発生する可能性のある処理
} catch (例外クラス e) {
    // 例外発生時の処理
} finally {
    // 必ず実行される処理
}

2. 例外の種類

種類 チェック 処理
Checked例外 コンパイル時 必須 IOException, FileNotFoundException
Unchecked例外 実行時 任意 NullPointerException, NumberFormatException

3. カスタム例外

// Checked例外
public class DataCorruptedException extends Exception {
    public DataCorruptedException(String message) {
        super(message);
    }
}

// 使用例
throw new DataCorruptedException("データが破損しています");

4. 実務的なポイント

適切な例外をキャッチ → 想定されるエラーごとに分岐
ユーザーフレンドリーなメッセージ → 技術用語を避ける
finallyでリソース解放 → ファイル、接続など
例外の再スロー(委譲) → 上位層で処理する場合


次回予告

第3回:コレクションで複数のデータを管理しよう!

// 第2回までの問題点
Player[] players = new Player[10];  // ← サイズ固定!

// 第3回で解決
List<Player> players = new ArrayList<>();  // ← 可変サイズ!
players.add(new Player("勇者", 100, 1));
players.add(new Player("戦士", 150, 2));

学習内容:

  • ArrayList/HashMap の使い方
  • パーティメンバー管理システム
  • アイテムインベントリの実装

📚 参考資料

🔗 関連記事


© 2026 Java Quest - オブジェクト指向って何?RPGで理解する超入門 Part2
All Rights Reserved.

本記事は教育目的で作成されています。RPGの例を通じてプログラミング概念を楽しく学習できることを目指しています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?