Java | 基礎文法:ランタイムエラーとコンパイルエラー

Java Java
スポンサーリンク

全体像

Java のエラーは大きく「コンパイルエラー」と「ランタイムエラー」に分かれます。コンパイルエラーは「プログラムに文法・型・宣言の誤りがあり、そもそも実行ファイルを作れない」状態です。ランタイムエラーは「コンパイルは通ったが、実行中の値や環境が原因で崩れる」状態です。違いを掴むコツは「いつ検出されるか」「何が原因か」「どう直すか」をセットで考えることです。


コンパイルエラーの基本

検出のタイミングと特徴

コンパイルエラーは、コンパイラ(javac)や IDE がソースコードを解析した時点で検出されます。プログラムは起動できません。原因は文法ミス、型不一致、未定義の名前、アクセス不可、チェック例外の未処理など「コードとして成立していない」ことです。IDE では赤い波線や Problems/Build ログに詳細が出ます。

代表的な例と直し方

不正な文(セミコロン抜け、括弧不一致、キーワードの誤り)などは最優先で修正します。次に型と宣言の整合性を取ります。

public class Sample {
    public static void main(String[] args) {
        int x = "123";        // 型不一致(compile error)
        System.out.println(x)
        //             ↑ セミコロン抜け(compile error)
    }
}
Java

未定義の識別子やスコープ外の変数参照も典型です。

void f() {
    System.out.println(value); // value が宣言されていない(compile error)
}
Java

チェック例外の未処理もコンパイルで止まります。これは「呼び出し側が回復戦略を持つべき」という契約違反です。

import java.nio.file.*;
import java.io.*;

static String firstLine(Path p) {
    return Files.readString(p); // IOException を処理/宣言していない(compile error)
}

// 修正例:宣言する
static String firstLine(Path p) throws IOException {
    return Files.readString(p);
}
Java

ランタイムエラーの基本

検出のタイミングと特徴

ランタイムエラーは、プログラムが実行されてから初めて起きます。コンパイル時点では構文・型的に正しくても、実行時の値や外部環境(入力、ファイル、ネットワーク、配列の長さ)が条件を満たさずに崩れます。JVM が例外やエラーを投げ、スタックトレースで「どの行で何が起きたか」を知らせます。

代表的な例と直し方

null を使ったメソッド呼び出しは確実に崩れます。使用前にガードするか、契約として禁止します。

String s = null;
int len = s.length(); // NullPointerException(runtime error)

// 修正例:入口で検証
import java.util.Objects;
int lenSafe(String s) {
    Objects.requireNonNull(s, "s must not be null");
    return s.length();
}
Java

配列・リストの範囲外アクセスは境界の誤りです。原則「0 ≤ index < size」を守ります。

int[] a = {10, 20, 30};
int v = a[3]; // ArrayIndexOutOfBoundsException(runtime error)

// 修正例:未満で回す
for (int i = 0; i < a.length; i++) System.out.println(a[i]);
Java

整数の 0 除算は例外になります。分母を事前チェックします。

int a = 10, b = 0;
int c = a / b; // ArithmeticException(runtime error)

// 修正例:ガード
int safeDiv(int x, int y) {
    if (y == 0) throw new IllegalArgumentException("divisor must not be 0");
    return x / y;
}
Java

文字列の不正な数値変換は入力の問題です。バリデーションとフォールバックを設計します。

int port = Integer.parseInt("12a"); // NumberFormatException(runtime error)
Java

重要ポイントの深掘り

「チェック例外はコンパイル、実行時例外はランタイム」ではない

チェック例外は「捕捉または throws 宣言」がコンパイル時に必須です。一方、RuntimeException(NPE、IOBE、ArithmeticException など)はコンパイルでは止まりませんが、実行時に起きます。ただし、RuntimeException を「防ぐコード」(null ガード、境界検証)を書かないと、実質的にランタイムエラーの温床になります。結論は「契約をコードで保証するか、契約違反を例外で早期に表面化する」ことです。

コンパイルが通る=安全ではない

型・構文が正しくても、仕様や契約が破られていれば実行時に崩れます。入力検証(null/範囲/フォーマット)、外部要因のハンドリング(I/O の失敗時の設計)、境界と丸めの統一(BigDecimal の divide は丸め指定)を「実行時の現実」を見据えて書く必要があります。

スタックトレースは「先頭行から読む」

ランタイムエラーが出たら、最初の原因行(クラス名:行番号)を開き、どの値が不正だったかを事実で特定します。包み直しがある場合は Caused by の最後まで辿って根本原因を確認します。ここで思い込みではなく観測(変数ウォッチ、式評価)に切り替えると修正が速くなります。


具体例で違いを体得する

例 1: コンパイルは通らないが、直せば実行できる

public class App {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]); // OK
        int x = "123"; // コンパイルエラー:型不一致
    }
}
Java

型を直せば起動できます。

int x = Integer.parseInt("123"); // 実行時に NumberFormatException になり得る点は別問題
Java

このように「型不一致」はコンパイルで止まり、「入力の不正」は実行時に顕在化します。

例 2: コンパイルは通るが、実行中に崩れる

public class Divide {
    public static void main(String[] args) {
        int a = 10, b = 0;      // コンパイルは通る
        System.out.println(a / b); // 実行時に ArithmeticException
    }
}
Java

事前チェックに置き換えると、崩れ方を「契約違反」として扱えます。

if (b == 0) throw new IllegalArgumentException("divisor must not be 0");
Java

例 3: チェック例外はコンパイル時の対応が必須

import java.nio.file.*;
import java.io.*;

public class ReadFile {
    public static void main(String[] args) {
        String s = Files.readString(Path.of("missing.txt")); // コンパイルエラー:IOException 未処理
    }
}
Java

対応例は二択です。ここで回復するか、上位へ委ねるか。

try {
    String s = Files.readString(Path.of("missing.txt"));
} catch (IOException e) {
    System.err.println("読込失敗: " + e.getMessage());
}

// または宣言して上位へ
static String read(Path p) throws IOException {
    return Files.readString(p);
}
Java

直し方の設計指針

コンパイルエラーは「コードの整合性」を直す

文法、型、宣言、アクセス修飾、例外宣言の整合性を取り、コンパイルが通る最小の状態へ戻します。IDE の修正候補や「Jump to source」で該当箇所へ直行し、波線が消えるまで集中して直します。

ランタイムエラーは「契約と現実」を近づける

使用前ガード、早期リターン、境界検証、入力正規化、try-with-resources による後始末保証、例外の包み直し(原因連鎖)で「失敗しても壊れない」設計へ寄せます。テストで境界と異常系を先に用意すると、実行時の崩れを事前に捕まえられます。


仕上げのまとめ

コンパイルエラーは「コードが成立していない」シグナルで、文法・型・宣言・チェック例外の整合性を直せば起動できます。ランタイムエラーは「実行時の値や環境が契約を破った」シグナルで、入力検証、境界チェック、後始末の保証、例外設計で防ぎます。違いを理解したら、失敗を再現し、スタックトレースの先頭行から事実を観測し、ガードや設計の修正へ落とす——この型が身につけば、エラーは「すぐ見つけて、正しく直せる」相手になります。

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