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要素とは無関係です。

そんな復習でした。