前回の記事では、
Javaの文字列比較においては、==
演算子ではなくequals
メソッドを使いましょうという説明をしました
その理由も説明しました
この記事では、もう少しだけ詳しい説明をします
- Javaの文字列比較で
==
演算子ではなくequals
メソッドを使うのは分かった人 - でも、その理由が分かったような分かってないような・・・イマイチ理解しきれていない人
- Javaのインスタンスの正体を知りたい人
◆解決できるかも知れないお悩み
- Javaの文字列比較で
==
演算子ではなくequals
メソッドを使う理由 - そもそも、newしたときのインスタンスの正体ってなに?
◆どうやって解決するか
- Stringではなく、独自のクラスを使ってメモリについて説明します
Contents
==
演算子でも一致させてみよう!
まずはサンプルコードとその結果をご覧ください
ソースコードとともに、コメントを読んでください
public class SampleA { public static void main(String [] args) { // hogeにのみ"aiueo"をセットしている String hoge = "aiueo"; // fugaとhegeには何もセットしない! String fuga; String hege; // 【★A★】 // 【ここポイント!】唯一文字列を持っているhogeをfugaとhegeにコピる fuga = hoge; hege = hoge; // 何もセットしていないはずのfugaとhegeからも文字列が取れる!? System.out.println("hoge : " + hoge.toString()); System.out.println("fuga : " + fuga.toString()); System.out.println("hege : " + hege.toString()); //------------------------------------------------- // == 演算子での比較 //------------------------------------------------- if( hoge == fuga ) { // ★こっちに入るはず System.out.println("[hoge == fuga] 一致"); } else { System.out.println("[hoge == fuga] 不一致"); } if( hoge == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } if( fuga == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } //------------------------------------------------- // equalsメソッドでの比較 //------------------------------------------------- if( hoge.equals(fuga) ) { // ★こっちに入るはず System.out.println("[hoge equals fuga] 一致"); } else { System.out.println("[hoge equals fuga] 不一致"); } if( hoge.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } if( fuga.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } } }
hoge : aiueo fuga : aiueo hege : aiueo [hoge == fuga] 一致 [hoge == hege] 一致 [hoge == hege] 一致 [hoge equals fuga] 一致 [hoge equals hege] 一致 [hoge equals hege] 一致
全てが「一致」と判断されましたよね
どういうことか、説明しましょう!
ポインタが分かる人は、こう理解してください
※ポインタが分からない人は読まなくても大丈夫ですよ
hoge
・fuga
・hege
はポインタみたいなもんだよ~~~ん!!
Javaにポインタは無い!って言うかも知れませんが・・・、
Javaにポインタはありまぁ~す!!
その存在を隠してはいますが、あります!
そもそもポインタを隠すから「参照の値渡し」っちゅうような、これまたややこしい問題を発生させてるじゃん
※「参照の値渡し」はまた後日別の記事で書きます
そもそも、コンピューターとプログラミングの仕組みとしてポインタが無いワケがない!
単にプログラム言語が隠しているだけの話!
Javaの場合のポインタはC言語のポインタとはまた違ってクセが強いので余計にややこしい!
さて、ポインタ云々は置いといて・・・、以降を見てください
ソースコード内の【★A★】の時点での状態
public class SampleA { public static void main(String [] args) { // hogeにのみ"aiueo"をセットしている String hoge = "aiueo"; // fugaとhegeには何もセットしない! String fuga; String hege; // 【★A★】 // 【ここポイント!】唯一文字列を持っているhogeをfugaとhegeにコピる fuga = hoge; hege = hoge; // 何もセットしていないはずのfugaとhegeからも文字列が取れる!? System.out.println("hoge : " + hoge.toString()); System.out.println("fuga : " + fuga.toString()); System.out.println("hege : " + hege.toString()); //------------------------------------------------- // == 演算子での比較 //------------------------------------------------- if( hoge == fuga ) { // ★こっちに入るはず System.out.println("[hoge == fuga] 一致"); } else { System.out.println("[hoge == fuga] 不一致"); } if( hoge == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } if( fuga == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } //------------------------------------------------- // equalsメソッドでの比較 //------------------------------------------------- if( hoge.equals(fuga) ) { // ★こっちに入るはず System.out.println("[hoge equals fuga] 一致"); } else { System.out.println("[hoge equals fuga] 不一致"); } if( hoge.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } if( fuga.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } } }
ソースコード中のコメント【★A★】の時点では、↓この図のようになっています
hoge
という箱は、"aiueo"
が配置されている番地を保持しています
fuga
とhege
は何も保持していません
【ここポイント!】での状態
public class SampleA { public static void main(String [] args) { // hogeにのみ"aiueo"をセットしている String hoge = "aiueo"; // fugaとhegeには何もセットしない! String fuga; String hege; // 【★A★】 // 【ここポイント!】唯一文字列を持っているhogeをfugaとhegeにコピる fuga = hoge; hege = hoge; // 何もセットしていないはずのfugaとhegeからも文字列が取れる!? System.out.println("hoge : " + hoge.toString()); System.out.println("fuga : " + fuga.toString()); System.out.println("hege : " + hege.toString()); //------------------------------------------------- // == 演算子での比較 //------------------------------------------------- if( hoge == fuga ) { // ★こっちに入るはず System.out.println("[hoge == fuga] 一致"); } else { System.out.println("[hoge == fuga] 不一致"); } if( hoge == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } if( fuga == hege ) { // ★こっちに入るはず System.out.println("[hoge == hege] 一致"); } else { System.out.println("[hoge == hege] 不一致"); } //------------------------------------------------- // equalsメソッドでの比較 //------------------------------------------------- if( hoge.equals(fuga) ) { // ★こっちに入るはず System.out.println("[hoge equals fuga] 一致"); } else { System.out.println("[hoge equals fuga] 不一致"); } if( hoge.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } if( fuga.equals(hege) ) { // ★こっちに入るはず System.out.println("[hoge equals hege] 一致"); } else { System.out.println("[hoge equals hege] 不一致"); } } }
ソースコード中のコメント【ここポイント!】の2行を処理したら、↓この図のようになります
hoge
という箱の中身をfuga
という箱とhege
という箱にコピる事になります
fuga
とhege
は150番地という参照先情報をもらったので、150番地を参照するようになります
前回記事のこの図↓、hege
とfuga
が参照している"aiueo"
は、見た目は同じ"aiueo"
だけど、別人なのです(同姓同名の赤の他人)
今回のこの図↓、hege
とfuga
とhege
が参照している"aiueo"
は同じものです。
同姓同名の別人じゃなくて、一人の人。
そうです、hege
さんと、fuga
さんと、hege
さんは、同じ一人の女性を愛してしまったのです!
インスタンス本体と、参照型変数について
と書いた時、otherのことをインスタンスと呼ぶことが多いと思います
しかし正確にはotherはインスタンスではありません
otherはインスタンスを参照するただの変数です
だから、参照型変数と呼ぶのです
インスタンスは、メモリ上のどこかにいるんですよ
メモリのどこにいるのかは、そのアドレスは公開されないので分かりません
唯一そのアドレスを知っているのは、参照型変数のotherだけなのです(と言っても、人間がそのアドレス値を知ることはできませんが・・・)
で、参照型変数otherを仲介して、インスタンスを操作していると考えてください
2つ目のサンプル
Stringクラスはイミュータブル(immutable、不変っていう意味)だし、newしなくても使えるしで、ちょっとごく普通のクラスとは違うんだよねぇ~
なので、本来はサンプルとしてはあまりよろしくない
ってことで、Stringクラスじゃなくて、ごくごく普通のクラスを使って説明しましょう
サンプルプログラム
SubClsクラスはString型とint型のプロパティを持っていて、そのsetter/getterを持っているだけです
class SampleB { public static void main(String args[]) { // 二つのクラスを作ります SubCls hoge1 = new SubCls(); SubCls hoge2 = new SubCls(); // それぞれ別々の文字と数値をセットします hoge1.setVal(100); hoge2.setVal(200); hoge1.setText("11111"); hoge2.setText("22222"); // ★1:当然、この時点では、==演算子でもequalsメソでも一致するワケがありません System.out.println("hoge1 : " + Integer.toString(hoge1.getVal())); System.out.println("hoge2 : " + Integer.toString(hoge2.getVal())); System.out.println("hoge1 : " + hoge1.getText()); System.out.println("hoge2 : " + hoge2.getText()); System.out.println("== : " + (hoge1 == hoge2)); System.out.println("equals : " + (hoge1.equals(hoge2))); System.out.println("-----------------------"); // ★2:hoge2の参照値をhoge1へ代入すると・・・ // hoge1の値が変化します!! hoge1 = hoge2; System.out.println("hoge1 : " + Integer.toString(hoge1.getVal())); System.out.println("hoge2 : " + Integer.toString(hoge2.getVal())); System.out.println("hoge1 : " + hoge1.getText()); System.out.println("hoge2 : " + hoge2.getText()); System.out.println("== : " + (hoge1 == hoge2)); System.out.println("equals : " + (hoge1.equals(hoge2))); System.out.println("-----------------------"); // ★3:hoge2の値を変更しすると・・・ // hoge1の値もつられて変化します!! hoge2.setVal(300); hoge2.setText("33333"); System.out.println("hoge1 : " + Integer.toString(hoge1.getVal())); System.out.println("hoge2 : " + Integer.toString(hoge2.getVal())); System.out.println("hoge1 : " + hoge1.getText()); System.out.println("hoge2 : " + hoge2.getText()); System.out.println("== : " + (hoge1 == hoge2)); System.out.println("equals : " + (hoge1.equals(hoge2))); } } class SubCls { private int val; private String text; public SubCls() { val = 0; text = "default"; } public void setText(String text) { this.text = text; } public String getText() { return this.text; } public void setVal(int val) { this.val = val; } public int getVal() { return this.val; } }
hoge1 : 100 hoge2 : 200 hoge1 : 11111 hoge2 : 22222 == : false equals : false ----------------------- hoge1 : 200 hoge2 : 200 hoge1 : 22222 hoge2 : 22222 == : true equals : true ----------------------- hoge1 : 300 hoge2 : 300 hoge1 : 33333 hoge2 : 33333 == : true equals : true
説明
★1について
特に問題ないですね
セットした値が出ているだけです
この時点では、hoge1
とhoge2
は別々の値が表示されます
★2について
hoge1
がhoge2
に乗っ取られていますよね?
hoge1
が参照先がhoge2
と同じ参照先になりました
hoge1
が元々参照していたインスタンスは誰からも参照されることがなくなりましたので、そのうちガベージコレクタの餌食になって消えて失くなります
これがいわゆる、ガベージコレクション、ガベコレです
★3について
値を書き変えたのはhoge2
だけなんですが、★2でhoge1
はhoge2
に乗っ取られました(と言うか、参照先が同じになった)ので、表示される値も同じになります
さいごに、
において、new SubCls()がインスタンスの本体で、hoge1
はただの仲介人です
※仲介人より、「ハンドラ」とか「ポインタ」の方がピンと来る人は、そう考えてもらっていいです
「Javaはポインタをもっている」と本文中で言及しましたが、C言語が持つポインタとは微妙に違うのがまたややこしいところです
初心者の方には分かりづらいし、想像しにくいと思いますが、変数やデータ等がメモリ上にどのように展開されるのか想像しながらプログラミングの学習をすることをオススメします!
プログラミング のレッスンに興味がある方、レッスン内容を聞いてみたい方、なんなりとお問い合わせください。
無料体験レッスンもありますのでお気軽にどうぞ!!!