Java | Java 標準ライブラリ:StackTrace の読み方

Java Java
スポンサーリンク

StackTrace を一言でいうと

StackTrace(スタックトレース)は、

「例外が発生したときに、そのときの『呼び出し履歴』を上から順に全部並べたもの」

です。

どのクラスの、どのメソッドの、どの行で、
どういう順番で呼ばれて、最後どこで爆発したのか。

これが全部書いてあります。

StackTrace を読めるようになると、

「とりあえず動かない」状態から
「ここが原因だ」とピンポイントで突き止めるところまで、一気に近づきます。


まずは形に慣れる:典型的な StackTrace の例

代表的な例を眺めてみる

例えば、こんなコードを動かしたとします。

public class Main {
    public static void main(String[] args) {
        a();
    }

    static void a() {
        b();
    }

    static void b() {
        String s = null;
        System.out.println(s.length());  // ここで NullPointerException
    }
}
Java

実行すると、コンソールにはこんな感じの StackTrace が出ます。

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
    at Main.b(Main.java:13)
    at Main.a(Main.java:9)
    at Main.main(Main.java:5)
Java

この 3 行の「at ~」で、呼び出しの階段が逆順に並んでいます。

一番上(最初の at)が「例外が実際に発生した場所」。
その下に「そこを呼び出したメソッド」たちが続きます。

ざっくり読む順番

StackTrace を読むときの基本は、こうです。

いちばん上の「Exception ~」行
→ 例外の種類とメッセージ

最初の at ~ 行
→ 実際に爆発した箇所(真犯人)

その下の at ~ 行
→ 「爆発箇所までどうたどり着いたか」の道筋

まずこの 2 段階だけでいいので、順番に目で追う癖をつけてください。


一行を分解して読む:at Main.b(Main.java:13) の意味

at クラス名.メソッド名(ファイル名:行番号)

StackTrace の 1 行には、ほぼ必ずこの情報が入っています。

at Main.b(Main.java:13)

これは分解すると、

クラス名:Main
メソッド名:b
ソースファイル:Main.java
行番号:13

という意味です。

実際のコードを見返すと、

static void b() {
    String s = null;
    System.out.println(s.length());  // ← これが 13 行目だとする
}
Java

まさに「ここでやらかしている」と分かります。

IDE(IntelliJ や Eclipse)では、
StackTrace のこの部分をクリックすると、その行にジャンプできます。

迷わず「最初の at 行の file:line」を押さえる癖をつけると、デバッグが一気に楽になります。

上から下が「呼び出しの逆順」

先ほどの StackTrace はこうでした。

at Main.b(Main.java:13)
at Main.a(Main.java:9)
at Main.main(Main.java:5)
Java

これは、

main が a を呼んだ
a が b を呼んだ
b の中で例外が起きた

という順番を、逆から並べている状態です。

イメージとしては、

一番上:最後に入った関数(現場)
一番下:最初に呼ばれた関数(スタート地点)

という構造です。


例外の「種類」と「メッセージ」をしっかり読む

例外名がまずヒントになる

StackTrace の一番上には、だいたいこういう行があります。

java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
Java

ここには、

例外クラス名:java.lang.NullPointerException
メッセージ:Cannot invoke "String.length()" because "s" is null

が書かれています。

例外クラス名だけ見ても、かなり情報があります。

NullPointerException
→ null に対してメソッド呼び出しやフィールドアクセスをしている

ArrayIndexOutOfBoundsException
→ 配列の範囲外のインデックスにアクセスしている

IllegalArgumentException
→ メソッドの引数が不正(仕様違反)

NumberFormatException
→ 文字列を数値に変換しようとして失敗

例外名だけで「何系のミスか」が見えてきます。

メッセージは「もう一歩踏み込んだ説明」

Java 14 以降の NullPointerException などは、メッセージがだいぶ親切です。

Cannot invoke "String.length()" because "s" is null

s が null だから length() を呼べない」と、
「どの変数が null だったか」まで教えてくれます。

ライブラリが投げてくる例外も、
たいていはメッセージに「具体的に何がダメだったか」を書いてくれます。

StackTrace を読むときは、

例外クラス名で “系統” をつかむ
メッセージで “具体的な事情” をつかむ

の二段構えで読み取ってください。


実戦パターンその1:ネストした呼び出しの中での NullPointerException

例のコード

public class A {
    B b;
    void doSomething() {
        b.doWork();
    }
}

public class B {
    void doWork() {
        System.out.println("working");
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new A();      // a.b は null のまま
        a.doSomething();    // ここで NPE
    }
}
Java

実行すると、たとえばこんな StackTrace。

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "B.doWork()" because "this.b" is null
    at A.doSomething(A.java:4)
    at Main.main(Main.java:7)
Java

読み方

一番上の at 行:

at A.doSomething(A.java:4)

A クラスの doSomething メソッド、A.java の 4 行目で NullPointerException。
そこを開くと

b.doWork();
Java

となっている。
メッセージは

because "this.b" is null

つまり、this.b が null。

ここまでくると、原因はかなりクリアですよね。

A のコンストラクタで b を初期化していない
あるいは setter などが呼ばれていない

という、オブジェクト生成の段階のバグです。

StackTrace を読むときにやっていることは、

爆発行を特定
メッセージから「どの変数が何状態」かを確認
そこに至るまでの初期化や代入の流れを疑う

という 3 ステップです。


実戦パターンその2:他クラスやライブラリ経由での例外

例のコード

ファイルから数値を読み取って合計するメソッドを考えます。

import java.io.IOException;
import java.nio.file.*;
import java.util.List;

public class FileSum {
    public static int sum(String fileName) throws IOException {
        Path path = Path.of(fileName);
        List<String> lines = Files.readAllLines(path);  // IOException の可能性
        int sum = 0;
        for (String line : lines) {
            sum += Integer.parseInt(line);  // NumberFormatException の可能性
        }
        return sum;
    }
}
Java

これを次のように呼び出すとします。

public class Main {
    public static void main(String[] args) throws Exception {
        int result = FileSum.sum("numbers.txt");
        System.out.println("合計 = " + result);
    }
}
Java

numbers.txt に数字以外の行が混じっていた場合の StackTrace を見てみましょう。

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
    at java.base/java.lang.Integer.parseInt(Integer.java:668)
    at java.base/java.lang.Integer.parseInt(Integer.java:786)
    at FileSum.sum(FileSum.java:12)
    at Main.main(Main.java:4)
Java

読み方のコツ

まず例外名とメッセージ。

NumberFormatException: For input string: "abc"

「”abc” を数値に変換しようとして失敗した」ことがわかる。

次に、自分のコードに関係する行を探します。

上から数行は Java ランタイム内部(java.lang.Integer など)なので、
直接は修正できません。

そこを過ぎると、

at FileSum.sum(FileSum.java:12)

が自分のコード。
FileSum.java の 12 行目を開くと、

sum += Integer.parseInt(line);
Java

「数値にしたいはずの line に “abc” が入っていた」のが原因だと分かります。

あとは、lines の元ファイルの中身を確認する、
あるいは「空行やコメント行をスキップする」などの防御コードを書く、といった改善に進めます。

ライブラリ内部の行は「ヒント」程度に眺めて、
最終的には「自分のコード内で最も上にある at 行」に注目する、
という読み方を覚えておいてください。


よくある「StackTrace 読みの落とし穴」

先頭の 1 行だけ見て諦める

java.lang.NullPointerException という文字だけ見て、「あー NPE か…」で終わってしまうパターンは非常にもったいないです。

必ず、

例外名
メッセージ
最初の at 行(file:line)

の 3 つをセットで見てください。

そこまで見れば、「何となく雰囲気は分かる」ではなく、
「どの行で、何が、どんな状態だったか」まで手がかりが得られます。

一番下の行だけ見る

StackTrace の一番下(main メソッドなど)だけ見て、「この行が問題なんだ」と勘違いしてしまうこともあります。

一番下は多くの場合「スタート地点」であって、「爆発地点」ではありません。

「一番上の at 行」こそが真犯人候補
その下に続く行は“そこへ至る道”

という意識で、上から読む癖をつけてください。


まとめ:StackTrace を読む「筋肉」をつける

StackTrace の読み方を、初心者向けに整理するとこうなります。

一番上の「例外名+メッセージ」で、エラーの“種類”と“具体的な事情”を把握する
最初の at クラス.メソッド(ファイル:行) を見て、「爆発した行」に飛ぶ
その行の周辺コードを見ながら、「何が null?何が範囲外?どの入力が不正?」を考える
必要に応じて、その下の at 行(呼び出し元)をたどって、「どういう流れでそこに来たか」を追う

これが自然にできるようになると、
エラーに遭遇したときの「何から見ていいか分からない」状態が減っていきます。

タイトルとURLをコピーしました