1. 概要

Javaコンストラクターは、完全に初期化されたクラスインスタンスを取得するためのデフォルトのメカニズムです。 結局のところ、これらは、手動または自動で依存関係を注入するために必要なすべてのインフラストラクチャを提供します。

それでも、いくつかの特定のユースケースでは、同じ結果を達成するために静的ファクトリメソッドに頼ることが望ましいです。

このチュートリアルでは、静的ファクトリメソッドとプレーンな古いJavaコンストラクターを使用することの長所と短所を強調します。

2. コンストラクタに対する静的ファクトリメソッドの利点

Javaのようなオブジェクト指向言語では、コンストラクターの何が問題になる可能性がありますか? 全体的に、何もありません。 それでも、有名なジョシュアブロックの効果的なJavaアイテム1は明確に述べています。

「コンストラクターではなく静的ファクトリメソッドを検討してください」

これは特効薬ではありませんが、このアプローチを維持する最も説得力のある理由は次のとおりです。

  1. コンストラクターには意味のある名前がないので、コンストラクターは常に言語によって課される標準の命名規則に制限されます。 静的ファクトリメソッドは意味のある名前を持つことができる、したがってそれらが何をするかを明示的に伝える
  2. 静的ファクトリメソッドは、メソッド、サブタイプ、およびプリミティブを実装するのと同じ型を返すことができるため、より柔軟な範囲の戻り型を提供します。
  3. 静的ファクトリメソッドは、完全に初期化されたインスタンスの事前構築に必要なすべてのロジックをカプセル化できるため、この追加のロジックをコンストラクターから移動するために使用できます。 これにより、コンストラクターは、フィールドを初期化するだけでなく、それ以上のタスクを実行できなくなります
  4. 静的ファクトリメソッドは制御インスタンスメソッドであり、シングルトンパターンはこの機能の最も明白な例です。

3. JDKの静的ファクトリメソッド

JDKには、上記で概説した多くの利点を示す静的ファクトリメソッドの例がたくさんあります。 それらのいくつかを調べてみましょう。

3.1. Stringクラス

よく知られているStringinterning のため、Stringクラスコンストラクターを使用して新しいStringオブジェクトを作成することはほとんどありません。 それでも、これは完全に合法です。

String value = new String("Baeldung");

この場合、コンストラクターは新しい String オブジェクトを作成します。これは、予想される動作です。

または、静的ファクトリメソッドを使用して新しいStringオブジェクトを作成する場合は、 valueOf()メソッドの次の実装のいくつかを使用できます。

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

valueOf()にはいくつかのオーバーロードされた実装があります。 メソッドに渡される引数のタイプに応じて、それぞれが新しい String オブジェクトを返します(例: int long boolean char、など)。

この名前は、メソッドの機能を非常に明確に表しています。 また、静的ファクトリメソッドに名前を付けるためのJavaエコシステムで確立された標準に準拠しています。

3.2. オプションクラス

JDKの静的ファクトリメソッドのもう1つの優れた例は、オプションクラスです。 このクラスは、 empty() of() ofNullable()など、かなり意味のある名前を持ついくつかのファクトリメソッドを実装します。

Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Baeldung");
Optional<String> value3 = Optional.ofNullable(null);

3.3. コレクションクラス

おそらくJDKの静的ファクトリメソッドの最も代表的な例はCollectionsクラスです。これは静的メソッドのみを実装するインスタンス化できないクラスです。

これらの多くは、提供されたコレクションに何らかのアルゴリズムを適用した後、コレクションも返すファクトリメソッドです。

クラスのファクトリメソッドの典型的な例を次に示します。

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List<Integer> unmodifiableList = Collections.unmodifiableList(originalList);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(originalMap);

JDKの静的ファクトリメソッドの数は非常に多いため、簡潔にするために例のリストは短くしておきます。

それにもかかわらず、上記の例は、Javaでユビキタスな静的ファクトリメソッドがどのように存在するかを明確に示しているはずです。

4. カスタム静的ファクトリメソッド

もちろん、独自の静的ファクトリメソッドを実装できます。しかし、プレーンコンストラクタを介してクラスインスタンスを作成する代わりに、本当にそうする価値があるのはいつですか?

簡単な例を見てみましょう。

この素朴なUserクラスについて考えてみましょう。

public class User {
    
    private final String name;
    private final String email;
    private final String country;
    
    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }
    
    // standard getters / toString
}

この場合、静的ファクトリメソッドが標準のコンストラクタよりも優れている可能性があることを示す警告は表示されません。

すべてのUserインスタンスがcountryフィールドのデフォルト値を取得するようにするにはどうすればよいですか?

フィールドをデフォルト値で初期化する場合、コンストラクターもリファクタリングする必要があるため、設計がより厳密になります。

代わりに静的ファクトリメソッドを使用できます。

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

countryフィールドにデフォルト値が割り当てられたUserインスタンスを取得する方法は次のとおりです。

User user = User.createWithDefaultCountry("John", "[email protected]");

5. ロジックをコンストラクターから移動する

User クラスは、コンストラクターにロジックを追加する必要がある機能を実装することにした場合、すぐに欠陥のある設計になってしまう可能性があります(この時点でアラームベルが鳴るはずです)。

すべてのUserオブジェクトが作成された時刻をログに記録する機能をクラスに提供するとします。

このロジックをコンストラクターに入れるだけでは、単一責任の原則に違反することになります。 最終的には、フィールドの初期化以上のことを行うモノリシックコンストラクターになります。

静的ファクトリメソッドを使用して、設計をクリーンに保つことができます。

public class User {
    
    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;
    
    // standard constructors / getters
    
    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }
}

改善されたUserインスタンスを作成する方法は次のとおりです。

User user 
  = User.createWithLoggedInstantiationTime("John", "[email protected]", "Argentina");

6. インスタンス制御のインスタンス化

上に示したように、完全に初期化された User オブジェクトを返す前に、ロジックのチャンクを静的ファクトリメソッドにカプセル化できます。 そして、複数の無関係なタスクを実行する責任を持つコンストラクターを汚染することなく、これを行うことができます。

例えば、 Userクラスをシングルトンにしたいとします。 これは、インスタンス制御の静的ファクトリメソッドを実装することで実現できます。

public class User {
    
    private static volatile User instance = null;
    
    // other fields / standard constructors / getters
    
    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

getSingletonInstance()メソッドの実装は、スレッドセーフですが、同期されたブロックにより、パフォーマンスがわずかに低下します。

この場合、レイジー初期化を使用して、インスタンス制御の静的ファクトリメソッドの実装を示しました。

ただし、シングルトンを実装する最良の方法はJava列挙型を使用することです。これは、シリアル化とスレッドセーフの両方であるためです。 さまざまなアプローチを使用してシングルトンを実装する方法の詳細については、この記事を確認してください。

予想どおり、このメソッドで User オブジェクトを取得することは、前の例と非常によく似ています。

User user = User.getSingletonInstance("John", "[email protected]", "Argentina");

7. 結論

この記事では、静的ファクトリメソッドがプレーンJavaコンストラクターを使用するよりも優れた代替手段となる可能性があるいくつかのユースケースについて説明しました。

さらに、このリファクタリングパターンは、典型的なワークフローに密接に根ざしているため、ほとんどのIDEがそれを実行してくれます。

もちろん、 Apache NetBeans IntelliJ IDEA 、および Eclipse はわずかに異なる方法でリファクタリングを実行するため、最初にIDEのドキュメントを確認してください。

他の多くのリファクタリングパターンと同様に、静的ファクトリメソッドを慎重に使用する必要があります。これは、より柔軟でクリーンなデザインを作成することと、追加のメソッドを実装するコストとの間のトレードオフに値する場合に限ります。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubから入手できます。