Javaのstaticとは?使いどころ・設計時の判断基準・Spring Bootでの注意点を解説

Javaを勉強していると、static というキーワードがよく出てきます。

例えば、Javaの main メソッドにも static が付いています。

public static void main(String[] args) {
    System.out.println("Hello Java");
}

また、定数を定義するときにも static final を使うことがあります。

public static final int MAX_RETRY_COUNT = 3;

ただ、Javaを学び始めたばかりの頃は、以下のような点で迷いやすいです。

  • static とは何か
  • なぜ static を付けるのか
  • どういう場合に static にすべきか
  • static にしない方がよいケースは何か
  • Spring Bootでは static を使ってよいのか

この記事では、Javaの static について、基本的な意味から実務での使いどころ、Spring Bootで気を付けるべき点までまとめます。

目次

スポンサーリンク

※このページにはプロモーションが含まれています。当サイトは各種アフィリエイトプログラムから一定の収益を得ています。

staticとは何か

static を一言でいうと、オブジェクトではなくクラスに所属させるためのキーワードです。

Javaでは、通常のフィールドやメソッドはオブジェクトに所属します。

一方で、static を付けたフィールドやメソッドは、オブジェクトではなくクラスに所属します。

  • 通常のフィールド・メソッド・・・オブジェクトに所属する
  • staticフィールド・staticメソッド・・・クラスに所属する

この違いが、static を理解するうえで一番重要です。

通常メソッドとstaticメソッドの違い

まず、通常のメソッドを見てみます。

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public void introduce() {
        System.out.println("私の名前は" + name + "です。");
    }
}

この introduce メソッドは、Userオブジェクトに対して呼び出します。

public class Main {
    public static void main(String[] args) {
        User user = new User("山田");
        user.introduce();
    }
}

実行結果は以下です。

私の名前は山田です。

この場合、introduce メソッドは name フィールドを使っています。

name は、オブジェクトごとに異なる値です。

User user1 = new User("山田");
User user2 = new User("佐藤");

user1.introduce() と user2.introduce() では、結果が変わります。

つまり、introduce メソッドは「どのUserオブジェクトなのか」が重要なメソッドです。

このようなメソッドは、基本的に static にしません。

staticメソッドの例

次に、staticメソッドを見てみます。

public class MathUtil {
    public static int add(int a, int b) {
        return a + b;
    }
}

呼び出し側は以下です。

public class Main {
    public static void main(String[] args) {
        int result = MathUtil.add(10, 20);
        System.out.println(result);
    }
}

実行結果です。

30

この例では、MathUtil.add(10, 20) のように、クラス名から直接メソッドを呼び出しています。

new MathUtil() のように、オブジェクトを作成していません。

staticメソッドは、オブジェクトを作らずにクラス名から呼び出せるメソッドです。

staticフィールドとは

staticフィールドは、オブジェクトごとではなく、クラス全体で共有されるフィールドです。

以下の例を見てみます。

public class Counter {
    public static int count = 0;

    public Counter() {
        count++;
    }
}

使う側です。

public class Main {
    public static void main(String[] args) {
        new Counter();
        new Counter();
        new Counter();

        System.out.println(Counter.count);
    }
}

実行結果です。

3

Counterオブジェクトを3つ作っていますが、count はクラス全体で1つだけです。

イメージとしては以下です。

Counterクラス
  static count = 3

Counterオブジェクト1
Counterオブジェクト2
Counterオブジェクト3

count はそれぞれのオブジェクトが持っているのではなく、Counterクラス側に1つだけ存在します。

通常フィールドとstaticフィールドの違い

通常フィールドの場合、値はオブジェクトごとに分かれます。

public class User {
    public String name;
}

public class Main {
    public static void main(String[] args) {
        User user1 = new User();
        user1.name = "山田";

        User user2 = new User();
        user2.name = "佐藤";

        System.out.println(user1.name);
        System.out.println(user2.name);
    }
}

実行結果です。

山田
佐藤

user1 と user2 は別々のオブジェクトなので、name の値も別々です。

一方で、staticフィールドにすると、値はクラス全体で共有されます。

public class User {
    public static String name;
}

public class Main {
    public static void main(String[] args) {
        User.name = "山田";
        User.name = "佐藤";

        System.out.println(User.name);
    }
}

実行結果です。

佐藤

staticフィールドは1つしかないため、後から代入した値で上書きされます。

そのため、ユーザー名のようにオブジェクトごとに異なる値を static にするのは不適切です。

staticを使う代表的な場面

static は何でも付ければよいわけではありません。

実務でよく使う場面は、主に以下です。

  • 定数
  • ユーティリティメソッド
  • ファクトリメソッド
  • mainメソッド
  • staticネストクラス

定数にはstatic finalを使う

static の代表的な使い方が、定数です。

public class AppConstants {
    public static final int MAX_LOGIN_ATTEMPTS = 5;
    public static final String DEFAULT_LANGUAGE = "ja";
}

使う側です。

if (loginFailedCount >= AppConstants.MAX_LOGIN_ATTEMPTS) {
    System.out.println("ログイン試行回数の上限に達しました。");
}

定数は、オブジェクトごとに値が変わるものではありません。

そのため、クラスに所属する値として static にします。

また、変更されない値なので final を付けます。

キーワード意味
staticクラスに所属する
final再代入できない

そのため、定数では以下の形がよく使われます。

public static final int MAX_RETRY_COUNT = 3;

ユーティリティメソッドにはstaticを使うことがある

特定のオブジェクトの状態を使わず、引数だけで処理が完結するメソッドは、static に向いています。

public class StringUtils {
    public static boolean isEmpty(String value) {
        return value == null || value.isEmpty();
    }
}

使う側です。

if (StringUtils.isEmpty(name)) {
    System.out.println("名前が未入力です。");
}

この isEmpty メソッドは、StringUtilsオブジェクトの状態を使っていません。

引数の value だけで結果が決まります。

このような処理は、staticメソッドにしても自然です。

他にも、以下のような処理は static に向いています。

  • 文字列チェック
  • 数値計算
  • 単純なフォーマット変換
  • 引数だけで完結する判定処理

例です。

public class NumberUtils {
    public static boolean isEven(int number) {
        return number % 2 == 0;
    }
}

public class NameFormatter {
    public static String toDisplayName(String lastName, String firstName) {
        return lastName + " " + firstName;
    }
}

ファクトリメソッドでstaticを使うことがある

オブジェクト生成に意味のある名前を付けたい場合、staticメソッドを使うことがあります。

public class User {
    private String name;
    private int age;

    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static User of(String name, int age) {
        return new User(name, age);
    }
}

使う側です。

User user = User.of("山田", 25);

この of メソッドは、Userオブジェクトを生成するための staticメソッドです。

また、生成パターンに意味を持たせたい場合にも使えます。

public class User {
    private String name;
    private int age;

    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static User guest() {
        return new User("ゲスト", 0);
    }
}

使う側です。

User guestUser = User.guest();

このように、コンストラクタよりも意味が分かりやすくなる場合、staticファクトリメソッドは有効です。

mainメソッドがstaticである理由

Javaの起動点である main メソッドも static です。

public static void main(String[] args) {
    System.out.println("Hello Java");
}

プログラム開始時点では、まだオブジェクトが作られていません。

そのため、JVMがクラスから直接呼び出せるように、main メソッドは static になっています。

staticメソッドの中から通常フィールドは直接使えない

staticメソッドの中から、通常のフィールドを直接使うことはできません。

public class User {
    private String name;

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

なぜなら、staticメソッドはクラスに所属しており、特定のオブジェクトに紐づいていないからです。

name はオブジェクトごとの値です。

User user1 = new User("山田");
User user2 = new User("佐藤");

この状態で、staticメソッドから単に name と書いても、山田なのか佐藤なのか判断できません。

そのため、コンパイルエラーになります。

staticメソッドの中から通常メソッドも直接呼べない

通常メソッドも、staticメソッドの中から直接呼ぶことはできません。

public class User {
    public void introduce() {
        System.out.println("自己紹介します");
    }

    public static void test() {
        introduce(); // コンパイルエラー
    }
}

introduce メソッドは、オブジェクトに対して呼び出すメソッドです。

そのため、呼び出すにはオブジェクトが必要です。

public class User {
    public void introduce() {
        System.out.println("自己紹介します");
    }

    public static void test() {
        User user = new User();
        user.introduce();
    }
}

このように、オブジェクトを作れば通常メソッドを呼び出せます。

staticにすべきかどうかの判断基準

設計時に static を付けるか迷った場合は、以下の観点で考えると分かりやすいです。

オブジェクトごとに値が変わるか

オブジェクトごとに値が変わるものは、static にしません。

例えば、ユーザー名、年齢、メールアドレスなどは、ユーザーごとに異なります。

public class User {
    private String name;
    private int age;
    private String email;
}

これは通常フィールドにするべきです。

逆に、以下のように static にすると危険です。

public class User {
    public static String name;
}

staticフィールドはクラス全体で共有されるため、複数のユーザーを正しく表現できなくなります。

そのメソッドはオブジェクトの状態を使うか

オブジェクトのフィールドを使うメソッドは、基本的に static にしません。

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public void introduce() {
        System.out.println("私の名前は" + name + "です。");
    }
}

この introduce メソッドは、name に依存しています。

name はユーザーごとに違うため、introduce メソッドの結果もオブジェクトごとに変わります。

このようなメソッドは通常メソッドにします。

一方で、引数だけで結果が決まる処理であれば、static を検討できます。

public class PriceCalculator {
    public static int addTax(int price) {
        return (int) (price * 1.1);
    }
}

この addTax メソッドは、引数 price だけで結果が決まります。

そのため、static にしても違和感はありません。

状態を持つ必要があるか

状態を持つ必要があるクラスでは、static を安易に使わない方がよいです。

例えば、買い物かごを表すクラスを考えます。

import java.util.ArrayList;
import java.util.List;

public class Cart {
    private List<String> items = new ArrayList<>();

    public void addItem(String item) {
        items.add(item);
    }

    public int getItemCount() {
        return items.size();
    }
}

買い物かごは、ユーザーごとに別々であるべきです。

Cart cart1 = new Cart();
Cart cart2 = new Cart();

cart1 と cart2 は、それぞれ別の items を持つ必要があります。

もし items を static にすると、すべての買い物かごで同じリストを共有してしまいます。

import java.util.ArrayList;
import java.util.List;

public class Cart {
    private static List<String> items = new ArrayList<>();
}

これは、多くの場合、設計として不適切です。

全体で共有してよい値か

staticフィールドは、クラス全体で共有されます。

そのため、全体で共有してよい値だけを static にするべきです。

共有してよい代表例は、定数です。

public class UserConstants {
    public static final int MAX_NAME_LENGTH = 50;
}

このような最大文字数は、ユーザーごとに変わる値ではありません。

そのため、static final で定義して問題ありません。

staticにしない方がよいもの

以下のようなものは、基本的に static にしない方がよいです。

  • ユーザー名
  • 年齢
  • メールアドレス
  • 注文ID
  • 注文金額
  • 商品名
  • 在庫数
  • ログイン状態
  • 画面入力値
  • リクエスト情報
  • DBから取得した業務データ

これらは、ユーザーごと、リクエストごと、注文ごと、商品ごとに変わる可能性があります。

つまり、オブジェクトごとに持つべき値です。

可変のstaticフィールドには注意する

特に注意が必要なのは、変更可能な static フィールドです。

public class LoginContext {
    public static String currentUserId;
}

このような書き方は危険です。

static は全体で共有されるため、Webアプリケーションでは複数ユーザーの情報が混ざる可能性があります。

例えば、以下のようなことが起きる可能性があります。

ユーザーAがログイン
  → currentUserId = "A"

ユーザーBがログイン
  → currentUserId = "B"

ユーザーAの処理中なのに
  → currentUserId が "B" になっている

このような問題が起きるため、ログインユーザー情報やリクエスト単位の情報を static に保存するのは避けるべきです。

staticにするか迷ったときの判断フロー

static にするか迷った場合は、以下のように考えると分かりやすいです。

確認ポイント判断
オブジェクトごとに値が変わるか変わるなら static にしない
メソッド内でフィールドを使うか使うなら static にしない
全体で共有してよい定数か共有してよいなら static final を検討
引数だけで結果が決まる処理かstaticメソッドを検討
SpringのDIを使いたいか使いたいなら static にしない
テストで差し替えたい処理か差し替えたいなら static にしない

特に重要なのは、以下の考え方です。

  • その処理を実行するために「どのオブジェクトか」が必要なら static にしない
  • ユーザーごと、リクエストごとに変わる値は static にしない
  • 全体で共有してよい定数は static final にする
  • 引数だけで完結する単純な処理は static を検討する

Spring Bootでstaticを使うときの注意点

ここからは、Spring Bootを使う場合に特に気を付けたい点を整理します。

Spring Bootでは、Service、Component、Repository などのクラスをSpringコンテナに管理させることが多いです。

import org.springframework.stereotype.Service;

@Service
public class PriceService {
    public int addTax(int price) {
        return (int) (price * 1.1);
    }
}

このようなクラスは、DIによって他のクラスから利用します。

import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final PriceService priceService;

    public OrderService(PriceService priceService) {
        this.priceService = priceService;
    }

    public void order() {
        int price = priceService.addTax(1000);
        System.out.println(price);
    }
}

Spring Bootでは、このようにDIを使う設計が基本になります。

そのため、何でも static メソッドにするのではなく、Spring管理のBeanとして扱うべきかを考える必要があります。

Spring BootではstaticよりDIを優先する場面が多い

例えば、税率計算の処理を考えます。

単純に10%を足すだけなら、以下のような static メソッドでも動きます。

public class TaxUtils {
    public static int addTax(int price) {
        return (int) (price * 1.1);
    }
}

ただし、実務では以下のような変更が入ることがあります。

  • 税率を設定ファイルから読みたい
  • 国や地域によって税率を変えたい
  • 軽減税率に対応したい
  • テストで税率を差し替えたい
  • DBから税率を取得したい

このような可能性がある場合、staticメソッドよりも Service として定義した方が扱いやすくなります。

import org.springframework.stereotype.Service;

@Service
public class TaxService {
    public int addTax(int price) {
        return (int) (price * 1.1);
    }
}

呼び出し側です。

import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final TaxService taxService;

    public OrderService(TaxService taxService) {
        this.taxService = taxService;
    }

    public void createOrder() {
        int price = taxService.addTax(1000);
        System.out.println(price);
    }
}

Spring Bootでは、このように依存関係をコンストラクタで受け取る設計がよく使われます。

staticメソッドはDIしにくい

Spring Bootで static を使うときに重要なのが、staticメソッドはDIの恩恵を受けにくいという点です。

例えば、以下のような static メソッドを考えます。

public class MailUtils {
    public static void sendMail(String to, String message) {
        // メール送信処理
    }
}

一見便利そうですが、実務ではメール送信処理に設定値や外部サービスが必要になることがあります。

  • SMTPサーバー情報
  • 認証情報
  • 送信元アドレス
  • テンプレートエンジン
  • 外部APIクライアント

こういった依存関係を扱う場合、staticメソッドよりもSpring Beanにした方が自然です。

import org.springframework.stereotype.Service;

@Service
public class MailService {
    public void sendMail(String to, String message) {
        // メール送信処理
    }
}

呼び出し側です。

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final MailService mailService;

    public UserService(MailService mailService) {
        this.mailService = mailService;
    }

    public void registerUser(String email) {
        mailService.sendMail(email, "登録ありがとうございます");
    }
}

このようにしておくと、テスト時に MailService をモックに差し替えやすくなります。

staticフィールドにリクエスト情報を入れない

Spring BootのWebアプリでは、複数のリクエストが同時に処理されます。

そのため、リクエストごとの情報を static フィールドに入れてはいけません。

悪い例です。

public class RequestContext {
    public static String requestId;
    public static String loginUserId;
}

これは危険です。

staticフィールドはアプリケーション全体で共有されるため、別リクエストの値で上書きされる可能性があります。

リクエストA
  → loginUserId = "userA"

リクエストB
  → loginUserId = "userB"

リクエストAの処理中に
  → loginUserId が "userB" になっている可能性がある

ログインユーザー情報やリクエスト情報は、Spring Securityの認証情報、リクエストスコープ、メソッド引数、セッションなど、適切な仕組みで扱うべきです。

staticフィールドにDB取得結果を安易にキャッシュしない

DBから取得した値を static フィールドに保持したくなることがあります。

例えば、以下のようなコードです。

import java.util.HashMap;
import java.util.Map;

public class CodeMasterCache {
    public static Map<String, String> codeMap = new HashMap<>();
}

このような設計は、慎重に判断する必要があります。

理由は以下です。

  • いつ更新されるのか分かりにくい
  • 複数スレッドから同時アクセスされる可能性がある
  • テスト時に状態が残りやすい
  • アプリケーション再起動まで値が残る
  • どのクラスがその値に依存しているか分かりにくい

キャッシュが必要な場合は、SpringのCache機能や、専用のキャッシュ管理クラスを使う方が安全です。

例えば、単純な例では以下のように Service として管理します。

import org.springframework.stereotype.Service;

@Service
public class CodeMasterService {
    public String getCodeName(String code) {
        // DBやキャッシュから取得する
        return "コード名称";
    }
}

このようにしておくと、どのクラスがコードマスタに依存しているか分かりやすくなります。

staticにAutowiredしようとしない

Spring Boot初心者がやりがちなミスとして、staticフィールドに Autowired しようとするケースがあります。

悪い例です。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SampleUtil {
    @Autowired
    private static UserService userService;
}

このような書き方は基本的に避けるべきです。

SpringのDIは、Springが管理するオブジェクトに対して依存関係を注入する仕組みです。

staticフィールドはクラスに所属するため、通常のDIとは相性がよくありません。

正しくは、Spring Beanとして通常フィールドに注入します。

import org.springframework.stereotype.Component;

@Component
public class SampleComponent {
    private final UserService userService;

    public SampleComponent(UserService userService) {
        this.userService = userService;
    }

    public void execute() {
        userService.doSomething();
    }
}

このように、コンストラクタインジェクションを使う方が分かりやすく、安全です。

staticメソッドからRepositoryを呼ぶ設計は避ける

Spring Bootでは、DBアクセスに Repository を使うことが多いです。

この Repository を staticメソッドから直接使おうとする設計は避けた方がよいです。

例えば、以下のようなイメージです。

public class UserUtils {
    public static User findUser(Long userId) {
        // Repositoryを使ってDB検索したい
        // しかしstaticメソッドではDIしにくい
        return null;
    }
}

DBアクセスが必要な処理は、基本的に Service に書く方が自然です。

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findUser(Long userId) {
        return userRepository.findById(userId)
                .orElseThrow();
    }
}

このようにすると、依存関係が明確になり、テストもしやすくなります。

Spring Bootでstaticを使ってよいケース

Spring Bootだからといって、static を完全に使ってはいけないわけではありません。

以下のようなケースでは、static を使っても問題ないことが多いです。

  • 定数
  • 状態を持たない単純なユーティリティメソッド
  • staticファクトリメソッド
  • 外側のクラスに依存しないstaticネストクラス

例えば、定数は static final で問題ありません。

public class ApiConstants {
    public static final String HEADER_REQUEST_ID = "X-Request-Id";
    public static final int DEFAULT_PAGE_SIZE = 20;
}

また、状態を持たない単純な変換処理であれば、staticメソッドでも問題ないことがあります。

public class StringMaskUtils {
    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }

        String[] parts = email.split("@");
        return parts[0].charAt(0) + "***@" + parts[1];
    }
}

ただし、少しでも以下のような要素があるなら、staticではなくService化を検討した方がよいです。

  • 設定値を使う
  • DBにアクセスする
  • 外部APIを呼ぶ
  • ログインユーザーによって結果が変わる
  • リクエスト情報によって結果が変わる
  • テストで処理を差し替えたい

staticを使いすぎると起きる問題

static は便利ですが、使いすぎると以下のような問題が起きやすくなります。

  • オブジェクト指向の設計から外れやすい
  • 依存関係が見えにくくなる
  • テストで差し替えにくくなる
  • 状態を共有してしまい、不具合の原因になる
  • 複数ユーザー・複数リクエストで値が混ざる可能性がある

特にWebアプリケーションでは、複数ユーザーが同時にアクセスします。

そのため、可変の static フィールドに業務データやログイン情報を持たせるのは避けるべきです。

staticにすべきか迷ったときの実務的な目安

実務では、以下のように考えると安全です。

対象staticにするか
定数static final にすることが多い
引数だけで完結する単純な処理staticメソッドを検討
ユーザーごとの情報staticにしない
リクエストごとの情報staticにしない
DBアクセスが必要な処理ServiceやRepositoryを使う
外部APIを呼ぶ処理Service化を検討
設定値を使う処理Service化を検討
テストで差し替えたい処理staticにしない方がよい

迷った場合は、基本的には static にしない方が安全です。

特にSpring Bootでは、DIを使った設計の方が後から変更しやすく、テストもしやすいです。

まとめ

static は、オブジェクトではなくクラスに所属させるためのキーワードです。

  • 通常フィールド・通常メソッド・・・オブジェクトに所属する
  • staticフィールド・staticメソッド・・・クラスに所属する

static を使う代表的な場面は以下です。

  • 定数
  • ユーティリティメソッド
  • ファクトリメソッド
  • mainメソッド

一方で、以下のようなものは static にしない方がよいです。

  • ユーザーごとの情報
  • リクエストごとの情報
  • 注文や商品などの業務データ
  • ログインユーザー情報
  • DBや外部APIに依存する処理
  • テストで差し替えたい処理

特にSpring Bootでは、staticメソッドに寄せすぎるよりも、ServiceやComponentとしてSpringに管理させ、DIで利用する設計の方が適している場面が多いです。

static にするか迷ったときは、以下の考え方を基準にすると分かりやすいです。

  • オブジェクトごとに値が変わるなら static にしない
  • オブジェクトの状態を使うなら static にしない
  • 全体で共有してよい定数なら static final を使う
  • 引数だけで完結する単純処理なら static を検討する
  • Spring BootでDIしたい処理なら static にしない

static は便利な機能ですが、使いどころを間違えると、データ共有による不具合やテストしにくい設計につながります。

最初のうちは、「全員で共有してよいものだけ static にする」と考えると分かりやすいです。

スポンサーリンク

Java