[Java]newした時のメモリを想像しよう!~参照型変数とインスタンスは別物~

 

こんにちは。ナガオカ(@boot_kt)です。

 

この記事は↓の記事を先に読んでいることが前提です

前回の記事では、

Javaの文字列比較においては、==演算子ではなくequalsメソッドを使いましょうという説明をしました
その理由も説明しました

この記事では、もう少しだけ詳しい説明をします

この記事のザックリした内容
◆対象読者

  • Javaの文字列比較で==演算子ではなくequalsメソッドを使うのは分かった人
  • でも、その理由が分かったような分かってないような・・・イマイチ理解しきれていない人
  • Javaのインスタンスの正体を知りたい人

 

◆解決できるかも知れないお悩み

  • Javaの文字列比較で==演算子ではなくequalsメソッドを使う理由
  • そもそも、newしたときのインスタンスの正体ってなに?

 

◆どうやって解決するか

  • Stringではなく、独自のクラスを使ってメモリについて説明します

 

 

 

==演算子でも一致させてみよう!

まずはサンプルコードとその結果をご覧ください
ソースコードとともに、コメントを読んでください

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] 一致

 

全てが「一致」と判断されましたよね

 

どういうことか、説明しましょう!

ポインタが分かる人は、こう理解してください
※ポインタが分からない人は読まなくても大丈夫ですよ

hogefugahegeはポインタみたいなもんだよ~~~ん!!

 

Javaにポインタは無い!って言うかも知れませんが・・・、

Javaにポインタはありまぁ~す!!

その存在を隠してはいますが、あります!
そもそもポインタを隠すから「参照の値渡し」っちゅうような、これまたややこしい問題を発生させてるじゃん
※「参照の値渡し」はまた後日別の記事で書きます

そもそも、コンピューターとプログラミングの仕組みとしてポインタが無いワケがない!
単にプログラム言語が隠しているだけの話!

Javaの場合のポインタはC言語のポインタとはまた違ってクセが強いので余計にややこしい!

 

さて、ポインタ云々は置いといて・・・、以降を見てください

前回の記事の図が前提となってるのでまずはこれを見ておいてね
java == equals string compare

Stringクラスのインスタンスが生成された時のメモリの状態

 

ソースコード内の【★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"が配置されている番地を保持しています
fugahegeは何も保持していません

 

【ここポイント!】での状態

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という箱にコピる事になります
fugahegeは150番地という参照先情報をもらったので、150番地を参照するようになります

前回記事のこの図↓、hegefugaが参照している"aiueo"は、見た目は同じ"aiueo"だけど、別人なのです(同姓同名の赤の他人)

前回の図
java == equals string compare

Stringクラスのインスタンスが生成された時のメモリの状態

今回のこの図↓、hegefugahegeが参照している"aiueo"は同じものです。
同姓同名の別人じゃなくて、一人の人。
そうです、hegeさんと、fugaさんと、hegeさんは、同じ一人の女性を愛してしまったのです!

メモリの状態
※番地の数字は適当に書いているだけです
※具体的な数字がある方が理解しやすいかなと思って書いているので気にしないでね

 

インスタンス本体と、参照型変数について

OtherClass other = new OtherClass();

と書いた時、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について

特に問題ないですね
セットした値が出ているだけです

この時点では、hoge1hoge2は別々の値が表示されます

 

★2について

hoge1hoge2に乗っ取られていますよね?
hoge1が参照先がhoge2と同じ参照先になりました

hoge1が元々参照していたインスタンスは誰からも参照されることがなくなりましたので、そのうちガベージコレクタの餌食になって消えて失くなります
これがいわゆる、ガベージコレクション、ガベコレです

 

★3について

値を書き変えたのはhoge2だけなんですが、★2でhoge1hoge2に乗っ取られました(と言うか、参照先が同じになった)ので、表示される値も同じになります

 

広告




 

さいごに、

というような説明でご理解頂けましたでしょうか?

SubCls hoge1 = new SubCls();

において、new SubCls()がインスタンスの本体で、hoge1はただの仲介人です
※仲介人より、「ハンドラ」とか「ポインタ」の方がピンと来る人は、そう考えてもらっていいです

「Javaはポインタをもっている」と本文中で言及しましたが、C言語が持つポインタとは微妙に違うのがまたややこしいところです

初心者の方には分かりづらいし、想像しにくいと思いますが、変数やデータ等がメモリ上にどのように展開されるのか想像しながらプログラミングの学習をすることをオススメします!

 


 

プログラミング のレッスンに興味がある方、レッスン内容を聞いてみたい方、なんなりとお問い合わせください。
無料体験レッスンもありますのでお気軽にどうぞ!!!

 

 

参考書籍

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

ABOUTこの記事をかいた人

Windows/Mac/Linuxを使う現役システムエンジニア&プログラマ。オープン系・組み込み系・制御系・Webシステム系と幅広い案件に携わる。C言語やC#やJava等数多くのコンパイラ言語を経験したが、少し飽きてきたので、最近はRubyやPython、WordPressなどのWeb系を修得中。初心者向けのプログラミング教室も運営中。オンライン・対面・出張等でプログラミングをレッスンします。