アルゴリズムA 第3回


Java言語のデータについて

「メモリ」と「番地」

コンピュータがプログラムを実行しているとき、 そのプログラムや データは主記憶装置(メモリ) の中に置かれています。 CPUは、メモリの中のプログラムやデータにアクセスして計算を 進めていきます。

コンピュータが Java のプログラムを実行しているとき、たとえば


sp204: ~/pro 5> java Hello 
として Hello.class を実行している場合は図1の動作を行なっています。

図1: プログラム実行(中間コードの場合)

さて、CPUはメモリの中のプログラムやデータにどうやって アクセスしているのでしょうか?

番地

コンピュータ内のメモリは1バイト単位で0から順番に番号が つけられています。 この「メモリにつけられた番号」を「番地」または「アドレス」といいます。

CPUはメモリ中の特定の位置を「番地」で指定してアクセスしているのです。

たとえば 64Kバイトの大きさのメモリであれば、0から ffff(16進数、 10進数だと65535になります)の範囲の「番地」でアクセスします。 また、1Gバイトのメモリであれば、0から 3fffffff(16進数、 10進数だと1073741823になります)の範囲の「番地」でアクセスします。

1バイトよりも大きいデータは、メモリ中の連続した複数のアドレスに 記憶されます。通常、それらのデータについて述べるときは、複数の アドレスのうちの一番小さなアドレスを使って表します。 たとえば、データが4バイトの大きさで、メモリ中の 1000 〜 1003 番地に 記憶されているとき、 「このデータは1000番地(から始まる4バイトに)記憶されている」 という言い方をします。

図2: int型データのメモリ内での配置(注:図はBig Endianの場合)

Java言語における「変数」の値

java言語では、データの型によって

の2種類があります。前者を「基本データ型」と、後者を「参照型」といいます。

「基本データ型」の場合

Java言語の基本データ型 (int, double など) では、 データがメモリ中に占める大きさが 厳密に定義されています。 たとえば int 型だと4バイトの大きさ、double型だと 8 バイトの 大きさとなります。 データの型によって1バイト〜8バイトの大きさになりますが、 基本データ型ではいつも決まった大きさのメモリ中にデータを 記憶できることになります。

基本データ型では、「データの値そのもの」を扱います。 変数にもデータの値そのものが直接保持されています。 したがって、

したがって、「元のデータ」と「コピーしたデータ」は別の実体となります。

Sample09.java


Sample09.javaの実行例
$ javac Sample09.java 
$ java Sample09 
main: x=3,y=3       ←xとyの値は同じ
main: x=5,y=3       ←xの値の変更の影響はyには及ばない
main:x=10
func: a=10
func: a=11       ←aの値を変更する
main:x=10        ←aの値の変更の影響はxには及ばない
$ 


図3: 値のコピーによる代入


図4: 値のコピーによる引数受け渡し

「参照型」の場合

オブジェクトはJavaの基本データ型とは異なり、『参照型』です。

参照型では「データの値」を直接扱うのではなく、「そのデータが 記憶されている場所(メモリ中の番地)」を扱います。 オブジェクトを変数に保持する場合は、そのオブジェクトへの 『参照』を保持することになります。

(注)Java言語の仕様としては、「「参照」は必ずしも「番地」そのものではない」 のですが、大抵のJava処理系では「参照」=「番地」として実装されて いますので、動作の理解のためには「参照」=「番地」と理解しておけば 十分です。

参照型では、変数にデータの参照が保持されています。 したがって、

『参照型』では「参照」を通して実体にアクセスします。 したがって、「元の『参照』」も「コピーされた『参照』」も 同じ実体を指すことになります。

Sample11.java


Sample11.javaの実行例
$ javac Sample11.java 
$ java Sample11 
x:{ 0 1 2 3 4 }
y:{ 0 1 2 3 4 }         yにxを代入すると
x:{ 10 1 2 3 4 }        ←x[0]の値を変更すると
y:{ 10 1 2 3 4 }        ←y[0]の値も影響を受ける
func:a:{ 10 1 2 3 4 }   ←メソッドに引数で渡されたものを
func:a:{ 20 1 2 3 4 }   ←値を変更する
x:{ 20 1 2 3 4 }        ←mainメソッド中のx[0]も影響を受ける
y:{ 20 1 2 3 4 }   ←mainメソッド中のa[0]も影響を受ける
$ 


図5: 参照のコピーによる代入


図6: 参照のコピーによる引数受け渡し

配列型

上で述べたように「クラス」型のデータは参照型でした。 「配列型」はもう一つの参照型といえます。

配列型は厳密には Javaオブジェクト、すなわちクラス型の一種なのですが、 特殊な文法が用意されていて、振舞いも特殊なので別のものと考えることが できます。

配列について

変数について学習したときに、「データの型さえ合っていればどんな値でも 入れられるので便利だな。だけど、1つの変数には1個の値しか入れられない のがちょっと不便かも」とか思いませんでしたか?

少し複雑なプログラムを作るようになると、「1つの変数に複数の値を 入れてまとめて管理できればいいのになぁ」と考えるようになります。 これを実現してくれるのが「配列型」なのです。

配列は「同じ型のデータを複数個まとめたもの」といえます。

配列の宣言

配列の型は、配列の各要素が保持する値の型に「開き大括弧」 [ と 「閉じ大括弧」 ] という文字を組み合わせたものです。


配列型の変数の宣言
[配列の宣言]
    型[]  配列名;

[配列の宣言例]
    int[] a;
    double[] b;

Java以外の言語(C言語やC++言語など)との互換性を維持するために、 配列の宣言方法はもう一つあります。


配列型の変数の宣言(もう一つの方法)
[配列の宣言]
    型  配列名[];

[配列の宣言例]
    int a[];
    double b[];

配列の生成

Javaで配列データを生成するには、オブジェクトと同じように new 演算子 を用います。配列は他のオブジェクトとは違って初期化する必要はありません ので、括弧の間にパラメータリストを渡すことはしません。その代り、 要素数を正の整数として [ と ]の間に指定します。


    int[] a = new int[5];          // 5個のint型データを要素とする配列
    double[] b = new double[64];   // 64個のdouble型データを要素とする配列

配列の各要素は、自動的にデフォルト値で初期化されます。 デフォルト値は以下の通りです。

デフォルト値
booleanfalse
char'\u0000'
整数(int, longなど)0
実数(double, floatなど)0.0
参照型(オブジェクトや配列)null

配列の長さ

配列は、厳密にはオブジェクトですので、インスタンス・フィールドを 持っています。lengthというインスタンス・フィールドは「配列の要素数」 すなわち「配列の長さ」を表します。


    int[] a = new int[5];
    ...
    System.out.println(a.length);   // 5と出力されます。

配列の要素へのアクセス

配列内の個々のデータを要素と呼び、配列名に添字(そえじ、index)を 付けて表します。 添字の有効範囲は 0 から「要素数-1」までであることに注意して 下さい。 1から「要素数」までを添字の範囲であると 勘違いする人が多いのですが、これは間違いです。

配列の参照の時は、添字には定数だけでなく「式」を記述できます。 すなわち変数や計算式、関数呼び出しなどを書くことができます。

配列を使った例として Array01.java を示します。 これは「指定された月は何日間か」を答えるプログラムです。 配列の添字は0から始まるので、「1月の日数」を「0番目の要素」に、 「2月の要素」を「1番目の要素」に、...、「12月の要素」を 「11番目の要素」に記憶しています。

Array01.java


Array01.javaの実行例
$ javac Array01.java 
$ java Array01 
何月ですか?    2 
2月は28日間です。
sp204: ~/pro 6> java Array01 
何月ですか?    7 
7月は31日間です。
sp204: ~/pro 7> java Array01 
何月ですか?    20 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 19
	at Array01.main(Array01.java:13)
$ 

Array01.javaの実行結果において、月の値としては不適切な 20 が入力されたとき、 「19という添字は範囲を超えている」という Exception が出力されて途中で プログラムの実行が終っていることに注意して下さい。

Java言語では、プログラム実行時に配列にアクセスするときに 添字範囲のチェックを行ないます。 したがって、「負の整数」や「添字範囲を越える大きな整数」 を添字として配列の範囲外の要素にアクセスしようとすると Exception が起きて、プログラムが終了してしまいます。

配列の範囲外にアクセスしないように、 配列のインデックスがおかしな値でないかプログラム内で チェックした方がよいプログラムであると言えます。 不適切な値のときに「入力が間違っています」と表示する ようにArray01.java を変更したプログラムが Array02.java です。

Array02.java


Array02.javaの実行例
sp204: ~/pro 4> javac Array02.java 
sp204: ~/pro 5> java Array02 
何月ですか?    20 
入力が間違っています。
sp204: ~/pro 6> 

不適切な値のときに「入力が間違っています」と表示して 正しい値が入力されるまで何度でも入力させるように Array02.java を変更したプログラムが Array03.java です。

Array03.java


Array03.javaの実行例
$ javac Array03.java 
$ java Array03 
何月ですか?    20 
入力が間違っています。
何月ですか?    6 
6月は30日間です。
$ 

注意

配列は「参照型」です。すなわち、メモリの中でそのデータ用に 確保されているひとまとまりの場所の「先頭の番地」で表されます。 メソッドの引数として配列を渡す場合は、この「先頭の番地」が コピーされて渡されます。 メソッドの中で、個々の要素にアクセスをするとき、 「その番地から始まるデータの何番目」という形式でアクセスされるので、 もしもメソッドの中で配列の要素に代入したとすると、その配列の その要素の値は変わってしまいます。 すなわち、メソッドを呼び出した側でも影響を受ける ことに注意が必要です。


配列の初期化についての補足説明

Java言語では、 配列の初期値を { と } の間にカンマで区切って記述することができます。 たとえば


	int[] m;
	m = new int[12]; // int型を要素とする要素数12個の配列を生成する
	m[0]=31; m[1]=28; m[2]=31; m[3]=30;
	m[4]=31; m[5]=30; m[6]=31; m[7]=31;
	m[8]=30; m[9]=31; m[10]=30; m[11]=31;
という記述は

    int[] m = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
と表現してもよいのです。 したがって、Array03.java は、次のように書き換えることができます。

Array05.java


アルゴリズムA 演習


課題提出〆切は次回の講義の開始時刻です。

課題3a

提出先 http://ynitta.com/class/algoA/local/handin/list.php?id=kadai3a
提出ファイルSalary01.java
コメント欄なし

アルバイトをしていて、その給料の支払日は月末であるとします。 「月」「日」を入力すると、その日から次の給料日までの日数を 表示するプログラム Salary01.java を書いて下さい。 入力された日が支払日当日の場合は0日と答えるものとします。 また閏年は考慮しなくてよいものとします。 各月が何日あるかを記憶するのに、配列を使うとよいでしょう。

間違った月や日の入力に対しては Exception を起こすプログラムで 構わないものとします。 もちろん、間違った入力に対しては「入力が間違いです」と表示 されるようにしても構いません。

Salary01.java


Salary01.javaの実行例
$ javac Salary01.java 
$ java Salary01 
何月ですか?    6 
何日ですか?    30 
月末まであと0日です。
sp204: ~/pro 6> java Salary01 
何月ですか?    6 
何日ですか?    1 
月末まであと29日です。
sp204: ~/pro 7> java Salary01 
何月ですか?    12 
何日ですか?    25 
月末まであと6日です。
$ 

課題3b

提出先 http://ynitta.com/class/algoA/local/handin/list.php?id=kadai3b
提出ファイルNthDay.java
コメント欄なし

月日を入力すると、その日が「その年の何日目であるか」を 表示するプログラム NthDay.java を作って下さい。 閏年は考慮しなくてよいものとします。

間違った月や日の入力に対しては Exception を起こすプログラムで 構わないものとします。 もちろん、間違った入力に対しては「入力が間違いです」と表示 されるようにしても構いません。

NthDay.java


NthDay.javaの実行例
$ javac NthDay.java 
$ java NthDay 
何月ですか?    1 
何日ですか?    1 
1月1日は正月から1日目です。
$ java NthDay 
何月ですか?    2 
何日ですか?    1 
2月1日は正月から32日目です。
$ java NthDay 
何月ですか?    6 
何日ですか?    20 
6月20日は正月から171日目です。
$ java NthDay 
何月ですか?    12 
何日ですか?    31 
12月31日は正月から365日目です。
$ 

課題3c

提出先 http://ynitta.com/class/algoA/local/handin/list.php?id=kadai3c
提出ファイルRevArray.java
コメント欄なし

次のプログラムは、入力された複数の整数を逆順に表示するプログラムです。 配列の要素を逆順にするメソッド void rev(int[]) の定義を自分で作成しなさい。

RevArray.java


RevArray.javaの実行例
sp204: ~/pro 4> javac RevArray.java  
sp204: ~/pro 5> java RevArray  
6  
1 2 3 4 5 6  
6 5 4 3 2 1 
sp204: ~/pro 6> java RevArray  
5  
1 2 3 4 5  
5 4 3 2 1 

課題3d

提出先 http://ynitta.com/class/algoA/local/handin/list.php?id=kadai3d
提出ファイルSalary02.java
コメント欄なし

原稿書きのアルバイトをしていて、その給料の支払日は原稿を 提出した月の翌月末であるとします。 原稿を提出した月日を入力すると、その日から実際に給料が 支払われるまでの日数を表示するプログラム Salary02.java を書いて下さい。 閏年は考慮しなくてよいものとします。 12月に提出した原稿の支払日には工夫が必要です。

Salary02.javaの実行例
$ javac Salary02.java 
$ java Salary02 
何月ですか?    12 
何日ですか?    25 
あと37日です
$ java Salary02 
何月ですか?    6 
何日ですか?    1 
あと60日です。
$ java Salary02 
何月ですか?    4 
何日ですか?    30 
あと31日です。
$ 

課題3e

提出先 http://ynitta.com/class/algoA/local/handin/list.php?id=kadai3e
提出ファイルWaitChristmas.java
コメント欄なし

月日を入力すると、その日からクリスマス(12月25日)まで あと何日であるかを答えるプログラムを書きなさい。 閏年は考慮しなくてよいものとします。 12月26日以降であれば、次の年のクリスマスまでの日数を 答えること。

WaitChristmas.javaの実行例
% javac WaitChristmas.java
% java WaitChristmas
何月ですか?    12
何日ですか?    24
クリスマスまで後1日です。
% java WaitChristmas
何月ですか?    12
何日ですか?    25
クリスマスまで後0日です。
% java WaitChristmas
何月ですか?    1
何日ですか?    1
クリスマスまで後358日です。
% java WaitChristmas
何月ですか?    12
何日ですか?    26
クリスマスまで後364日です。