Javaの「クラス」を簡単に理解するいまから始めるJava(3)

» 2003年01月30日 00時00分 公開
[中野克平@IT]

 前回はプリミティブ型変数の宣言と初期化について説明しました。簡単におさらいすると次のようになります。

  • 変数の宣言
    変数の「型」によって定まるサイズ分のメモリを確保すること
  • 変数の初期化
    変数の宣言によって確保されたメモリに値を書き込んで、メモリの内容と変数の内容を一致させること

 プリミティブ型の変数は、変数用に割り当てられたメモリ領域の先頭アドレスが分かれば、CPUがメモリを読み書きできます。プリミティブ型変数の先頭アドレスはJavaのバーチャルマシンが管理してくれますので、プログラマが意識する必要はありません。

 一方、Javaにはプリミティブ型の変数とは別に「クラス」という概念があります。例えば、地図上の座標を扱うために経度と緯度を扱うとしましょう。プリミティブ型の変数だけを使って地図上の座標を扱おうとすると、次のように変数を2つ使うことになります。

double longitude;
double latitude;

 実際にはいくつかの地点の座標を扱うことになりますので、上記の2つの変数があっても何もできません。例えばA地点とB地点を扱うには、次のように2組4個の変数を宣言することになります

double longitudeA;
double latitudeA;
double longitudeB;
double latitudeB;

 扱う座標の数が増えるごとに「longitudeXXX」「latitudeXXX」という変数をいちいち宣言するのは面倒です。座標を表すために経度と緯度は必ずセットで使われますので、int型やdouble型のように「地点型」とでもいうべき変数の型があれば便利になるはずです。

クラスの定義と生成

 Javaでは、「クラス」という仕組みを使うことで、変数の組み合わせを1つの変数であるかのように扱えるようになります。クラスを使うためには、まずクラスを定義しなくてはいけません。

Class GeographicInfo {
    double latitude;
    double longitude;
}

 クラスの定義は、クラスに含む変数を列挙するだけです。簡単ですね。さっそくクラスを使うプログラムを書いてみましょう。

   GeographicInfoクラスを使うプログラム
class GeographicInfo {
    double latitude;
    double longitude;
}

public class UsingGeographicInfo {
  public static void main( String args[] ) {
    GeographicInfo g;
    g = new GeographicInfo();
    g.latitude = 35.66;
    g.longitude = 139.75;

    System.out.println(g.latitude);
    System.out.println(g.longitude);
  }
}

 これまでよりも少し複雑なプログラムのように見えますが、基本的な考え方はいままでと同じです。変数を宣言して値を代入し、変数の値を画面に表示しています。ただし、プリミティブ型の変数の宣言とは少し違いがあるようです。次の部分に注目してください。

    GeographicInfo g;
    g = new GeographicInfo();
    g.latitude = 35.66;
    g.longitude = 139.75;

 1行目は変数gをGeographicInfo型として宣言しています。「GeographicInfo」はプログラムの最初で定義した「クラス」です。クラスといってもメモリを使うという点ではプリミティブ型と変わりはありません。プリミティブ型の変数は宣言することで必要なサイズのメモリが確保されました。では、GeographicInfo型の変数であるgを宣言すると何bytesのメモリが確保されるでしょうか?

 GeographicInfoは2つのdouble型変数を含むクラスです。double型のサイズは8bytesですので、GeographicInfo型の変数を宣言すると、8bytes×2=16bytesのメモリが確保されるのでしょうか?

 実は違います。GeographicInfoに限らず、すべてのクラス型変数はアドレスを格納するために使います。Javaではアドレスを4bytesで表しますので、GeographicInfoを含むすべてのクラス型変数のサイズは4bytesです。つまり、変数gの宣言で確保されるメモリ領域は4bytesということになります。

 2行目はGeographicInfoを「生成」して、必要なメモリを確保しています。クラスを使うときは変数の宣言とは別に生成という手順が必要になります。生成(「new GeographicInfo()」の部分)の手順によって初めて、GeographicInfoを使うために必要な16bytes分のメモリが確保されるのです。プリミティブ型とは異なり、クラスは生成によって必要なサイズのメモリ領域を確保する必要があるのです。

 なお、クラスに必要なメモリ領域を確保することを「インスタンス化」といいます。また、メモリ内に存在するクラスの実体のことを「インスタンス(instance:事案)」とか「オブジェクト(object:物体)」と呼びます。

 クラス型の変数は、確保したメモリ領域の先頭アドレスをプログラマが意識する必要があります。そこで、確保したメモリの先頭アドレスを変数gに代入するのです。この手順によって変数gは安全にアクセスできるアドレスを示すように初期化され、以後使えるようになります。

 ここまでの手順をメモリ内の動作で表すと次のようになります。

「g = new GeographicInfo()」で起きること 「g = new GeographicInfo()」で起きること

 3行目と4行目はGeographicInfoクラスが含む2つの変数への値の代入です。latitude(緯度)とlongitude(経度)はプリミティブ型の変数ですので、CPUが直接値を読み書きします。プリミティブ型変数の場合、変数の先頭アドレスはJVMが管理します。

 プリミティブ型は型のサイズが決まっていますので、先頭アドレスさえ分かれば、どこからどこまでがどの変数用のメモリなのか判断できます。しかし、latitudeとlongitudeはクラスの定義に含まれるだけで、プログラムの中で宣言したわけではありません。JVMはあくまでもGeographicInfoクラス用のメモリを確保するだけです。そこで変数gに代入したインスタンスの先頭アドレスを使うことになります。

 クラスの定義によって、クラス内の変数latitudeはGeographicInfoクラスの最初の変数であることが分かります。また、変数latitudeはdouble型ですので、8bytes分のメモリが必要です。変数longitudeはクラスの2番目の変数です。最初の変数のサイズが8bytesですので、longitude用のメモリ領域は先頭から9bytes目以降の8bytes分です。従って、3行目の「g.latitude = 35.66;」という文は、gに格納されているアドレス+0byte目から8bytes分のメモリをdouble型として扱い、「35.66」という値を代入することを意味します。4行目の「g.longitude = 139.75;」はgに格納されているアドレス+8bytes目から8bytes分のメモリをdouble型として扱い、「35.66」という値を代入することを意味します。変数gに格納したインスタンスの先頭アドレスからの相対アドレスを使うことで、クラス内で定義された変数もプリミティブ型と同様に読み書きできるわけです。なお、クラス内で定義された変数のことを「メンバ変数」とか「フィールド」と呼びます。

 クラスの定義とインスタンスの扱い方が分かりましたので、あとはいくらでもGeographicInfoクラスのインスタンスを生成できます。経度と緯度をクラスのメンバ変数として定義したので、どの地点の経度・緯度なのかが分かりやすくなります。

   GeographicInfoクラスを使うプログラム
class GeographicInfo {
    double latitude;
    double longitude;
}

public class UsingGeographicInfo2 {
  public static void main( String args[] ) {
    GeographicInfo myhome  = new GeographicInfo();
    GeographicInfo mycompany  = new GeographicInfo();

    myhome.latitude  = 35.66;
    myhome.longitude  = 139.75;
    mycompany.latitude  = 34.50;
    mycompany.longitude  = 136.60;

    System.out.println("私の家の緯度は" + myhome.latitude + "、経度は"+ myhome.longitude + "です。");
    System.out.println("私の会社の緯度は"+ mycompany.latitude +"、経度は"+ mycompany.longitude +"です。");
  }
}

 このコードを実行すると以下のような結果になります。

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>javac UsingGeographicInfo2.java C:\DOCUME~1\MYDOCU~1\MYJAVA~1>java UsingGeographicInfo2
私の家の緯度は35.66、経度は139.75です。
私の会社の緯度は34.50、経度は136.60です。 C:\DOCUME~1\MYDOCU~1\MYJAVA~1>

参照型を理解しよう

 Javaでは、クラス型のように先頭アドレスを参照して実際の変数にアクセスする変数の型を総称して「参照型」と呼びます。参照型の変数では、値が格納されているメモリ領域にアクセスするとき、別の変数に格納されている先頭アドレスを手掛かりにします。最後に参照型の理解を深めるための練習問題をやってみましょう。

   参照型変数の入れ替え
class SimpleClass {
    int x;
}

public class ExchangeReferences {
  public static void main( String args[] ) {

    SimpleClass a = new SimpleClass();
    SimpleClass b = new SimpleClass();

    a.x = 123;
    b.x = 456;

    b = a;

    System.out.println(b.x);
  }
}

 SimpleClassはその名のとおりメンバ変数が1つしかないとてもシンプルなクラスです。プログラム内では、SimpleClass型のオブジェクトを2つ生成し、変数a、bに代入しています。

 プログラムでは、まずオブジェクトaのメンバ変数xに123を代入しています。次に、オブジェクトbのメンバ変数xに456を代入します。その後、変数bに変数をaの値を代入し、b.xの内容を表示させます。では、結果はどうなるでしょうか?

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>javac ExchangeReferences.java

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>java ExchangeReferences
123

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>

 「b.x」に代入したのは456のはずなのに、結果はa.xに代入した123が表示されています。「え、どうして?」と思った方は参照型を理解できていないようです。でも、安心してください。どんな人でも参照型が理解できる魔法の言葉があります。

 その前に、まず参照型をおさらいしましょう。a、bはクラス型の変数ですので、aとbには生成したオブジェクトの先頭アドレスが格納されています。最初に生成したオブジェクトの先頭アドレスがa、その次に生成したオブジェクトの先頭アドレスがbに格納されます。

 「b = a;」という文を実行すると、bの値として、aに格納されているアドレスが代入されます。その結果、aとbは同じメモリ領域を指すことになるわけです。従って、a.xもb.xも同じメモリ領域を指すことになり、123と表示されたわけです。ちなみに、このプログラムではbがもともと指し示していたアドレスは分からなくなってしまい「456」と表示する方法はなくなります。

 さて、参照型が理解できなかった人と話をすると、メモリ領域と変数名が固く結び付いてしまっている場合が多いです。つまり、プリミティブ型と参照型の区別がついていないわけです。そもそも「参照型」という呼び方からしてよく分かりませんよね。

 そこで、「参照型とはエイリアス(alias:別名)のことだよ」というと、ほとんどの人はパッと表情が明るくなります。最初に確保したメモリ領域にa、次に生成したメモリ領域にbという名前を付けましたが、プリミティブ型の変数名とは違って、これはエイリアスです。「b = a;」という文で、aもbも同じメモリ領域を指すようになりますから、「b.x」の値として123が表示されるのは当たり前の話です。

 どうでしょうか。あなたの表情も明るくなりましたか? 次回はクラス型について、もう少し詳しく説明します。


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。