━目次━
連載4日目最終日です。1~3日目がまだの人は1日目から読むことをオススメします。
全体はこんな感じ。
【1日目】値(プリミティブ)型と参照(オブジェクト)型の違いをコードと図で解説
【2日目】参照渡しとは?参照型の変数を代入した時の挙動を押さえよう!
【3日目】==とequalsの違いは図で理解すれば絶対間違えない
【4日目】イミュータブルな変数ってなんだ?String型変数の特殊性を知る
参照周りで押さえておきたい内容について4回に渡って解説していきます。
1~3日目の話で値(プリミティブ)型と参照(オブジェクト)型の違いやメモリの使い方など解説してきたので、
ここまで読んだ人はかなりイメージが沸いているのではないでしょうか。
しかしこれまで話した内容を頭に入れつつ1つ気を付けないければならないのが
String型変数の特殊性
です。
最後にString変数の特殊性を押さえて4日間の集中講座を終わりとしましょう。
それでは最終日いきましょう~(/・ω・)/
イミュータブルな変数とは?
イミュータブルとはJavaのみでなく、全てのプログラムを組む上で大事な考え方です。
意味は「不変性」という意味で、対極の言葉としてミュータブル(変性)という言葉もあります。
1度定義したオブジェクトなどを変えないという思想ですね。
定数みたいなもんか?
ん~まぁイメージはそんな感じかな!
定数の場合は明示的に final などを付けたりしますが、Javaでは仕様としてイミュータブル(不変)となっている型が存在します。
その代表がString型です。
解説していきましょう。
String型はイミュータブル(不変)である!
ちょっと待てい!Stringは変えれるじゃろ!わしのコードを見てみい!
1 2 3 4 5 |
String str1 = "儂は天才"; System.out.println(str1); // 儂は天才 str1 = "儂は超天才"; System.out.println(str1); // 儂は超天才 |
ほれ!str1の中身変わっとるじゃないか(ドヤ顔)
確かに一見str1の値が書き換えられているように見えるね。それはJavaが使い易いように中で上手くやってくれているからなんだよね。
どういうことじゃ??
実際に何が起きているか図で確認してみよう。
まずStringは参照(オブジェクト)型なので、1~3日目で解説してきたように
スタック領域に参照値
ヒープ領域に実際の値
が格納されます。
(newしないで使えるのはJavaが使いやすいようにしてくれているから)
ここからが大切で、str1に別の値を代入したときに内部的に何が起こっているかというと、こんなことが起きています。
str1に”い”という文字列を代入しようとした時、Stringがイミュータブル(不変)なので、
新たな参照値が確保され別の領域に”い”という値が保持される
のです。
えーー?ほんとに??
と思った方は以下のコードで確認することができます。
(3日目の内容は頭に入れておいてください)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
String str1 = "あ"; // str2にstr1の参照値をコピー String str2 = str1; // str1の値を変更 str1 = "い"; // ==で参照値を比較 String msg = ""; if (str1 == str2) { msg = "==で一緒です。"; } else { msg = "==で違います。"; } System.out.println(msg); // ==で違います。 |
もしstr1に”い”を代入した時点で値を上書きするだけなら、参照値の比較は一緒ですとなるはずです。
ですが”い”を代入した時点で新たな参照が作成されているため、str1とstr2の参照値は異なるということが分かります。
これがString型がイミュ―タブル(不変)であるということです。
String型は再利用される!
もう1つ覚えておかないといけないのはString型は再利用されるということです。
変数に値を代入する時、もしその値が既に存在すればその参照が使われる
ということになります。
???さっぱりじゃ
これも図で説明しよう
String型の変数にある値を代入しようとした時、Javaではヒープ領域にその値が既にあるかどうかをチェックします。
●既に存在する場合
その参照を割り当てる
●存在しない場合
新たな参照を割り当てる
という動きになります。
String型においてメモリを効率良く使いまわすためにこうなっています。
これもコードで確認することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 違う変数に同じ"あ"という値を代入 String str1 = "あ"; String str2 = "あ"; // ==で参照値を比較 String msg = ""; if (str1 == str2) { msg = "==で一緒です。"; } else { msg = "==で違います。"; } System.out.println(msg); // ==で一緒です。 |
「==で一緒です。」と出力され、異なる変数名で同じ参照値を持っていることが分かります。
因みにStringはnewを省略できますが、newを記述することもできます。(まぁ使うことはありませんが…)
その場合は「必ず新しい参照を作成する」という意思表示になります。以下のコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// newして違う変数に同じ"あ"という値を代入 String str1 = new String("あ"); String str2 = new String("あ"); // ==で参照値を比較 String msg = ""; if (str1 == str2) { msg = "==で一緒です。"; } else { msg = "==で違います。"; } System.out.println(msg); // ==で違います。 |
明示的にnewした場合は、参照が再利用されていないことが分かりますね。
ややこしいのぉ~
こういったイミュータブルや再利用の特性があるため、
String型の比較はequalsで!
とルール化されているわけです。意図しない比較になる可能性があるためです。
String型を扱うときに注意すること
以上2点の特殊性を頭に入れておくことで、String型を扱う時に注意するべきポイントが見えてきます。
それは
String型の変数を大量に書き換えるような処理の場合メモリが圧迫される
ということです。
上記で説明したように、Stringに他の文字列を代入するときはその値を書き換えるのではなくて、新しい参照を作成します。
そのため何万件、何十万件とループしながら処理する中でStringを書き換えるような場合、何も考えずにやっているとメモリがいっぱいになって落ちる。なんてことが起きてしまうかもしれません。
String型のメモリの使い方を知らない人は何が起きているのかさっぱり分からないでしょう。
そういった時は、明示的にGC(ガベージコレクション)を行うなど、適切にプログラムを組む必要があります。
(ガベージコレクションではヒープ領域の中で、参照が既に存在しない値があればメモリを解放してくれます)
ガベージコレクションについては色々深いので、興味ある人は調べてみてください。
機会があればこのブログでも取り上げたいと思います。
さいごに(4日間集中講義のまとめ)
さて、4日間の集中講義ということでJavaの参照周りで押さえておきたい知識について図とコードを多めに解説してきました。
1~4日まで全部読んでないよって人は是非1日目から読んでみて下さい^^
【1日目】値(プリミティブ)型と参照(オブジェクト)型の違いをコードと図で解説
【2日目】参照渡しとは?参照型の変数を代入した時の挙動を押さえよう!
【3日目】==とequalsの違いは図で理解すれば絶対間違えない
【4日目】イミュータブルな変数ってなんだ?String型変数の特殊性を知る
ここで解説してきたことを理解してプログラムを組むか、知らずに組むのかではエンジニアとして非常に大きな差が出てきます。
1つの言語でメモリの使い方を習得しておけば、他の言語でも雰囲気で掴めるようになりますし、言語ごとの良さや特徴も掴みやすいと思います。
まぁJavaではまずこれまで解説してきた「左にスタック領域、右にヒープ領域」の図を覚えておくと良いね
儂も今ならスマートなコードが書ける気がするわ!
以上となります。少しでもJavaを勉強中の方々の手助けになれば幸いです。
もしツッコミあれば訂正しますのでコメントください。
また、イイねやコメント、SNSへの拡散等大歓迎ですので宜しくお願いしますm(__)m
それでは!また!!
人気の記事だけ集めたので是非覗いていってください^^
厳選!目的別にオススメ記事を紹介-あなたの欲しい情報がここに-
非常にわかりやすい説明の記事で、大変参考になりました。
ひとつ気になった点について、コメントいたします。
Stringの書き換えを大量に行わなければならない場合、「明示的にGCをコールする」ではなく、「StringではなくStringBuilderクラスを使用する」ように教わった記憶があります。
GCはJVMが最適なタイミングで書けるようにコントロールされているので、明示的にしないほうが良いともどこかで目にした覚えがあります。
Javaのバージョンにもよるとは思うのですが、この辺りの話は実際のところどうなのでしょうか?
(最近のJavaをあまり知らないので、間違った認識でしたら申し訳ありません)
コメントありがとうございます。
StringBuilderクラスに関しては、今回は文字連結によるメモリ圧迫ではなく、どちらかというと再代入(丸替え)を例として取り上げているので特に触れていませんでした。
GCは色んなタイプがあり、私も精通しているわけではないので何ともですが、以前大量データを処理する夜間バッチ処理がメモリリークを起こしたことがあり(恐らくyoung領域が溢れた)、その際はgcを明示することで対処しました。
おっしゃる通り、基本的にはgcのタイミングはJavaに委ねるべきですし、gcの設定で回避できるものは設定のほうをいじるのが最適かと思います。
めちゃくちゃわかりやすくて感動しました!
まだまだペーパーエンジニアなので読み漁ります!!
ありがとうございました!