【java】Listについて
勘違いしていたというか、内心ちょびっとだけ「そんなにうまくはいかないか」と思っていたら実際そうだったんだが、
java.util.Listは=で同じ型の別変数に移しても内容が維持されるらしい。
2つの異なる変数間で同じメモリの内容を共有するようになるというか。
例えば、List(変数名:list)で5つの要素を格納した後、
別のList(変数名:list2)にlistをそのまま移して、
list2側でremoveかけると、list2もlistも要素数が4になる(list2だけ5⇒4に減るわけではない)。
たとえば以下のようなプログラムを作る
import java.util.*;public class ListTest {
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("あいうえお"); list.add("かきくけこ"); list.add("さしすせそ"); list.add("たちつてと"); list.add("なにぬねの"); List<string> list2 = list; System.out.println("★変数単純退避後"); System.out.println("list.size() = " + list.size() + " , list2.size() = " + list2.size()); list2.remove(0); System.out.println("★list2側要素0番削除後"); System.out.println("list.size() = " + list.size() + " , list2.size() = " + list2.size()); }
}
これの実行結果は以下のようになる↓
★変数単純退避後
list.size() = 5 , list2.size() = 5
★list2側要素0番削除後
list.size() = 4 , list2.size() = 4 ←list2のremove操作でlistも要素数が減ってしまっている
あるListの内容を別変数に移して、かつ両者で独立した(片方の操作により同じような要素数の増減をされないような)動きをするためには
一度変数を用意してからaddAllで要素を詰め込めばよいようである。
import java.util.*;public class ListTest2 {
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("あいうえお"); list.add("かきくけこ"); list.add("さしすせそ"); list.add("たちつてと"); list.add("なにぬねの"); List<string> list3 = new ArrayList<string>(); list3.addAll(list); System.out.println("★addAll後"); System.out.println("list.size() = " + list.size() + " , list3.size() = " + list3.size()); list.remove(0); System.out.println("★list側remove後"); System.out.println("list.size() = " + list.size() + " , list3.size() = " + list3.size()); }
}
★addAll後
list.size() = 5 , list3.size() = 5
★list側remove後
list.size() = 4 , list3.size() = 5 ←listのremove操作ではlist3の要素数は減らない
ただjavaのAPIによると、addAllは内部的にIteratorで要素をもってきて別Listに格納している風なことを書いている。
実際に中身まで見てないけど、addAll後は、要素数は同じでも、中身の順序は一致しない(一致する保証がない)のだろう。
ちなみに最初のパターンは下記のようにしても同じ結果になった
//List<String> list2 = list; List<String> list2 = new ArrayList<String>(); list2 = list;
remove後にhashCodeと==による比較を試みたが、完全一致していることもわかった(要するに、全く同じ変数だった)
System.out.println("list.hashCode() = " + list.hashCode() + " , list2.hashCode() = " + list.hashCode()); System.out.println("list == list2 = " + String.valueOf(list == list2));
list.hashCode() = -287403501 , list2.hashCode() = -287403501 list == list2 = true
これはListにしか起きないんだっけ?(他のクラスも「こうゆう記述したらそうなるにきまってるじゃんあほなの?」というのが常識だったっけ?)と
ふと疑問に思ってStringでためしてみたけど、同じことは起きなかった。
public class StringTest {public static void main(String[] args) { String test = "あいうえお"; String test2 = test; System.out.println("test = " + test + " , test2 = " + test2); System.out.println("test.hashCode() = " + test.hashCode() + " , test2.hashCode() = " + test2.hashCode()); System.out.println("test.equals(test2) = " + test.equals(test2)); System.out.println("test == test2 = " + String.valueOf(test == test2)); test = test.substring(0,3); System.out.println("test = " + test + " , test2 = " + test2); System.out.println("test.hashCode() = " + test.hashCode() + " , test2.hashCode() = " + test2.hashCode()); System.out.println("test.equals(test2) = " + test.equals(test2)); System.out.println("test == test2 = " + String.valueOf(test == test2)); }
}
test = あいうえお , test2 = あいうえお test.hashCode() = -1095354298 , test2.hashCode() = -1095354298 test.equals(test2) = true test == test2 = true test = あいう , test2 = あいうえお test.hashCode() = 12267588 , test2.hashCode() = -1095354298 test.equals(test2) = false test == test2 = false
この例では、文字列"あいうえお"をtestに定義して、単純退避(String test2 = test)して複製した後、
test側で先頭3文字だけ切り出して(substring(0,3)を実行)みる。
「単純退避(String test2 = test)して複製」した直後はhashCodeも同じ値で、要するに「同じ変数」だが、
複製元の切り出しを行った後は複製元だけが別の変数になって、
複製先(test2)とは別になる。
やっぱりListで起きるようなことは(少なくとも)Stringでは起きないようだ。
実装元のインターフェースとかが関係しているのか。
SetとかMapでも同じこと起きるのかな。
あんまり意識してなかったが。
※ちなみにLinkedListでも同じことは起きた。