GoFのデザインパターンの中にはごく限られた場面でしか使えそうにないパターンがいくつかあり、その一例がFlyweightです。しかし、Flyweightは、Stringオブジェクトの管理に使われるなど、Java自体が大きく依存しているデザインパターンです。本稿では、Flyweightパターンの特徴や利用方法を解説します。
Flyweightパターン
GoFのデザインパターン本では、ソフトウェア開発でよくある問題についての23パターンの解決策が名前付きで紹介されています。著者の場合も、長年の間にこれらのパターンに何度となくお世話になりました。CommandやTemplate Methodなどのパターンは、今でも高く評価しています。
もっとも、なかにはごく限られた場面でしか使えそうにないパターンもいくつかあります。その一例がFlyweightです。しかし、FlyweightはJava自体が大きく依存しているデザインパターンなのです。
Wikipediaによれば、Flyweightパターンは、「多数のオブジェクトを操作する必要があり、これらのオブジェクトに余計なデータを持たせる余裕がない」場合に適しています。Javaでは、StringオブジェクトがFlyweightパターンで管理されています。Javaの固定Stringリテラルはすべてリテラルプールに格納され、重複するリテラルについてはプール内にコピーが1つだけ保持されます。JUnitで作成した簡単な「言語テスト」を使用してこのことを説明しましょう。
@Test public void pool() { String author = "Henry David Thoreau"; String authorCopy = "Henry David Thoreau"; assertTrue(author.equals(authorCopy)); assertTrue(author == authorCopy); }
2つのStringオブジェクトの内容は同じなので、当然この2つのオブジェクトは意味的に等価です。最初のアサーションでは、equals()を呼び出すことによって2つのオブジェクトの等価性を確認しています。実は、この2つのオブジェクトはメモリ内でも同じ場所に格納されています。2番目のアサーションでは、authorとauthorCopyの参照(アドレス)を比較しています。
2つのStringオブジェクトは別々に生成されていますが、Javaは密かにこれらのオブジェクトを同じ場所に格納して、メモリ空間を節約しています。これにより、かなりのメモリ節約効果が期待できます。プロファイリングにより、標準的なアプリケーションで生成されるすべてのオブジェクトのうち、3分の1から2分の1はStringオブジェクトであるということがわかっています。
余談ですが、JavaではこのようにFlyweightパターンを用いてStringの格納領域を最小限に抑えているため、経験の少ないプログラマが大きな失敗をすることがよくあります。2つの参照を比較して、その参照が同じ文字の並びを保持しているかどうかを判定する場合に、Javaプログラミングの初心者は次のようなコーディングをしがちです。
if (author == authorCopy)
前述のテストで示したように、この比較は成立します。この2つのオブジェクトがJavaアプリケーションの実行フロー中の別々のタイミングで生成された場合でも、この比較が真を返すことがあります。しかし残念ながら、Javaでは、動的に生成されたStringはリテラルプールに自動的に格納されません。次のテストでは、最後のアサーションは失敗します。
@Test public void pool() { String author = "Henry David Thoreau"; String authorCopy = "Henry David Thoreau"; assertTrue(author.equals(authorCopy)); assertTrue(author == authorCopy); StringBuilder builder = new StringBuilder("Henry"); builder.append(" David Thoreau"); assertTrue(author.equals(builder.toString())); // this assertion fails! assertTrue(author == builder.toString()); }
Stringリテラル「Henry David Thoreau」はコンパイル時に抽出可能であるため、このリテラルはリテラルプールに存在します。しかし、コンパイルの時点では、このコードを実行した結果として「 David Thoreau」が「Henry」リテラルに連結されることは保証されません。理論上は、コンパイラでこのような例を解決することも可能ですが、実際に行った場合にはコンパイル時間が著しく長くなるおそれがあります。また、このような方法が機能するのは、ごく単純な場合に限られています(動的に生成された文字列を強制的にリテラルプールに格納するには、Stringのintern()メソッドを使用して新しいStringを生成します)。
そこでベストプラクティスとして、Stringの参照を比較するときは必ずequals()を使うようにしましょう。
この記事は参考になりましたか?
- デザインパターンの使い方連載記事一覧
- この記事の著者
-
japan.internet.com(ジャパンインターネットコム)
japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
Jeff Langr(Jeff Langr)
本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle Bob Martin著、Prentice Hall、2008年8月)にも寄稿している。また、ソフトウェア開発に関する記事を80件以上執筆しており、そのうちの...
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
