Javaの変数の本質を知るいまから始めるJava(2)

» 2002年11月29日 00時00分 公開
[中野克平ITmedia]

 前回は初めてのJavaプログラムとして、画面に「Hello World!」と表示させました。今回はこのプログラムをベースに、前回とは別のことをさせてみましょう。

 今回の学習テーマは変数です。一見地味で面白くないテーマのようですが、変数の本質がわからないと、実はJavaの本質もわかりません。そして学習効率も極端に落ちてしまいます。オブジェクト指向言語であるJava特有の型である「クラス型」「参照型」を理解するために、変数の本質に迫りましょう。

 さて、以下のリストを見てください。

   「Hello World!」を表示するプログラム
public class HelloWorld {
  public static void main( String args[] ) {
    System.out.println("Hello World!");
  }
}

 初めてのプログラム言語としてJavaを学習している方には何やら呪文のような英単語が並んでいますが、「System.out.println("Hello World!");」の部分で画面に文字を表示させていることは分かると思います。「Hello World!」の代わりに日本語で「こんにちは世界!」と記述することもできます。

   「こんにちは世界!」を表示するプログラム
public class HelloWorld {
  public static void main( String args[] ) {
    System.out.println("こんにちは世界!");
  }
}

 「Hello World!」や「こんにちは世界!」のように、「"」〜「"」(ダブルクオーテーション)でくくられた部分を「文字列」といいます。どうしてわざわざ「"」〜「"」でくくるのでしょうか? 文字列であることを明示しないとコンパイラが解釈しようとしてしまうからです。例えば「System.out.println("Hello World!");」という、コンパイラにとって意味のある文字列を表示したいとします。このとき、文字列として明示せず、System.out.println(System.out.println("Hello World!"););のようにしてしまうと、コンパイラはプログラマが何を表示させたいのか分からなくなってしまい、エラーになります。「"」〜「"」でくくることで、コンパイラに対して、「ここは文字列なので解釈しなくていいですよ」と伝えているわけです。

 もう1つ、「System.out.println("Hello World!");」の最後に付いている「;」にも注目しましょう。Javaでは「; 」(セミコロン)を使って文を区切ります。別のいい方をすると、文を区切るのに改行しなくてもよいし、文の中に改行があってもよいということです。例えば、以下のプログラムは文法的に正しく、きちんとコンパイルできます。

   文中に改行のあるプログラム
public class HelloWorld {
  public static void main( String args[] ) {
    System.out.
      println("Hello World!");
  }
}

 文中の改行はプログラムが読みにくくなるので乱用すべきではありません。文が長く、エディタで表示するときに見栄えが悪くなってしまったときにだけ使うとよいでしょう。

 「System.out.println(…)」を使うと、文字列だけでなく、数字や式の計算結果も出力できます。

   System.out.println(…)で数字や式の計算結果を表示するプログラム
public class Calculation {
  public static void main( String args[] ) {
    System.out.println(12345);
    System.out.println(3*6+4/2);
  }
}

 このプログラムをコンパイルして実行すると、以下のようになります。

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

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>java Calculation
12345
20


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

 赤字部分が数字や式の計算結果として表示された部分です。「12345」は数字なので文字列のように「"」〜「"」でくくる必要はありません。また、数字と演算子の組み合わせである「3*6+4/2」は式ですので、実行時に計算されて、計算結果が表示されています。

 ちなみに、「*」のように計算させるための記号のことを「演算子」といいます。Javaには次のような演算子があります。

 「×」が「*」、「÷」が「/」など、キーボードで入力するプログラム言語では、数学で使う演算子とは若干異なります。演算子には優先順位が決まっています。優先順位の概念は数学でもありますよね。小学校高学年の算数で習ったと思います。例えば、式の中に足し算と掛け算があるときは掛け算を先にする、()があるときは、()の中を先に計算する、という規則のことです。Javaには四則演算用以外にも演算子があり、すべての演算子について優先順位が決まっています。

 例えば、以下の式について考えてみましょう。

3*6+4/2

 「+」よりも「*」や「/」の方が優先順位が高いことは演算子の優先順位表から分かります。では、「3*6」と「4/2」はどちらが先に計算されるのでしょうか? Javaには「左結合」というルールがありますので、優先順位が同じ場合は、左側の式が先に計算されます。従って、上の式は「3*6」「4/2」「18+2」と計算されて、答えは「20」になります。

Javaの変数

 Java以外のプログラム言語の経験がある方は、「なんだか当たり前の説明ばかりで飽きちゃったなあ」と思われたかもしれません。今度は「変数」の説明ですので、「すでにほかの言語を学習したことのある方はまた次回にお会いしましょう」となってもおかしくないですね。しかし、ちょっと待ってください。筆者が最初にプログラム言語を学習してから20年以上たちますが、変数について、きちんと説明している学習本に出合ったことがありません(運が悪いのかもしれませんが……)。

 先日もある人からJavaについて質問を受けたのですが、彼が悩んでいたことの根本的原因は、変数とデータ型についてきちんと理解していないことにありました。

 まずは、Javaで変数を使ったプログラムを書いてみましょう。

   変数を使って計算結果を表示するプログラム
public class CalcWithParams {
  public static void main( String args[] ) {
    
    int x;
    int y;
    
    x = 2;
    y = 2*x + 3;
    
    System.out.println(y);
  }
}

 「変数」という概念は数学に由来するものです。上記のプログラムは、「 2x + 3」という式についてxが2のときの値を計算し、その結果「7」をyに代入して表示するものです。プログラム言語の変数は、数学の変数とまったく同じ役割を果たしていますので、義務教育を終えた方ならだれでも理解できることです。ところが、一般的なプログラム言語学習本は、「変数」という概念を、わざわざ難しく説明していると思うのです。

 特にどの本というわけではありませんが、変数の説明に出てきがちな図を示します。

よくある変数の説明 よくある変数の説明

 私には、変数の説明をするのに、なぜ「箱」が出てきてしまうのかよく分かりません。新しい概念を身に付けるのは大変なことですので、「箱」という「例え話」を使うという発想は分かるのですが、「変数」はプログラム言語特有の概念でしょうか? 義務教育を終えた方ならだれでも理解できるはずのことを、「変数=箱」という例え話がかえって分かりにくくしてしまっているように思えます。コンピュータの中に箱はできません! もっと悪いことは、箱という例え話を使うことで、プログラマが変数について理解すべき「変数の宣言はメモリを確保する」ことと、「変数へ代入されるデータのサイズが、型によって異なる」ことの2点が抜け落ちてしまいます。そこで、変数の宣言と代入がメモリをどのように使うのかを見てみましょう。

変数の宣言

 Javaでは、変数を使う前に「宣言」をします。Visual Basicの場合は宣言しない変数でも使えますが、Javaでは変数を使う前には必ず宣言しないといけません。

int a;

 「int」は「integer(整数)」の略で、「int a;」という文で、「これから整数型の変数aを使います」という意味になります。では、この文が実行されると、コンピュータの中では何が起きるのでしょうか?

「int a;」で起きること 「int a;」で起きること

 上の図をコンピュータの中のメモリだと考えてください。「int a;」という文の本当の意味は、コンピュータ内のメモリのうち、4bytes分を確保し、以後ソースコード中で「a」と呼ぶことにするとコンパイラに対して宣言することです。では、なぜ4bytes分なのでしょうか?

 Javaで扱うすべてのデータには「型」があります。すでに説明した文字列も実は「String型」のデータです。データの「型」とは、結局のところ「データを格納するのに必要なメモリのサイズ」のことです。「int a;」という文では、変数aをint型と宣言しています。Javaでは、int型は「4bytes分のメモリを使って表す整数」のことだと定義されているので、4bytes分のメモリが確保されるのです。

 Javaで使える型とそれぞれのサイズを以下に示します。

型名 サイズ 値の範囲
整数 byte 1bytes -128〜+127
short 2bytes -32768〜+32767
int 4bytes -2147483648〜+2147483647
long 8bytes -9223372036854775808〜+9223372036854775807
浮動少数 float 4bytes ±3.40282347E+38〜±1.40239846E-45
double 8bytes ±1.79769313486231570E+380〜±4.94065645841246544E-324
文字 char 2bytes 0〜65535(Unicode表現)
論理 boolean (1bytes) true/false

 それでは、練習問題を兼ねて、いろいろな型の変数を宣言してみましょう。

   いろいろな型の変数を宣言するプログラム
public class DeclareParams {
  public static void main( String args[] ) {
    byte b;
    short s;
    int i;
    long l;
    float f;
    double d;
    char c;
  }
}

 変数の宣言はコンピュータの中に箱を作ることではなく、メモリを型のサイズ分確保することです。では、上記のプログラムでは合計何bytes分のメモリが変数用に確保されるでしょうか? 表の順番どおりに変数を宣言していますので、1+2+4+8+4+8+2=29bytes分のメモリが確保されることになります。

変数の初期化と代入

 変数の宣言は、メモリを必要なサイズ分確保することです。ただし、メモリを確保した段階では、メモリにどんな値が書き込まれているか分かりません。そこで、変数から値を読み出す前に、何かの値を書き込んで変数を初期化する必要があります。初期化せずに変数を使おうとするとコンパイラがエラーを出します。

   初期化せずに変数を使おうとするプログラム(エラーになる)
public class NoInitParam {
  public static void main( String args[] ) {
    int a;
    System.out.println(a);
  }
}

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>javac NoInitParam.javaNoInitParam.java:4: 変数 a は初期化されていない可能性があります。
                System.out.println(a);
                                   ^
エラー 1 個
C:\DOCUME~1\MYDOCU~1\MYJAVA~1>

 変数を初期化する方法は、読み出す前に何かの値を代入するだけです。

   初期化してから変数を使うプログラム
public class InitParam {
  public static void main( String args[] ) {
    int a;
    a = 1;
    System.out.println(a);
  }
}

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

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

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

 今度はエラーが発生せずにコンパイルして実行できました。「a = 1;」という文で、変数a用に確保されたメモリに値が書き込まれたからです。

「a = 1;」で起きること 「a = 1;」で起きること

 「変数=箱」の例え話が事実を正確に表していないもう1つの点は、メモリに書き込まれるのが「1」ではなく、「00000001」という値であることです。なぜ、「00000001」なのでしょうか?

 Javaでは、変数だけではなく、数値を含むすべてのデータに型があります。int型の変数aに代入される数値「1」は、同じくint型に扱われますので、メモリに書き込まれるデータは「00000001」になるわけです。では、もし、変数aがbyte型として宣言されていたらどうなるでしょうか?

 byte型は1byte分のメモリを使って整数を表す型ですので、同じ「a = 1;」という文でも、書き込まれるデータは「01」という1byte分のデータになります。「変数は箱だ」と理解してしまうと、代入されるデータが変数の型によって異なることが分からなくなってしまいます。

プリミティブ型以外の「型」

 Javaの説明のはずが、「メモリをどう使うのか」の話になるなんて「ずいぶん原始的な話だな」と思いましたか? 実は、intやbyteなどの型を総称してプリミティブ型(基本データ型)と呼びます。英語のprimitiveは「原始的」という意味です。これらの型が、CPUで直接メモリの内容を読み書きして計算させるという「プリミティブ」な用途で使われるからです。

 プリミティブでない用途では、すでに少しだけ紹介したString型などが使われます。また、何度も出てきた「System.out.println(…);」という文の「System」や「out」もプリミティブでない型の変数です。プリミティブ型ではない型に「クラス型」や「参照型」があります。次回は、この「クラス型」や「参照型」について説明します。だんだんJavaっぽい話になりますのでご期待ください。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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