第9週:オブジェクトとは


9. 1 復習(1)
9. 2 オブジェクト
9. 3 インスタンス変数
9. 4 練習問題
9. 5 インスタンスを作る
9. 6 練習問題
9. 7 インスタンスのメソッド
9. 8 練習問題
9. 9 クラス変数とクラスメソッド
9.10 宿題
9.11 おまけ

プログラミングの基本的な概念を第8週までで一通り説明しました。これまで に出てきた概念は、Java 言語だけに限らず、他の言語でプログラムを書く場 合にも必要になるものです。今週以降は、復習の節を設けますので、ここまで のところに不安がある人は、習ったことを確認して完全に理解するようにして ください。

9.1 復習(1)

Java 言語のプログラムはクラスとメソッドで出来ています。メソッドの中で は普通上から下に順に実行していきますが、実行の流れを変える文がいくつか あります。

[問]1から100までの数字のうち、偶数だけの合計を求めるにはどうすればよ いでしょう?for文を使う方法、while文を使う方法、 for文とif文を使う方法など、何種類もの解き方がありま す。いくつ考えつきますか?

9.2 オブジェクト

プログラミング言語にはいろいろな種類がありますが、その中にオブジェ クト指向プログラミング言語(object oriented programming language) と呼ばれるものがあり、Java 言語もその一つです。これは、計算の単位とし てオブジェクトと呼ばれるものを使います。

オブジェクト(object)とは、いくつかのデータやメソッドをまと めて一塊にしたものです。小さなプログラムを作るときはそれほど便利さを感 じませんが、大きなプログラムを作るときは、オブジェクトに分割することに よって見通しが良くなるので重要です。

オブジェクトは、直接ソースファイルの中に書くものではありません。ソース ファイルの中に書くのはクラスです。オブジェクトは、クラスを基にして作り ます。例えば、タコ焼の鉄板をクラスとすると、それで焼いたタコ焼がオブジェ クトになります。クラス A を基にして作ったオブジェクトを、クラ スAインスタンス(instance)と呼びます。一つのクラ スから、いくつでもインスタンスを作ることができます。

9.3 インスタンス変数

それぞれのインスタンスは、データを覚えておくための変数をいくつか持つこ とができます。これをインスタンス変数(instance variable)と呼 びます。同じクラスのインスタンスは、同じ名前のインスタンス変数を持ちま すが、変数が記憶しているデータはインスタンスごとに違います。

インスタンス変数の変数宣言は、クラス宣言の本体の中で、どのメソッド宣言 にも入らない場所で行います。例えば、第1週の例題
class Count extends SfcDoNothing {
    int x = 0;
    void sfcProcess () {
	x = x + 1;
	sfcOutput(Integer.toString(x));
    }
}
では変数 x はインスタンス変数です。しかし、第6週の練習問題
class Precision extends SfcDoNothing {
    void sfcProcess () {
	double x = 0.0;
	for (int i = 1000000; i >= 1; i = i - 1) {
	    x = x + 1.0 / i;
	}
	sfcOutput(Double.toString(x));
    }
}
に出てくる x はメソッド宣言の中で宣言されているので、インスタ ンス変数ではありません。メソッド宣言の中に変数宣言があるような変数を ローカル変数(local variable)と呼びます。インスタンス変数、 ローカル変数、仮引数の違いは次の通りです。

インスタンス変数ローカル変数仮引数
変数宣言クラス宣言の中でメソッド宣言の外メソッ ド宣言の中メソッド名の直後の括弧の中
有効範囲プログラム全体(注1)変数宣言を含む最も内側 のブロックだけ(注2)そのメソッドだけ
初期値インスタンスが作られた時に初期値が代入される 変数宣言が実行されるときに初期値が代入されるメソッド 呼び出しの時に実引数が代入される
存続期間ずっと存在するブロックの実行が終了す ると消えてしまう(注2)メソッドの実行が終了すると消えてしまう

(注1)そのインスタンス自身が使うときは変数名だけを書きますが、他のイン スタンスで使うときは、

    インスタンス.変数名
と書きます。

(注2)for の直後の括弧の中で宣言された変数は、有効範囲がその for 文全体で、存続期間はその for 文が終了するまでで す。

9.4 練習問題

上の第1週の例題で、変数宣言の位置を次のように変えるとどうなるでしょう か。ボタンをクリックしたときの動作を予想し、実際にそうなるか確かめなさ い。
class Count extends SfcDoNothing {
    void sfcProcess () {
        int x = 0;
	x = x + 1;
	sfcOutput(Integer.toString(x));
    }
}

9.5 インスタンスを作る

今まで練習で書いてきたクラスは、SfcExample1 または SfcExample2 がインスタンスを一つだけ作って、その中のメソッド を呼び出してくれていました。今度は自分でインスタンスを作る練習をしましょう。

クラス A のインスタンスを新しく作るには、

    new A(引数)
と書きます。引数のところには、そのインスタンスに与えるデータを書きます。 同じ鉄板を使って焼くタコ焼でも、中にはタコを入れたり、イカを入れたりで きます。

作ったインスタンスを記憶しておくには、そのクラスに対応する型の変数に代 入しなければなりません。クラス A のインスタンスを作って、変数 x に覚えておくには、次のようにします。

    A x = new A(引数);
違うクラスの型の変数に代入することはできません。お好み焼きの皿にタコ焼 を盛り付けてはいけません。
ただし、二つのクラスが継承関係にある場合は、スーパークラスの型の変数に サブクラスのインスタンスを代入することができます。

先週の国別の人口と GDP のデータを考えてみましょう。
Country Total Population
(in thousands, 1997 est.)
GDP
(million US$, 1995)
United Kingdom 58201 1102658
Egypt 64466 60436
India 960178 338785
Japan 125638 5217573
Australia 18250 358147
United States of America 271648 6954787
Brazil 163132 717187
国名は文字列、人口と GDP は整数なので、一つの配列にすることはできませ ん。三つの配列に分けて記憶しますが、もしデータの種類が増えると、そのた びに新しい配列を追加し、ソーティングのメソッドを書き直さなければいけな いので、大変です。そこで、一つの国のデータは、一つのオブジェクトにまと めてしまいましょう。国名、人口、GDP の三つのインスタンス変数を持つクラ スを作ります。

    class Nation {
        String name;
        int population;
        int gdp;
    }
これだけでも、new Nation() とすれば、インスタンスを作ることが できます。しかし、そのインスタンスの変数の中身は空っぽなので、役に立ち ません。後から変数それぞれに代入していくこともできますが、それではかえっ て面倒です。そこで、クラスの中にコンストラクタ(constructor) を宣言して、インスタンスを作るときにその中身も作るようにします。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Nation {
    String name;
    int population;
    int gdp;

    Nation(String n, int p, int g) {
        name = n;
	population = p;
	gdp = g;
    }
}

クラス Nation を使って、グラフを描くクラス Nations を書いてみましょう。ファイル NationGraph.java の内容を次のように 書いてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.awt.*;

class Nation {
    String name;
    int population;
    int gdp;

    Nation(String n, int p, int g) {
        name = n;
	population = p;
	gdp = g;
    }
}

class NationGraph extends SfcDoNothing {
    void sfcDraw(Graphics g) {
	Nation data[] = new Nation[7];
	data[0] = new Nation("UK", 58201, 1102658);
	data[1] = new Nation("Egypt", 64466, 60436);
	data[2] = new Nation("India", 960178, 338785);
	data[3] = new Nation("Japan", 125638, 5217573);
	data[4] = new Nation("Australia", 18250, 358147);
	data[5] = new Nation("USA", 271648, 6954787);
	data[6] = new Nation("Brazil", 163132, 717187);
	for (int i = 0; i < data.length; i = i + 1) {
	    g.setColor(Color.black);
	    g.drawString(data[i].name, 10, i*30+30);
	    g.setColor(Color.blue);
	    g.fillRect(80, i*30+15, data[i].population*200/1000000, 10);
	    g.setColor(Color.green);
	    g.fillRect(80, i*30+25, data[i].gdp*200/7000000, 10);
	}
    }
}

これを

    % javac NationGraph.java
でコンパイルすると、NationGraph.classNation.class という二つのクラスファイルができます。実行すると きは、SfcDoNothing を拡張したクラスを指定するので、
    % java sfcExample2 NationGraph
とします。

9.6 練習問題

Nation クラスのデータをソートするプログラムを作りましょう。

  1. まず、二つの Nation クラスのインスタンスのどちらが前に来るか を調べるメソッドを作ります。入力文字列により、国名、人口、GDP のどれで 比べるかを指定します。返り値は、xy より前なら true、逆なら false にします。
        Boolean CompareNation(Nation x, Nation y, String key) {
            if (key.equals("name")) {
                ......
    
  2. 配列の n 番目以降で、一番前に来るデータを探すメソッドを作りま す。これも、どれで比べるかを指定します。返り値は、一番前に来るデータの 添字です。
        int findHead(Nation a[], int n, String key) {
            Nation head = a[n];
    	int index = n;
    	for (int i = .....
    
  3. 配列の n 番目と m 番目を入れ替えるメソッドを作ります。
        void swap(Nation a[], int n, int m) {
            Nation tmp = a[n];
    	........
    
    メソッド呼び出しの時、仮引数には、実引数の値がコピーされます。この値を 変更しても、オリジナルの実引数は影響を受けません。ただし、配列やインス タンスの場合、実体は一つだけで、コピーされるのはその実体を指し示す矢印 です。したがって、メソッドの中で配列のデータを入れ替えると、実体の内容 が変更され、これはメソッドを呼び出した方にも影響します。
  4. 以上のメソッドを組み合わせて、ソーティングのプログラムを作ります。

9.7 インスタンスのメソッド

インスタンスは、変数の中にデータを記憶するだけでなく、メソッドを実行す ることで計算も行います。これまではインスタンスが1個しかなかったので、 どのインスタンスのメソッドかということは考えませんでしたが、メソッドを 実行するときは、必ずそれを実行しているインスタンスが存在します。

国別の、一人当たり GDP を計算するメソッドを追加しましょう。

class Nation {
    String name;
    int population;
    int gdp;

    Nation(String n, int p, int g) {
        name = n;
	population = p;
	gdp = g;
    }

    int gdpPerPerson() {
        return gdp/population;
    }
}
gdpPerPerson を呼び出すときは、実行するインスタンスを指定して、
    インスタンス.gdpPerPerson()
とします。引数がないことに注意してください。計算に必要な人口と GDP の 値は、インスタンスの中にある変数の値を使います。先ほどのグラフの、GDP の代わりに一人当たり GDP を表示するプログラムは次のようになります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.awt.*;

class Nation {
    String name;
    int population;
    int gdp;

    Nation(String n, int p, int g) {
        name = n;
	population = p;
	gdp = g;
    }

    int gdpPerPerson() {
        return gdp/population;
    }
}

class NationGraph extends SfcDoNothing {
    void sfcDraw(Graphics g) {
	Nation data[] = new Nation[7];
	data[0] = new Nation("UK", 58201, 1102658);
	data[1] = new Nation("Egypt", 64466, 60436);
	data[2] = new Nation("India", 960178, 338785);
	data[3] = new Nation("Japan", 125638, 5217573);
	data[4] = new Nation("Australia", 18250, 358147);
	data[5] = new Nation("USA", 271648, 6954787);
	data[6] = new Nation("Brazil", 163132, 717187);
	for (int i = 0; i < data.length; i = i + 1) {
	    g.setColor(Color.black);
	    g.drawString(data[i].name, 10, i*30+30);
	    g.setColor(Color.blue);
	    g.fillRect(80, i*30+15, data[i].population*200/1000000, 10);
	    g.setColor(Color.orange);
	    g.fillRect(80, i*30+25, data[i].gdpPerPerson()*200/80, 10);
	}
    }
}

9.8 練習問題

前の練習問題のプログラムを変更し、"GDP per person" と入力する と、一人当たり GDP の多い順に並べ替えてグラフを描くようにしなさい。

9.9 クラス変数とクラスメソッド

変数宣言やメソッド宣言の前に static と書くと、その変数やメソッ ドはインスタンスにではなく、クラスに属することになります。そのような変 数、メソッドを使うときは、

    クラス名.変数名
および
    クラス名.メソッド名(引数)
と書きます。例えば、Integer.parseInt() は、Integer クラスに属する parseInt メソッドです。クラスに属する変数、メ ソッドは、インスタンスを作らなくても使えます。

9.10 宿題

次のような入試結果があるとします。
受験番号英語数学小論文
1001617550
1002805270
1003736880
1004405240
1005568260

各受験生の得点は、英語と数学のどちらか良い方と小論文の合計点であるとし ます。各受験生を表すクラスと、得点を計算するメソッドを作り、そのインス タンスを使って得点の高い順にソートするプログラムを作りなさい。

9.11 おまけ

StringTokenizer クラスを使うと、文字列をコンマなどの区切りで 分解して取り出すことができます。使い方は次の通りです。

  1. ファイルの最初に
        import java.util.StringTokenizer;
    
    を書きます。
  2. StringTokenizer クラスのインスタンスを作ります。
        new StringTokenizer(String s, String separator);
    
    s は分解したい文字列、separator は区切り文字です。例 えば、"100,200,40" という文字列をコンマで区切って分解したいと きは、
        StringTokenizer st = new StringTokenizer("100,200,40", ",");
    
    とします。
  3. 区切り文字で区切られた部分を一つずつ StringTokenizer のインス タンスのメソッドで読み出します。
         String nextToken()
    
    上の例では、最初に st.nextToken() を実行すると、 "100" が、2回目に実行すると "200" が、3回目に実行す ると "40" が返ってきます。
これを使って、X 座標、Y 座標、半径を入力し、その位置に円を描くプログラ ムを作りなさい。


慶應義塾大学の授業以外での無断利用、複製はご遠慮下さい。
Copyright (c) 1999 慶應義塾大学