0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 9.65: リフレクションよりもインターフェースを選ぶ

Posted at

9.65: リフレクションよりもインターフェースを選ぶ

結論

リフレクションは強力だが最後の手段にするべき。
可能ならインターフェース(または関数型インターフェース/抽象化)を使って型安全に振る舞いを差し替える。
インターフェースはコンパイル時チェック・可読性・テスト容易性・セキュリティ・保守性を与える一方、リフレクションは実行時エラー・性能低下・モジュール境界の破壊・脆弱で理解しづらいコードを招きやすい。


良い例

依存性注入/ポリモーフィズムで振る舞いを切り替える典型パターン。

// インターフェース
public interface PaymentProcessor {
    Receipt process(PaymentRequest req);
}

// 実装A(本番)
public class StripePaymentProcessor implements PaymentProcessor {
    public Receipt process(PaymentRequest req) {
        // Stripe API を呼ぶ実装
    }
}

// 実装B(テスト用スタブ)
public class StubPaymentProcessor implements PaymentProcessor {
    public Receipt process(PaymentRequest req) {
        return new Receipt("stub", true);
    }
}

// 利用側(UseCase / Service)
public class CheckoutService {
    private final PaymentProcessor processor;

    public CheckoutService(PaymentProcessor processor) {
        this.processor = processor;
    }

    public Receipt checkout(PaymentRequest req) {
        return processor.process(req);
    }
}

// 起動時に差し替え(Composition Root)
PaymentProcessor p = new StripePaymentProcessor(); // 本番
// PaymentProcessor p = new StubPaymentProcessor(); // テスト
CheckoutService svc = new CheckoutService(p);

利点:
コンパイル時にメソッド存在が保証され、IDE補完・リファクタが効き、簡単にスタブ差し替えできる。


悪い例

メソッド名やシグネチャを文字列で扱い、実行時に呼び出すパターン。

// 呼び出し側(リフレクション利用)
Object impl = Class.forName("com.example.StripePaymentProcessor").getConstructor().newInstance();
Method m = impl.getClass().getMethod("process", PaymentRequest.class);
Receipt r = (Receipt) m.invoke(impl, req);

問題点:

  • process の名前やシグネチャを文字列で保持 → リファクタ時に壊れる

  • 例外が増える(ClassNotFoundException, NoSuchMethodException, InvocationTargetException…)

  • メソッドが存在してもアクセス制御に引っかかる場合がある(モジュールシステム)

  • 実行時オーバーヘッド(性能低下)

  • テストやモックが難しい


いつリフレクションを許容するか

  • フレームワークやライブラリ(DI・シリアライズ・ORM・テストフレームワーク等)がユーザーのコードを自動的に扱う場合は 仕方なく使うことがある
    ただしその責務はフレームワーク層に閉じるべき。
     
  • ランタイムプラグインロードや動的モジュール検出が真に必要な場合のみ
     
  • 代替手段(ServiceLoader、DIコンテナ、ファクトリ、Strategy/Commandパターン、注入された Supplier/Function)が使えないときのみ
     
  • Java 9+ のモジュールシステムを使っている場合、リフレクションでアクセスするには --add-exports 等の特別な設定が必要になり、運用負担が増す

代替テクニック(リフレクションを使わず安全に実現する方法)

  • インターフェース + DI(コンストラクタ注入 / ServiceProvider)

  • ServiceLoader(プラグイン検出)

  • ファクトリ / Provider / Supplier を注入

  • 関数型インターフェース(Function/Supplier)を引数で受ける

  • コンパイル時注釈処理(Annotation Processor)でコード生成(リフレクションのコストと脆弱性を排除)


チェックリスト

  • API を設計するときはまず インターフェース を作る
     
  • 動的な振る舞い変更が必要か再検討:不要ならリフレクションを使わない
     
  • フレームワークコードでリフレクションを使うなら、その箇所を明確に分離しテストする
     
  • リフレクション使用時は例外処理・型チェックを丁寧に行い、ドキュメントに理由を書く
     
  • Java モジュールを使っている場合、リフレクションでアクセスする API はエクスポートポリシーに注意
     
  • 性能が重要なら事前にベンチ(Profiler/JMH)を取り、MethodHandle 等の高速手段を検討する
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?