throw の全体像
throw は「ここで例外を発生させる」という明示的な合図です。コードが不正な状態や契約違反を検知した瞬間に処理を中断し、例外オブジェクトを投げて呼び出し側へ失敗を伝えます。使いどころは「前提条件のチェック」「状態の矛盾」「外部リソースの異常に意味を付けて再送出」など。正しく使うと、バグを早期に表面化させて、壊れない設計に寄せられます。
throw と throws の違い
役割の違いを一言で
- throw は「いま例外を起こす」実行文。
- throws は「このメソッドからその例外が出る可能性がある」宣言。
void f() {
throw new IllegalArgumentException("不正な入力"); // いま投げる
}
void g() throws java.io.IOException { // 出る可能性を宣言
// I/Oで失敗したら IOException が投げられるかもしれない
}
Javathrows を付けても例外は自動で発生しません。実際に失敗したとき、もしくはあなたが throw したときに伝播します。
使い方の基本:前提違反を早期に弾く
入力検証で契約を守る
public static int taxed(int subtotal, double taxRate) {
if (subtotal < 0) {
throw new IllegalArgumentException("subtotal must be >= 0: " + subtotal);
}
if (taxRate < 0 || taxRate > 1) {
throw new IllegalArgumentException("taxRate must be 0..1: " + taxRate);
}
return (int) Math.round(subtotal * (1 + taxRate));
}
Java「失敗を後で見つける」より「早く止める」。メッセージに入力値や期待値を含めると、原因特定が圧倒的に速くなります。
null の扱いを明確化
import java.util.Objects;
static String upper(String s) {
Objects.requireNonNull(s, "s must not be null"); // NullPointerException を投げる
return s.toUpperCase();
}
Java契約違反は例外で即座に知らせ、曖昧な状態をシステム内に残さないのが基本方針です。
例外の再送出と包み直し(重要ポイントの深掘り)
原因を連鎖させて意味を付け足す
下位層の具体例外に「文脈」を与え、上位へ伝えるのが包み直しです。原因は必ず渡します。
try {
dao.save(order);
} catch (java.sql.SQLException e) {
throw new IllegalStateException("注文保存失敗: id=" + order.id(), e);
}
Java- 原因(cause)を渡すことでスタックトレースの連鎖が保たれ、根本原因を辿れます。
- メッセージには「なぜ」「どの入力で」を含めると、運用で効きます。
同じ型で再スローする
try {
riskyIO();
} catch (java.io.IOException e) {
// ログして、責任は上位へ
System.err.println("I/O失敗: " + e.getMessage());
throw e;
}
Java「この層では回復しない」方針なら、再スローで上位の集中ハンドリングに委ねます。
チェック例外と実行時例外に投げ分ける指針(重要ポイントの深掘り)
何を投げるかを設計で決める
- 外部要因(I/O、ネットワーク、DB)で呼び出し側が回復し得る失敗は「チェック例外」(例:IOException)として throws で宣言して伝える。
- 契約違反やバグ(不正引数、状態不整合)は「実行時例外」(例:IllegalArgumentException、IllegalStateException、NullPointerException)を throw して即座に表面化させる。
曖昧なときは「呼び出し側が合理的に回復できるか?」を基準に判断します。
カスタム例外を作って意味を伝える
ドメイン固有の失敗を型で表現
public class NotEnoughBalanceException extends RuntimeException {
public NotEnoughBalanceException(String accountId, int needed, int actual) {
super("残高不足: id=" + accountId + " needed=" + needed + " actual=" + actual);
}
}
Javastatic void pay(String accountId, int amount, int balance) {
if (balance < amount) throw new NotEnoughBalanceException(accountId, amount, balance);
// 決済処理...
}
Java「型名そのもの」が意味を語るため、呼び出し側は型で分岐でき、メッセージは運用へ効きます。
よくある落とし穴と避け方(重要ポイントの深掘り)
情報のないメッセージ
「Error」「失敗しました」だけでは運用で詰みます。入力値・ID・期待値・状態を要約してメッセージに含めましょう。
原因を捨てる包み直し
// 悪い例:原因 e を渡していない
throw new IllegalStateException("保存失敗");
Java原因が失われると解析不能になります。必ず new Xxx("msg", e) で連鎖させます。
例外で通常フローを作る
例外は異常専用。通常の分岐や終了条件に例外を使うと可読性・性能が悪化します。前提チェックは if で前倒しに。
広すぎる例外へ投げ替え
「throws Exception」や何でもかんでも RuntimeException へ変換は呼び出し側にとって不親切。意味のある型へ絞り、回復可能性を失わせないようにします。
例題で身につける
例 1: 入力前提違反を即時に通知
public class Price {
public static int taxed(int subtotal, double taxRate) {
if (subtotal < 0) {
throw new IllegalArgumentException("subtotal must be >= 0: " + subtotal);
}
if (taxRate < 0 || taxRate > 1) {
throw new IllegalArgumentException("taxRate must be 0..1: " + taxRate);
}
return (int) Math.round(subtotal * (1 + taxRate));
}
}
Java例 2: 下位層の失敗を文脈付きで包み直す
class Repository {
User find(String id) {
try {
// DBアクセス...
return new User(id, "Sato");
} catch (java.sql.SQLException e) {
throw new IllegalStateException("ユーザー取得失敗: id=" + id, e);
}
}
}
Java例 3: ルール違反をカスタム例外で明快に
public class OverLimitException extends RuntimeException {
public OverLimitException(int limit, int actual) {
super("上限超過: limit=" + limit + " actual=" + actual);
}
}
static void add(int value, int limit) {
if (value > limit) throw new OverLimitException(limit, value);
// 追加処理...
}
Java仕上げのアドバイス(重要部分のまとめ)
throw は「ここで止める」を宣言するスイッチです。前提違反は実行時例外で早期に弾き、外部要因の失敗はチェック例外として意味を保ったまま上位へ。包み直すときはメッセージに文脈を足し、原因例外を必ず連鎖させる。カスタム例外で型に意味を持たせ、通常フローは if で書く——この型が身につくと、失敗は早く見つかり、直しやすいコードになります。
