Javaの弱参照とStringをkeyに持つWeakHashMap
Javaの弱参照について復習していたら、いつものようにひしだまさんのページに行き着きました。「使用失敗例」として紹介されている例が分かりやすかったです。
少しだけコード片を書き足して、動きを確認しました。
参考
コード
import static org.junit.Assert.assertEquals; import java.util.Map; import java.util.WeakHashMap; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class WeakRefSample { private static final Logger logger = LoggerFactory .getLogger(WeakRefSample.class); public int executeFail() { String[] force = createForce(); Map<String, Integer> map = createWeekMapWithNewObject(force); callGc(10); if (map.containsKey(force[0])) { return map.get(force[0]); } return -1; } public int execute() { String[] force = createForce(); Map<String, Integer> map = createWeekMap(force); callGc(10); if (map.containsKey(force[0])) { return map.get(force[0]); } return -1; } public int executeFailEx() { Map<String, Integer> map = createWeekMapEx(); String[] forceEx = createForce(); // mapのkeyと同じ値(≠オブジェクト)を代入 callGc(10); if (map.containsKey(forceEx[0])) { return map.get(forceEx[0]); } return -1; } void callGc(long millis) { System.gc(); try { Thread.sleep(millis); } catch (InterruptedException e) { logger.error("fail in sleep"); } } String[] createForce() { String[] result = new String[10]; for (int i = 0; i < 10; i++) { String key = "Key" + i; result[i] = key; } return result; } Map<String, Integer> createWeekMapWithNewObject(String[] force) { Map<String, Integer> result = new WeakHashMap<>(); for (int i = 0; i < 10; i++) { result.put(new String(force[i]), i); // force配列の要素から新しいオブジェクトを作成してkeyにする } return result; } Map<String, Integer> createWeekMap(String[] force) { Map<String, Integer> result = new WeakHashMap<>(); for (int i = 0; i < 10; i++) { result.put(force[i], i); // force配列の要素をそのままkeyにする } return result; } Map<String, Integer> createWeekMapEx() { String[] force = createForce(); // メソッドのスコープ外では保持されない Map<String, Integer> result = new WeakHashMap<>(); for (int i = 0; i < 10; i++) { result.put(force[i], i); // force配列の要素をそのままkeyにする } return result; } @Test public void testExecuteFail() throws Exception { WeakRefSample target = new WeakRefSample(); assertEquals(-1, target.executeFail()); } @Test public void testExecute() throws Exception { WeakRefSample target = new WeakRefSample(); assertEquals(0, target.execute()); } @Test public void testExecuteFailEx() throws Exception { WeakRefSample target = new WeakRefSample(); assertEquals(-1, target.executeFailEx()); } }
上のコードについて
String配列であるforceの各要素値を、WeakHashMapインスタンスであるmapのkeyに使用します。これが基本形です。
1.「使用失敗例」の再現
executeFailメソッドは、ひしだまさんのページ(http://www.ne.jp/asahi/hishidama/home/tech/java/weak.html)にあった使用失敗例」の再現です。mapの各keyへの参照をforceの要素として保持しているつもりが、実は保持できていなかった様子を表わしています。
executeFailメソッドでmapを作るために呼び出すcreateWeekMapWithNewObjectメソッドでは、mapのkeyとしてforceの要素をそのまま使わず、new String(force[i])で新しいオブジェクトを生成して使います。このため、forceの要素とmapのkeyは、値が同じですがオブジェクトが異なります。普通こんな恣意的なことはしないと思いますが、あくまでも例なので許してにゃん(?)
mapのkeyオブジェクトはmap以外のだれからも参照されていないので、System.gc()の呼び出しでGCが起きた場合、mapの要素が削除されます。その結果、map.containsKey(force[0])はfalseになります。
2. 成功例
executeメソッドは、mapの各keyへの参照を保持し続けた場合です。
String配列のforceをcreateWeekMapメソッドに渡し、createWeekMapメソッドではforceの要素をmapのkeyとしてそのまま使っています。つまり、System.gc()が呼び出された時点で、mapのkeyオブジェクトは、forceからも参照されています。
そのため、mapの要素は削除されず、map.containsKey(force[0])はtrueになり、valueを取り出せます。
3. 1.のより汎用的な失敗例
executeFailExメソッドは、1.と同じく値とオブジェクトの違いに起因する別の失敗例です。(ひどい名前ですが)
createWeekMapExメソッドでは、mapのkeyとなる値を生成するためにString配列のforceを使用します。
もちろんforceは、createWeekMapExメソッドのスコープを抜けると無効になります。したがって、System.gc()が呼び出された時点で、mapのkeyオブジェクトを参照している者はだれもいません。そのため、mapの要素は削除されます。
ちなみに、map.containsKey(forceEx[0])のforceExは、map作成後に定義したString配列なので、mapのkey要素とは無関係です。
そんな復習でした。