Javaのstaticメソッドの使いどころ

はじめに

先日のJJUGナイトセミナー『オブジェクト指向プログラミング入門』(アーカイブ動画)の後半の座談会で、staticメソッドの利用の是非についての話題があった。その中で、ふだんstaticメソッドを使うのは、コンストラクタの代わりのファクトリメソッドと、あとはユーティリティくらいかなというのがあった。

個人的にもその2つがメインかなと思い、今担当しているプロダクトやJDKの標準ライブラリをサンプリングしてみた。結果としては、やはりその2つのパターンで90%以上を占めているイメージ。

static ファクトリメソッド

実はこれは、『Effective Java 第3版』の最初の項目に登場する最初のテクニック(項目1 コンストラクタの代わりにstaticファクトリメソッドを検討する)である。
例としてBoolean オブジェクトのファクトリメソッドが挙げられている。

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

static ファクトリメソッドの長所が5つ挙げられている。

  1. コンストラクタと異なり、名前を持つこと
  2. コンストラクタと異なり、その呼び出しごとに新たなオブジェクトを生成する必要がないこと
  3. コンストラクタと異なり、メソッドの戻り値型の任意のサブタイプのオブジェクトを返せること
  4. 返されるオブジェクトのクラスは、入力パラメータの値に応じて呼び出しごとに変えられること
  5. 返されるオブジェクトのクラスは、その static ファクトリメソッドを含むクラスが書かれた時点で存在する必要させないこと

それぞれの長所が具体的に何なのかは書籍を読んでもらうとして、特に1つ目のメリットが最大の動機となるだろう。
例として、JDKのjava.time.LocalDate のJavaDocを見てみよう。

シンプルなof のほか、現在の日付を生成するnow 、エポック日数から日付を生成するofEpochDay など、名前を見ただけでどのようなオブジェクトが生成されるのか想像がつく。
もしこの9個がすべてコンストラクタで実装されていたら、必ずJavaDocを読んで目的のものを探さねばならない。
また、コンストラクタの場合、引数の数と型(シグネチャ)がバッティングしてしまう可能性もある。

コンストラクタが2個以上必要になったら、static ファクトリメソッドの導入を検討してみるとよいと思う。

ユーティリティ

ユーティリティ(メソッド)とは、お便利メソッドのことである。アプリケーション内で共通的に利用される処理のことだ。通常、似た種類のユーティリティメソッドを集めてユーティリティクラスが作成される。
そのようなクラスはXXXUtil XXXUtils という命名がなされることが多い。StringUtils CollectionUtils などはいろんなライブラリ中に含まれていて、IDEで検索すると何十個と候補が出てきたりする。

JDKの言語標準のAPIでUtils と名付けるのは憚れるのだろうか。JDKのユーティリティクラスは ObjectsCollectionsといった名前になっている。
ObjectsのJavaDocには、それがユーティリティであることが明記されている。

このクラスは、オブジェクトで操作するためのstaticユーティリティ・メソッドで構成されます。このようなユーティリティには、オブジェクトのハッシュ・コードを計算したり、オブジェクトを表す文字列を返したり、…(以下略)

何でもユーティリティ症候群

ユーティリティをstaticメソッドで提供することは、基本的に問題ない。ただ、アプリケーション開発の現場では、何でもかんでもがユーティリティ扱いされ、XXXUtilsが不用意に乱立してしまうことが多いのが問題だ。

DRY原則に則り、アプリケーション内で繰り返し使われる処理を切り出して共通化する。それ自体は正しい。
大切なのは、ユーティリティとしてstaticメソッドで提供すべきものと、そうではなくオブジェクトとして役割を与えるものと区別をしないといけないのだ。

悪いユーティリティ

例えば以下のクラスを考える。

public class DateUtils {

    public static LocalDate getCurrentDate() {
        // 当システムでは日付はUTCで扱う
        return LocalDate.now(ZoneOffset.UTC);
    }
}

アプリケーション内で日付を必要とする処理は、必ずこのDateUtils#getCurrentDateを呼び出すことになっている。

public class DateClient {

    public void someMethod() {
        LocalDate now = DateUtils.getCurrentDate();
        // nowを使った処理
        // ...
    }
}

職場の先輩から、上記処理に対してユニットテストを書くように言われたとする。テストを書くのは困難だ。DateUtils#getCurrentDateはOSが管理する実際のシステム日付を返すため、テストを実行する度に値が変わるからだ。

「PowerMock(あるいはMockito 3.4以降)を使ってるからstaticメソッドもモック(スタブ)できるぜ、ラッキー!」って? それは間違っている。そもそも環境や状態に依存する処理をユーティリティとしていることが問題なのだ。

この例で言うと、「日付を生成する役割」をインタフェースとして捉え、実装はすげ替えられるような設計にするのが、オブジェクト指向プログラミングとしては正しい。実は、JDKにはズバリClockというインタフェースが存在する(実際は抽象クラス)。

先程のDateClientは以下のように書き直すことができる。

public class DateClient {

    private Clock clock;

    public DateClient(Clock clock) {
        this.clock = clock;
    }

    public void someMethod() {
        LocalDate now = LocalDate.now(clock);
        // nowを使った処理
        // ...
        System.out.println(now);
    }
}

ポイントは、Clockの実体はコンストラクタ引数で外から渡すことで、直接具体的な実装に依存しないようにしている(6行目)。いわゆる依存性注入(Dependency Injection: DI)パターンの適用だ。
そして、LocalDate#nowという static ファクトリメソッドを、引数にClockオブジェクトを指定して呼び出すことでLocalDateのインスタンスを取得する(10行目)。

このような設計にすれば、プロダクトコードから呼び出す際と、テストコードから呼び出す際とでClockの実装を交換することができるため、開発者によって自由に制御がかけられるのだ。
ちなみにDIコンテナとしてSpringを利用している場合、Clockのコンポーネント(プロダクトコード用)は以下のように登録されるだろう。

    @Bean
    public Clock clock() {
        // 当システムでは日付はUTCで扱う
        return Clock.system(ZoneOffset.UTC);
    }

繰り返しになるが、環境や状態に依存する処理をユーティリティとして扱いstaticメソッドにするのは避けよう。
現場で見かける具体例としては、以下のようなものだ。

  • Webのセッションオブジェクト、スレッド変数などにアクセスする処理
  • ランダムな値を生成する処理
  • DIコンテナのAPIを呼び出す処理(SpringのApplicationContextなど)

良いユーティリティ

良いユーティリティ、というかユーティリティメソッドが満たすべき条件は以下と考える。

副作用のない、純数な関数であること。
言い換えると、関数の出力値が入力値によってのみ決まること。

まとめ

  • Javaで staticメソッドを利用する代表的なユースケースは、 static ファクトリメソッドとユーティリティメソッド
  • static factoryメソッドはソースの可読性を向上するなどメリットが多いので積極的に活用しよう
  • ユーティリティメソッドは副作用があってはいけない

参考文献