読者です 読者をやめる 読者になる 読者になる

Status Code 303 - See Other

Java, C, C++, C#, Objective-C, Swift, bash, perl, ruby, PHP, Python, Scala, Groovy, Go, DevOps, Raspberry Pi など。情報の誤りや指摘・意見などは自由にどうぞ。

static メソッドによるインスタンス生成

Java コード改善

概要

static メソッドとは、オブジェクト指向言語において、インスタンスを生成せずとも
処理を実行できるクラスに関連づけられたメソッドである。

使用例としては、便利な機能を提供する Util クラスを作成する場合に用いられ、
Java ライブラリでは、java.lang.Math、java.util.Collections クラスなどがそれにあたる。

これ以外にも、インスタンス生成をコントロールする場合実装の内部情報を隠す際にも用いられる。
この場合についての説明を記述する。

詳細説明

比較用クラス

トランプのクラスを例として説明する。まず、次のCardクラスを2通りの方法で作成する。

一般的な構造をしたCardクラス
// カードクラス
public class Card {

	// スーツ
	public String suit;
	// 番号 A->1, 2->2, ..., 10->10, J->11, Q->12, K->13とする
	public int num;

	// コンストラクタ
	public Card(String suit, int num) {
		this.suit = suit;
		this.num = num;
	}
}

クライアントがCardクラスを利用する場合のコード記述例。

	Card clobAce = new Card("clob", 1);
staticメソッドでCardクラスを取得するよう変更
// カードクラス
public class Card {

	// スーツ
	private String suit;
	// 番号 A->1, 2->2, ..., 10->10, J->11, Q->12, K->13とする
	private int num;

	// ここを変更! プライベートコンストラクタ
	private Card(String suit, int num) {
		this.suit = suit;
		this.num = num;
	}

	// ここを追加! インスタンス生成用メソッド
	public static Card getCard(String suit, int num) {
		return new Card(suit, num);
	}
}

クライアントでは次の呼び出し方法となる。

	Card clobAce = Card.getCard("clob", 1);	

static メソッド利点1. インスタンスを毎回作成しなくて良い

もし作成するインスタンスが次のようだったとしよう。

  • 作成にコスト(生成時間/メモリ)がかかる
  • 同じインスタンスを多数回作成
  • 一度作成すればそれらを使い回せる

このような場合、既存インスタンスを使い回したいと思うだろう。
しかし、コンストラクタは必ずインスタンスを作成してしまうため実現できない。

staticメソッドであれば、メソッド内でその制御を行うことにより実現できる。

	// インスタンスをプールする
	private static Map<String, Card> pool = new HashMap<String, Card>();

	// インスタンス生成用関数
	public static Card getCard(String suit, int num) {
		String id = suit + num;
		if (!pool.containsKey(id)) {
			pool.put(id, new Card(suit, num));
		}
		return pool.get(id);
	}

これによって、以下のパフォーマンス改善が見込まれる。

static メソッド利点2. 適切な名前が使える

このCardクラスは、修正されることになった。それは ジョーカーの追加だ。
では、staticメソッドを用いない場合のジョーカーを作成するとしよう。

今回は suitが null でかつ num が 0 ならジョーカーだという仕様にしたとする。
こんな仕様を理解できるのは、説明書きを読むか、実際のコードを見ないと分からない。
しかし、クライアントはジョーカーを扱う場合、以下のように書かねばならない。

	// これが ジョーカーだ!  ← 分かるかー!!
	Card joker = new Card(null, 0);

これをstaticメソッドを使って書き直す場合。Cardクラス定義に以下を追加、

	// ジョーカー作成用メソッド
	public static Card getJoker() {
		return new Card(null, 0);
	}

クライアントは次のように変更する。

	// これが ジョーカーだ!
	Card joker = Card.getJoker();

この実装により、クライアントのコードは非常に分かりやすくなる。
適切なメソッド名が使えるのは、staticメソッドを使ううえで重要な利点だ。

static メソッド利点3. インスタンスの内部情報を隠す

さきほどのコードでは、ジョーカーの内部情報(suitとnum)をクライアントが直接記述している。
今後、ジョーカーの仕様を変更すれば、これらを利用しているクライアントのプログラムも変更しなければならない。

つまり、クライアントに影響がでないようにしたいなら、今後その仕様を変更しないになる。
それは今後修正されるであろう、パフォーマンスの改善もできなくなることを意味する。

それに比べ、static メソッドによる実装は内部情報を隠しているため、
今後ジョーカーの内部情報を変更しても、クライアントのプログラムには影響を及ぼさない。

static メソッド利点4. 生成するインスタンスを変更

絵柄カード(J, Q, K)は、他のカードとは異なる性質を有する場合がある。
例えば、ブラックジャックでは、他の数カードと異なり、Aは1か11、J,Q,K は10になる。

カードによって挙動が異なる場合は、Cardクラスを継承した
FaceCardクラス(コードでは省略)を新たに作成すべきだろう。

そして、static メソッドを用いた場合は、CardクラスだけではなくFaceCardクラスのインスタンスを返しても良い。

	// インスタンス生成用メソッド
	public static Card getCard(String suit, int num) {
		if (10 < num && num < 14) {
			return new FaceCard(suit, num);
		}
		return new Card(suit, num);
	}

このようなことは、コンストラクタを用いたインスタンスの生成では不可能だ。
なぜなら、コンストラクタは実装クラスが何かを公開しているから。

これがstaticメソッドを用いた場合、クライアントは、Cardクラスのファミリーの何かが返ってくると認識する。
つまり、ファミリー(Cardクラスを継承している)なら、今後何を返されても構わないのだ。

static メソッド利点5. 実装クラスをクライアントに使わせない

publicクラスは、誰でも使用でき、継承して新しいクラスを作成することもできる。
複数人開発やフレームワーク提供などの場合、それが開発者がたとえ意図していなくとも、
クライアントはコンストラクタからインスタンスを作成して利用できる。

もし、クライアントが実装クラスを直接使用したなら、
今後そのクラスが不要になって削除した途端クライアントのプログラムに影響がでてしまう。

このような事態を防ぐには以下を行う。

  1. 実装クラスのコンストラクタを private にして他人に公開しない
  2. staticメソッドでのみ実装クラスを提供

結論

プログラムは設計段階では、今後変更されるかもしれない箇所が予想できる場合がある。

それは、機能追加やパフォーマンス/セキュリティ上の問題解決かもしれないが、
いずれにせよ、それを改善するための方法は、大体は以下を行う事である。

これらの変更をクライアントに今後意識させたくなければ、
static メソッドを用いたインスタンス生成を行うことを考慮した方が良い。
そうすることで、今後の追加やメンテナンスに対して多大なコストをかけずに変更できる。

あとがき

基本的に下記書物の最初の項に具体例を入れてまとめただけです(^^;

参考図書

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)