【Java初心者向け】基本データ型と参照型の違い

Java

どうも、Solowareです。
今日も元気にJavaの勉強をしていきましょう!
今回は、基本データ型と参照型の違いについて解説したいと思います^^
基本データ型も参照型も変数のことです。
先に結論をいうと

基本データ型の変数には、「値そのものが入っている」
参照型の変数には、「参照値が入っている」

この時点では「はい?」だと思います。僕もそうでした。
変数はJavaの最初らへんで学ぶ内容にはなるんですが、この2つの違いについて詳しく書かれている書籍は少ないため、初学者が躓くことが多いです。

この記事を読むメリット
  • 基本データ型とは何かがわかる
  • 参照型とは何かがわかる
  • 「==」と「equals()」の違いがわかる
  • Stringでの比較の違いがわかる

ではさっそく、やっていきましょう!

コンピュータがどのようにしてデータを記憶しているのか

さっそく両者の違いを説明したいところではあるのですが、
その前にまずはコンピュータがどのようにしてデータを記憶しているのかを説明しようと思います。
ここをイメージできると後の説明がすんなり頭に入ってくると思います^^

では、実際どのようにしてデータを記憶しているんでしょうか。

はい、そうなんです。
コンピュータは「2進数」でデータを記憶しているんですね。
2進数とは「0」と「1」だけで構成される数の数え方です。
僕らは数を数えるとき「10進数」を使用しています。
「0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11」という感じで数えますよね。
おわかりだと思うのですが、「0」から始まって「9」になったら次は一桁増えますよね^^
「0」から「9」までの「10」個の数字で構成される数え方です。

では、「2進数」は?
そうなんです。「0, 1, 10, 11, 100, 101, 110, 111, 1000」という感じで数えるんです!
「0」から始まって「1」になったら一桁増えます。
ちなみに「2進数」で数えるときは桁のことを「bit」と言います。
「0」→1bit
「10」→2bit
「101」→3bit
てな感じです。
これが2進数です。

基本データ型とは何か

はい、では本題に入っていきましょう!
まず基本データ型とはどんなものがあるかおさらいしましょう!

  • boolean
  • byte
  • char
  • short
  • int
  • float
  • long
  • double

ですよね^^
それぞれの使用メモリの量はこうなってます。

こんな感じになってます。
それぞれの型によって使用するメモリ量が違いますね^^

int型の場合は、32bit(32桁)メモリを使うのでこんな感じですね。
基本データ型のメモリ使用量を理解したので次は、実際にいつメモリを使われるのか見ていきます。

こんな感じで、最初は全て「0」の状態です。
その後、「int a;」と変数を宣言した時点でコンピュータは「32bit」分メモリを確保します。
そして「a = 5;」と変数に「5」を代入したら、確保した「32bit」の箇所に「101」(10進数でいう「5」)を記憶します。

つまり「基本データ型(int)の変数(a)には、値そのもの(5)が入ります」
これが基本データ型です。

余談なんですが、今回の例の場合int型に「5」を入れてますよね。
「5」は2進数でいうと「101」なんですが、これ3bitで表現されますよね。
int型は「32bit」のメモリを確保するので、残りの「29bit」は「0」で埋め尽くされます。
メモリを効率的に使うなら、int型ではなくbyte型で宣言した方が良いということになります。
ま、でも今はPCのスペックも高くなってきているのでそこまで気にする必要はないです^^

参照型とは何か

では、次に参照型です。
参照型とは、ずばり「new」できるものです。

  • クラス
  • 配列
int[] array= newint[4];
MyClass class1 = newMyClass();

てな感じで使いますよね^^
これらが参照型です。

ただここで、「え?Stringは?」ってなると思います。僕もそう思いました。
Stringは実はクラスなんです。先頭の文字が大文字になってますよね^^
なのでStringも参照型の1つです。
でもStringって「new」しないですよね?
ここが、初学者を紛らわせるJavaのトラップなんです。

実は、Stringは陰で「new」してるんです。。

String name = "Soloware";

これは、実はこう書いているのと同じなんです。

String name = new String("Soloware");

試しにやってみてください。同じ結果になりますよ^^
Javaは親切心から、「文字はよく扱うやろから、毎回newさせんのもあれやから簡単に書けるようにしとこかー」
ということで毎回「new」しなくてもいいようになってるんです。
親切心がまさかのトラップになるという少々可哀想なことになってるんですね・・

話が逸れましたが、「new」できるものが「参照型」ということを理解していただけたかと思います。

で、参照型には「参照値が入っている」と冒頭で説明しました。
なぜ、基本データ型みたく「データそのもの」が入らないのか。

こんな感じで、Stringの宣言段階では、変数「str」に何文字のデータが入るかわからないですよね。
なので、メモリの確保ができないんです。

じゃあ、どうするか。
変数の宣言時点では、実際の値が格納される場所の情報(参照値)を入れるんです。
画像はる

こんな感じで、変数「str」には実際の値「Soloware」が入るのではなく
「Soloware」の文字列データが格納される場所「123456」が入ります。
これが、「参照型の変数には、参照値が入っている」という正体でした^^

「==」と「equals()」による比較の違い

ついでにこれも説明しちゃいます。
結論はこれです。

  • 「==」は変数の値そのものを比較
  • 「equals()」はそのクラスで定義(オーバーライド)された処理に基づいて比較

まず、「==」です。
これは変数の値そのものを比較するので、基本データ型の場合はうまく比較できます。

int num1 = 5;
int num2 = 5;
System.out.println("num1とnum2を「==」で比較::" + (num1 == num2));

実行結果

num1num2を「==」で比較::true

しかし参照型の場合、変数の値には「参照値」が入っているので、思ったように比較できません。

String str1 = new String("Soloware");
String str2 = new String("Soloware");
System.out.println("str1とstr2を「==」で比較::" + (str1 == str2));

実行結果

str1とstr2を「==」で比較::false

※なぜ「new String」しているかは後述します。

Stringでは、文字列が同じ場合「true」としたいですよね。
そのために使われるのが「equals()」になります。
先ほど

「equals()」はそのクラスで定義(オーバーライド)された処理に基づいて比較

と説明しました。

つまり、Stringクラスに定義されている「equals()」の処理に基づいて比較するということです。
Stringクラスの「equals()」では、Javaがデフォルトで「文字列が同じ場合はtrueを返す」ように処理が書かれています。
その結果、「equals()」で比較すると文字列が同じ場合はtrueになるんです。

String str1 = new String("Soloware");
String str2 = new String("Soloware");
System.out.println("str1とstr2を「equals()」で比較::" + (str1.equals(str2)));

実行結果

str1とstr2を「equals()」で比較::true

このことから、自作したクラスで比較をしたい場合は、「equals()」をオーバーライドする必要があります。

public class Main {
    public static void main(String[] args){
        Eva first1 = new Eva("初号機");
        Eva first2 = new Eva("初号機");
        System.out.println("first1とfirst2を「==」で比較::" + (first1 == first2));
        System.out.println("first1とfirst2を「equals()」で比較::" + (first1.equals(first2)));
    }
}
class Eva {
    String model;
    public Eva(String model){
        this.model = model;
    }
}

実行結果

first1とfirst2を「==」で比較::false
first1とfirst2を「equals()」で比較::false

Javaではクラスを作成すると自動で「Object」クラスを継承します。
この場合、「Eva」クラスは「Object」クラスを継承していることになります。
そして、「Object」クラスには「equals()」メソッドが定義されていて、その内容は「参照値が同じ場合はtrueを返す」ものになってます。
つまり「==」で比較した時と同じ結果となるんです。
今回の場合「first1」と「first2」の参照値は違うため、falseとなります。

試しに参照値を同じようにしてみます。

public class Main {
    public static void main(String[] args){
        Eva first1 = new Eva("初号機");
        Eva first2 = new Eva("初号機");
        first1 = first2;
        System.out.println("first1とfirst2を「==」で比較::" + (first1 == first2));
        System.out.println("first1とfirst2を「equals()」で比較::" + (first1.equals(first2)));
    }
}
class Eva {
    String model;
    public Eva(String model){
        this.model = model;
    }
}

実行結果

first1とfirst2を「==」で比較::true
first1とfirst2を「equals()」で比較::true

「first1」に「first2」の参照値(メモリ番地)を代入しているので、
両者の参照値は同じになります。
その結果、「==」でも「Objectクラスで定義されたequals()」でもtrueとなる訳です。

今回の場合、Evaクラスが持つ「model」が同じ文字列の場合、trueを返したいですよね。
そのためには「equals()」メソッドをオーバーライドしてやる必要があります。

public class Main {
    public static void main(String[] args){
        Eva first1 = new Eva("初号機");
        Eva first2 = new Eva("初号機");
        System.out.println("first1とfirst2を「==」で比較::" + (first1 == first2));
        System.out.println("first1とfirst2を「equals()」で比較::" + (first1.equals(first2)));
    }
}
class Eva {
    String model;
    public Eva(String model){
        this.model = model;
    }
    @Override
    public boolean equals(Object obj){
        if (obj == this) return true;   //自分自身が引数に渡された場合は無条件でtrue
        if (obj == null) return false;  //nullが引数に渡された場合は無条件でfalse
        if (!(obj instanceof Eva)) return false;    //引数のクラスがEvaクラスでなかったらfalse
        Eva eva = (Eva)obj;             //この時点で引数のクラスはEvaクラスだと確定するので、ObjectクラスをEvaクラスにキャスト
        if (this.model.equals(eva.model)) return true;  //modelの文字列が同じ場合はtrueを返す
        return false;                   //modelの文字列が同じでない場合はfalseを返す
    }
}

こんな感じです。
実行結果

first1とfirst2を「==」で比較::false
first1とfirst2を「equals()」で比較::true

「equals()」をオーバーライドしたので、同じ「初号機」の文字列の場合trueになってますね^^

なぜ「new String」したのか

最後に「String」での比較の時に

Stringstr1= "Soloware";
Stringstr2= "Soloware";

ではなく

String str1 = new String("Soloware");
String str2 = new String("Soloware");

としたのかを説明します。

Javaには、メモリを効率的に使うために「コンスタントプール」という領域があります。
「String str1 = “Soloware”;」のように記述した場合、「Soloware」という文字列は、「文字列リテラル」と呼ばれ
この「コンスタントプール」という箇所にその参照値が登録されます。
なぜ、こんなことをするかというとメモリ領域を削減するため。
同じ文字列やのに、毎回新たなメモリを確保してたらもったいないですよね。
なので、Javaでは文字列リテラルが作られた場合は、コンスタントプールに登録されてる文字列と同じかを確認し
同じ場合は、コンスタントプールに登録している参照値を代入します。つまり「str2 = str1」してるのと同じです。
その結果、「str2」には「str1」と同じ参照値が入り「==」で比較した場合「true」となるんです。

Stringstr1= "Soloware";
Stringstr2= "Soloware";
System.out.println("str1とstr2を「==」で比較::" + (str1== str2));
System.out.println("str1とstr2を「equals()」で比較::" + (str1.equals(str2)));

実行結果

str1とstr2を「==」で比較::true
str1とstr2を「equals()」で比較::true

しかし、文字列リテラルとして書かれていない場合、例え同じ文字列だとしてもJavaは新たにメモリを確保し別の参照値を設定します。
例えば、このようなケース

Stringstr1= "Soloware";
Stringstr2= "Solo";
Stringstr3= "ware";
Stringstr4= str2+ str3;
System.out.println("str1::" + str1);
System.out.println("str4::" + str4);
System.out.println("str1とstr4を「==」で比較::" + (str1== str4));
System.out.println("str1とstr4を「equals()」で比較::" + (str1.equals(str4)));

実行結果

str1::Soloware
str4::Soloware
str1とstr4を「==」で比較::false
str1とstr4を「equals()」で比較::true

「str1」と「str4」は同じ「Soloware」という文字列ですが、「==」で比較した場合falseとなります。
これは、「str4」が文字列リテラルとして作られておらず「str2」と「str3」の文字列を連結させた演算結果を「str4」に代入しているためです。
このような演算結果も推測してコンスタントプールに登録されている文字列と一致するかどうかを毎回検証すると
Javaはかなり遅い言語となっていたと思います。
なので、処理速度に影響が出るとJavaが判断した場合は中身の文字列が同じであっても異なる参照値が設定されてしまうんです。
ちなみに、「new String」とした場合は、必ず別の参照値が設定されます。
今回は「==」で比較したら「false」になるよという説明をしたかったので、「new String」の形で書いていたということですね。

少し踏み込んだ話をしてしまいましたが、
基本Stringの比較は「equals()」ですればOKです^^
決して「==」で比較しないようにしてください。

まとめ

  • 基本データ型には値そのものが入る
  • 参照型には、値が入っているメモリ番地への参照値が入る
  • 「==」はデータの値そのものが同じか比較する
  • 「equals()」はこのメソッドの処理内容によってデータが同じか比較する
  • 自作したクラスでは「equals()」をオーバーライドすることで自分の意図した比較が可能になる
  • 文字列リテラルは同じ文字列の場合「==」で比較しても「true」になるケースがある
  • Stringでの比較は基本「equals()」を使う

いかがでしたでしょうか。
後半は少し話が逸れてしまいましたが、基本データ型と参照型の違いを理解していただけたと思います^^

僕のブログでは、プログラミングに関するノウハウを記事にしていきます。
少しでも参考になりましたら、Twitterもやってますのでフォローしていただければ嬉しいです!

ほいじゃねー

タイトルとURLをコピーしました