Javaでstatic contextから非staticメソッドを呼べないコンパイルエラーの原因と直し方を解説する記事のアイキャッチ。

Java

Javaの「non-static method cannot be referenced from a static context」原因と直し方

AIによる要約

non-static method cannot be referenced from a static contextは、staticメソッドなど現在のインスタンスが存在しない場所から、どのオブジェクトに対する処理かを指定せずにインスタンスメソッドを呼んだときのコンパイルエラーです。mainはstaticなので、非staticメソッドを呼ぶには対象オブジェクトを生成して変数経由で呼びます。メソッドがインスタンス状態やDIされた依存を一切使わない純粋な共通処理ならstatic化も候補ですが、エラーを消すためだけにServiceやRepositoryをstaticへ変えてはいけません。誰の状態を使う処理か、Springが生成するBeanか、単なるユーティリティかを判断して直します。

新人SE
新人SE
mainメソッドから普通のメソッドを呼んだら、static contextのエラーになりました。呼ぶメソッドにもstaticを付ければよいですか?
ポンコツSE
ポンコツSE
static化は直し方の一つですが、最初に「その処理はどのオブジェクトの状態を使うか」を確認します。

Javaの学習中やバッチ処理のmainメソッドを書いたとき、non-static method cannot be referenced from a static contextというコンパイルエラーが出ることがあります。日本語のIDEでは「staticでないメソッドをstaticコンテキストから参照することはできません」などと表示されます。

エラー箇所へstaticを追加するとコンパイルが通る場合はありますが、Serviceの状態やDIまでstatic化すると、別の設計問題を作ります。クラスに属する処理と、オブジェクトに属する処理の違いを理解して修正します。

この記事のポイント

  • staticメソッドには現在のインスタンスを表すthisがない
  • 非staticメソッドは対象オブジェクトを指定して呼ぶ
  • mainから呼ぶならインスタンス生成が基本候補
  • 状態を使わない共通処理だけstatic化を検討する
  • SpringのService・Repositoryをエラー回避でstaticにしない
  • staticフィールドへ依存オブジェクトを入れない

エラーが発生する基本例

次のコードでは、staticなmainメソッドから、非staticのprintMessageを対象オブジェクトなしで呼んでいます。

public class MessageApp {

    public static void main(String[] args) {
        printMessage(); // コンパイルエラー
    }

    public void printMessage() {
        System.out.println("Hello");
    }
}

printMessage()は、実際には「どのMessageAppオブジェクトのprintMessageか」を決める必要があります。しかしmainはクラスに属するstaticメソッドなので、暗黙のthisがありません。

非staticメソッドを呼ぶときは、処理対象となるインスタンスが必要です。staticコンテキストには、その対象が自動では用意されません。

staticメソッドとインスタンスメソッドの違い

項目staticメソッドインスタンスメソッド
所属クラス生成されたオブジェクト
呼び出し例Math.max(1, 2)user.getName()
this使えない使える
インスタンスフィールド対象指定なしでは使えない使える
代表用途状態を持たない共通処理オブジェクトの状態や依存を使う処理

たとえばuser.getName()は、そのuserが持つnameを返します。一方、Math.maxは特定のMathオブジェクトの状態を必要としません。staticを付けるかは、処理がどこに属するかで判断します。

直し方1:インスタンスを生成して呼ぶ

メソッドがインスタンスの状態を使うなら、対象オブジェクトを生成し、その変数から呼びます。

public class MessageApp {

    private final String message;

    public MessageApp(String message) {
        this.message = message;
    }

    public static void main(String[] args) {
        MessageApp app = new MessageApp("Hello");
        app.printMessage();
    }

    public void printMessage() {
        System.out.println(message);
    }
}

printMessageは各インスタンスのmessageを使うため、staticには向きません。どのmessageを表示するかがオブジェクトによって変わるからです。

直し方2:本当に状態を使わないならstatic化する

引数だけで結果が決まり、インスタンスフィールドやDIされた依存を使わない処理なら、staticメソッドとして表現できる場合があります。

public final class TextNormalizer {

    private TextNormalizer() {
    }

    public static String normalize(String value) {
        if (value == null) {
            return "";
        }
        return value.trim().toUpperCase(Locale.ROOT);
    }
}

この例は、入力value以外の状態を使いません。ユーティリティクラスとしてインスタンス化を禁止するため、privateコンストラクタを置いています。

注意

コンパイルエラーを消すことと、staticにすべき設計であることは別です。将来設定値や外部サービスへ依存する可能性、テストで差し替える必要も考えます。

インスタンスフィールドも同じ理由で参照できない

メソッドだけでなく、非staticフィールドもstaticコンテキストから対象なしでは参照できません。

public class Counter {

    private int count;

    public static void printCount() {
        System.out.println(count); // コンパイルエラー
    }
}

Counterを2個生成すれば、それぞれ別のcountを持てます。staticメソッド側には、どちらのcountを表示するか決める情報がありません。

public class Counter {

    private int count;

    public void printCount() {
        System.out.println(this.count);
    }
}

インスタンスメソッドなら、呼び出し対象のcounter.printCount()が現在のオブジェクトになり、this.countを参照できます。

SpringのServiceをstaticにしてはいけない理由

Spring BootのServiceから非staticメソッドを呼べず、ServiceやRepositoryへstaticを付けようとするケースがあります。Spring管理のBeanは、コンテナが生成したインスタンスへ依存を注入します。staticメソッドは、そのBeanインスタンスに対して呼ぶ処理ではありません。

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public Order find(Long id) {
        return orderRepository.findById(id)
                .orElseThrow(OrderNotFoundException::new);
    }
}

このfindは、注入されたorderRepositoryを使うインスタンスメソッドです。ControllerもSpringからOrderServiceを受け取り、変数経由で呼びます。

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public OrderResponse find(@PathVariable Long id) {
        return OrderResponse.from(orderService.find(id));
    }
}

エラー回避のためにOrderService.findorderRepositoryをstaticにすると、DI、テスト時の差し替え、トランザクションなどSpringの仕組みと噛み合わなくなります。

staticフィールドへBeanを入れない

staticメソッドからRepositoryを使うために、staticフィールドへ無理に代入する実装も避けます。

// NG: グローバル状態になり、初期化順やテストが不安定になる
@Component
public class OrderFinder {

    private static OrderRepository repository;

    public OrderFinder(OrderRepository repository) {
        OrderFinder.repository = repository;
    }

    public static Order find(Long id) {
        return repository.findById(id).orElseThrow();
    }
}

この構造では依存関係がコンストラクタから利用箇所まで素直につながらず、テスト間で状態を共有しやすくなります。通常のBeanとしてインスタンスメソッドを呼びます。

mainからSpringのBeanを使う場合

Spring BootアプリのmainメソッドでServiceを直接newするのではなく、ApplicationContextを起動し、処理本体をSpring管理のRunnerなどへ分けます。

@SpringBootApplication
public class BatchApplication {

    public static void main(String[] args) {
        SpringApplication.run(BatchApplication.class, args);
    }
}

@Component
public class OrderBatch implements CommandLineRunner {

    private final OrderService orderService;

    public OrderBatch(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void run(String... args) {
        orderService.processPendingOrders();
    }
}

mainは起動だけを担当し、業務処理はDIされたBeanへ任せます。既存のバッチ基盤やジョブ管理がある現場では、その起動方式へ合わせてください。

何でもstaticにすると現場で困ること

  • 依存オブジェクトをコンストラクタで表しにくい
  • テストで実装を差し替えにくい
  • 共有状態がテスト間・利用者間で残りやすい
  • 初期化順序へ依存しやすい
  • Springのプロキシやトランザクションと噛み合わない
  • クラスの責務が「共通置き場」へ膨らみやすい

static自体が悪いわけではありません。定数、状態を持たない変換処理、ファクトリメソッドなど、クラスに属することが自然な用途で使います。

直し方を選ぶ判断表

状況直し方理由
オブジェクトのフィールドを使うインスタンスを生成して呼ぶ対象ごとに状態が異なる
引数だけで結果が決まるstatic化を検討現在インスタンスが不要
SpringのService・Repositoryを使うDIされたBeanから呼ぶSpring管理を維持する
mainから業務処理を起動Runnerなどへ処理を分けるmainは起動責務に絞る
とにかくエラーだけ消したいstatic化しない設計判断が不足している

テストで状態の独立性を確認する

インスタンスに属する状態なら、複数インスタンスが互いに影響しないことを確認できます。

@Test
void カウンターごとに値を保持する() {
    Counter first = new Counter();
    Counter second = new Counter();

    first.increment();
    first.increment();
    second.increment();

    assertEquals(2, first.getCount());
    assertEquals(1, second.getCount());
}

countをstaticへすると全インスタンスで共有され、この期待を満たせません。データの所有者を考えることで、staticかインスタンスかを判断できます。

コードレビューで伝えるコメント例

コンパイルエラー回避のためOrderService.findをstaticへ変更していますが、このメソッドはDIされたOrderRepositoryを使うため、Spring Beanのインスタンスメソッドとして扱う必要があります。呼び出し元へOrderServiceをコンストラクタ注入し、orderService.find(id)と呼ぶ形に戻すのがよいです。

「staticを消してください」だけでなく、依存関係と呼び出し方を示すと、同じエラーが別クラスで再発しにくくなります。

提出前のセルフチェック

レビュー前に確認すること

  • 呼び出し元がstaticコンテキストか
  • 呼び出すメソッドがインスタンス状態を使うか
  • 対象オブジェクトを変数経由で呼べるか
  • エラー回避だけのstatic化になっていないか
  • staticフィールドへ可変状態を置いていないか
  • Spring Beanをstatic化していないか
  • mainでServiceを直接newしていないか
  • ユーティリティクラスの責務が膨らんでいないか
  • 複数インスタンスで状態を分ける必要があるか
  • テストで依存を差し替えられる構造か

Javaのstaticとオブジェクトを学ぶ参考書

staticのエラーは文法だけでなく、クラスとインスタンス、フィールド、コンストラクタ、依存関係をつなげて学ぶと理解しやすくなります。

書籍「スッキリわかるJava入門 実践編 第5版」の表紙
商品画像:Amazon.co.jp
PR Java基礎学習後の若手向け

スッキリわかるJava入門 実践編 第5版

中山 清喬 (著), 株式会社フレアリンク (監修)

基礎文法の次に必要な、現場寄りのJava知識を補う。

コレクション、ジェネリクス、ラムダ式、ストリームなど、業務コードで出会いやすい機能を入門編の次に学べます。

  • Java基礎の次に何を学ぶか迷っている
  • コレクションやStreamを整理したい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

non-static method cannot be referenced from a static contextは、現在のインスタンスがないstaticコンテキストから、対象を指定せずにインスタンスメソッドを呼んだときのコンパイルエラーです。

インスタンス状態を使うなら対象オブジェクトを生成して呼び、引数だけで結果が決まる共通処理ならstatic化を検討します。SpringのServiceやRepositoryはエラー回避でstaticにせず、BeanをDIしてインスタンスメソッドとして呼んでください。

staticメソッドとstaticコンテキストの仕様はJava言語仕様でも確認できます。

-Java
-, , ,