ほぷしぃ

Java言語入門 〜C言語を学んだ君へ〜

[第8回]クラス

[1] 参照

これまで「オブジェクトはクラスより生成される」という説明しかしていませんでした。
オブジェクトの生成とはどういうことか、int型などと何が違うのかを説明します。

基本データ型とオブジェクトの違い

基本データ型とは以下のようなもののことをいいます。
char型、boolean型、byte型、short型、int型、long型、float型、double型です。
それに対し、new演算子を使って生成するものをオブジェクトと言います。
オブジェクトは基本データ型と異なる点があります。その1つが参照です。
では、参照とはどういうものか、以下の図を見てください。

参照のイメージ

参照のイメージ図
図のようにオブジェクトは参照型です。C言語のポインタに近いものがあります。
Javaには、ポインタがないと最初に説明を行いましたが実はポインタに近い要素は使っているのです。
しかし、意識的に使う必要はないので安心してください。
これにより、JavaはC言語よりバグが起きにくくなっています。
Javaでは、常にオブジェクト=参照型という関係があることを覚えておいてください。

値渡しと参照渡し

「値渡しと参照渡し」という言葉を聞いたことがあるはずです。それは、C言語にありました。
関数に値を渡す場合、関数内で値を変更しても呼び出し元に反映はありませんが、
ポインタを渡す場合、呼び出し元に反映されるというものです。
JavaもC言語と同じように、値渡し、参照渡しをすることができます。
先ほど説明しましたが、オブジェクトは参照型です。
つまり、メソッドやコンストラクタにオブジェクトを渡した場合に、
呼び出し先でオブジェクトを変更した結果が、呼び出し元に変更されます。
次に値渡しと参照渡しの例を示します。

値渡しと参照渡しの比較プログラム

class Value {
    private int value;

    public Value(int value) {
        setValue(value);
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

public class Java08_09 {
    public static void main(String args[]) {
        int value1 = 10;
        Value value2 = new Value(10);

        put(value1, value2); // 値を表示(1回目)
        change(value1, value2); // 値渡しと参照渡し
        put(value1, value2); // 値を表示(2回目)
    }

    private static void change(int value1, Value value2) {
        value1 = 20;
        value2.setValue(20);
    }

    private static void put(int value1, Value value2) {
        System.out.print("基本データ型の値:" + value1);
        System.out.println(" オブジェクトの値:" + value2.getValue());
    }
}

実行結果

実行結果

このように表示されました。少しソースが長いですが、やっていることは単純です。
メソッドに基本データ型とオブジェクトを渡します。
メソッド内で値を変更した結果、呼び出し元側でどのように反映されるかどうかです。
結果は、予想通りオブジェクトの値のみが変更されます。

null

null(ヌル)とは、オブジェクトを参照していないことを意味します。
C言語にも「NULL」がありました。
C言語では、ポインタがどこも指していないことを意味し、Javaの「null」と似た意味を持ちます。
Javaでは、次のようにオブジェクトのインスタンス化を行いました。

クラス名 インスタンス名;
インスタンス名 = new コンストラクタ;

このインスタンス名がオブジェクトを参照する変数です。
1行目では、変数を宣言しているだけで、まだ何も参照していません。
つまり、nullの状態(インスタンス名=null)であると言えます。

続いて2行目でオブジェクトを生成し、ようやくインスタンス名がオブジェクトを参照するようになります。
nullにはいくつかの役割があります。その1つはオブジェクトの削除です。
C言語ではメモリを確保した場合、明示的に削除する必要がありました。
Javaのオブジェクトも同様にメモリを確保したものですが、
C言語と異なり、自動的に不要になったオブジェクトを削除してくれます。
この仕組みをガーベジコレクションと言います。もし、プログラマが意図的に削除したいオブジェクトがあれば、
オブジェクトを参照する変数にnullを代入することで、オブジェクトが削除される候補に挙がります。
ただし、いつ削除するかはJVMによって決まります。もう1つはオブジェクトがない場合です。
例えば、次のようなメソッドがあるとします。

public String method(int value) {
    if (value > 0) return "正の整数";
    else return null; // nullを返す
}

ちょっと強引な処理ですが、引数の値でオブジェクトを返すかどうかを決めています。
引数が整数なら文字列を返し、それ以外なら何も返しません。
ただし、戻り値をStringと指定している以上何かを返さなくてはいけません。
その場合、何も返すものがないという意味でnullを使うことがあります。

[2] == と equals

int型などの基本データ型が等しい値かどうかを判定する場合、次のように記述しました。

a == b

C言語でもおなじみの等価演算子です。これをオブジェクトに使うと次のようになります。

オブジェクトを等価演算子で比較

public class Java08_10{
    public static void main(String args[]){
        String str1 = new String("ほぷしぃ");
        String str2 = new String("ほぷしぃ");
        System.out.println(str1 == str2);
    }
}

実行結果

実行結果
同等の文字列を2つ作って「==」で判定しています。しかし、結果は「false」と出力されてしまいます。
なぜでしょうか?理由は

オブジェクトそのものを比較しているのではなく、オブジェクトの参照を比較している

からです。C言語風に言うとアドレスを比較しています。
同じ文字列のオブジェクトを作っても、メモリ上に別々に作られてしまうため、
参照先が異なり、「false」が出力されたのです。
これを解決する方法がequalsです。これは、オブジェクトそのものの比較を行ってくれます。

オブジェクトをequalsで比較

public class Java08_10{
    public static void main(String args[]){
        String str1 = new String("ほぷしぃ");
        String str2 = new String("ほぷしぃ");

        System.out.println(str1.equals(str2));
        System.out.println(str1 == str2);
    }
}

実行結果

実行結果
equalsの戻り値は「true」か「false」です。上記のソースは「true」になります。
参照先が異なっても、オブジェクトそのものが同じだと判定されたためです。
1つ注意点があります。これまで文字列は「String str = "文字列"」と書きました。
ところが、今回のソースでは「new演算子」を使っています。
Stringはクラスなので、このような書き方があります。
今まで書いてきた方法が特殊でした。そして、もう1つ特殊なことがあります。
2種類の宣言方法があっても、生成するものが同じように見えますが、違いがあります。
前者は同じオブジェクトのコピーが渡され、後者は新しくオブジェクトを作成しています。
せっかくですから、プログラムで確認してみましょう。

比較

public class Java08_11{
    public static void main(String args[]){
        String str1 = "ほぷしぃ";
        String str2 = "ほぷしぃ";
        System.out.println(str1 == str2);
    }
}

実行結果

実行結果

このプログラムでは「true」と出力されます。少しわかりにくかったかもしれません。
しかし、このようなケースはStringクラスぐらいのものです。よって、

オブジェクトの参照先を比較したいなら「==」
オブジェクトそのものを比較したい場合は「equals」

を使うようにしてください。

[3] 今まで使ったクラスとオブジェクト

今まで使ったクラスとオブジェクトについて少しだけ詳しく説明します。
今まで曖昧だったところがよりわかるようになるでしょう。

Systemクラス

今までよく出てきた記述がありました。「Sytstem.out.println()」です。
この構文の意味について説明します。
「.」が2つも付いて分かりにくいですが、これまでの知識があれば理解できます。
少しずつ見ていきましょう。まず、「System」とはSystemクラスのことです。
そして「System.out」とはSytemクラスが保持するoutという静的変数のことです。
out変数の実体はOutputStreamクラスであり、出力に関する処理をサポートします。
そして、「out.println()」でOutputStreamクラスのprintln()メソッドを使ってコンソール出力を行います。
まとめると、「System.out.pritnln()」とは「Systemクラスの静的変数outのprintln()メソッドを使って出力する」という意味です。

配列

これまでJavaの配列をC言語の配列と同じように使ってきたと思います。
しかし、それは基本データ型(int型など)しか扱わなかったためです。
では、オブジェクトの配列を作成するにはどのようにすればよいのでしょうか?
次のソースを見てください。初めに書いておきますが、次のソースはよくある誤った書き方です。

誤ったプログラム

class Obj {
    public int value;
}
public class Java08_12 {
    public static void main(String args[]) {
        Obj obj[] = new Obj[3];
        obj[0].value = 10; // ここでエラーが起きる
        System.out.println("obj[0].value = " +obj[0].value);
    }
}

実行結果

実行結果

Objクラスを定義し、このクラスの配列を宣言します。
そして、要素[0]のvalueに10を代入し、その値を出力しています。
一見、問題はなさそうですが、実行すると、このようなエラーが表示されてしまいます。
その理由は、これまでの学習を振り返ればわかると思います。
配列を宣言するとは「領域を確保」するだけで、オブジェクトを作っていません。
空っぽの配列の要素を使えるわけがなく、エラーが発生したということです。
ちなみに、エラーメッセージの「java.lang.NullPointerException」とは、「nullです」という意味です。
このエラーメッセージについては第13回「例外処理」で行います。
では、先ほどのJava08_12クラスを以下のように変更してください。

正しいプログラム

class Obj {
    public int value;
}

public class Java08_12 {
    public static void main(String args[]) {
        Obj obj[] = new Obj[3];
        obj[0] = new Obj();// 追加文(オブジェクトを作る)
        obj[0].value = 10;
        System.out.println("obj[0].value = " +obj[0].value);
    }
}

実行結果

実行結果
オブジェクトを生成し、配列の要素[0]に参照を渡しています。
これで正しく実行するようになります。

[4] まとめ

ここで、もう1度このページで学習したことをおさらいしておきましょう。

オブジェクトと基本データ

オブジェクトは基本データ型と異なる点は参照であるかどうかの違い。

null

nullの役割は、オブジェクトの削除とオブジェクトがないという合図です。
自動的に使わないオブジェクトは削除するが、意図的に削除したいときに使う。

オブジェクトの比較

オブジェクトの参照先を比較するときに「==」を使う。
オブジェクトそのものを比較するときは「equals」を使う。
a == bのようにオブジェクトの比較をすることはできない。
通常の比較とは違うということを認識しましょう。

配列

オブジェクトの配列の手順は少し異なる。

クラス名 配列名[] = new クラス名[要素数]
クラス名[要素番号] = new クラス名[]

オブジェクト指向プログラミングの要素の1つ、クラスの説明は終了しました。
これから先は「クラス」を知っている前提で進めていくため、
あまり理解できていない場合にはもう1度学習することをお勧めします。
次回では「継承」について説明していきます。



前のページへ ページのトップへ 第1問-問題へ