Stringオブジェクトに関する罠
Stringオブジェクトに関しても、Booleanオブジェクトと同様に不変オブジェクトであるが、同一の文字列を表現する異なるStringオブジェクトを生成できてしまう。
生成されてもすぐに捨てられるのなら問題はあまりないのだが、セッションデータとして保持し続けるなど比較的長い時間生存するオブジェクトとなる場合には注意が必要。同じ文字列を表すStringオブジェクトが1つしかないのと10万個あるのとでは大違いだから。
で、Javaの基本的なクラスのメソッドなどで「同一の文字列を表現する異なるStringオブジェクト」が生成されるケースを少し調べてみた。
まぁ、どれもそういう結果になるのは当然なんだけど、ちゃんと意識しないと無駄なメモリを使っちゃうんだぞ、と。
シリアライズ/デシリアライズ
例えば、JBossCacheのように複数のサーバー間でのオブジェクトのやり取りにシリアライズ/デシリアライズを使うようなケースが該当する。
PipedInputStream pin = new PipedInputStream(); PipedOutputStream pout = new PipedOutputStream(pin); ObjectOutput out = new ObjectOutputStream(pout); ObjectInput in = new ObjectInputStream(pin); String s1 = "Hello"; out.writeObject(s1); String s2 = (String) in.readObject(); System.out.println( System.identityHashCode(s1) + " <==> " + System.identityHashCode(s2)); System.out.println("same? " + (s1 == s2));
これを実行すると次のようになる。
6413875 <==> 21174459 same? false
String#split(String pattern)
分割した結果、配列の複数の要素が同じ文字列を表すStringオブジェクトとなるケースを考えてみる。
String str = "a:a:a"; String[] ss = str.split(":"); for (String s : ss) { System.out.printf("%8s: %10d %10d\n", "\"" + s + "\"", System.identityHashCode(s), System.identityHashCode(s.intern())); }
これを実行すると次のようになる。
"a": 827574 17510567 "a": 6718604 17510567 "a": 8918002 17510567
String#substring(int beginIndex, int endIndex)
同じStringオブジェクトに対して、同じインデックスを指定するsubstring()メソッド呼び出しを複数。
String str = "Hello World!!"; System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7))); System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7))); System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7))); System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7)));
これを実行すると次のようになる。
30771886 8637543 14718739 14577460
Integer#toString(int num)
Integerクラスを使うのはあくまでも代表として。
まぁ、staticメソッドなので当然と思う度合いは高いだろうけど。
System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1))); System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1))); System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1))); System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1)));
これを実行すると次のようになる。
22474382 4699264 26954214 1102920
Integer#toString()
こっちはインスタンスメソッド。同一オブジェクトに対して複数回呼んでみる。
Integer num = new Integer(1); System.out.printf("%10d\n", System.identityHashCode(num.toString())); System.out.printf("%10d\n", System.identityHashCode(num.toString())); System.out.printf("%10d\n", System.identityHashCode(num.toString())); System.out.printf("%10d\n", System.identityHashCode(num.toString()));
これを実行すると次のようになる。
14737862 444956 29303146 19581314
で‥
これだけ並べておけば、大概のケースで「同一の文字列を表現する異なるStringオブジェクト」が生成されるんだということを意識付けできるだろう(謎)