コンピュータ基礎および演習II

例題

これまではプログラムで行う仕事を、 データとその処理手順を計算を行う順番に最初から順に記述することで、 プログラムを書いてきました。 すなわち、変数に値を入れておき、 演算、条件分岐や繰り返しといった処理を順番に行うことで 仕事を行ってきました。 しかし、このやり方では、 規模の大きなプログラムや 複雑な手順の処理を行うプログラムを書く場合に適していません。 同じデータやそのデータに対する処理手順をひとまとまりにしておき、 必要に応じてその機能を使うことができれば便利です。

Java は「オブジェクト指向 (Object Oriented) 」のプログラミング言語です。 オブジェクト (Object) とは日本語で「モノ」のことです。 オブジェクト指向プログラミング言語では、 プログラムを書く際には、すべてがモノを中心とした考え方をします。

オブジェクト

「オブジェクト」はオブジェクト指向を理解する上で重要な概念です。

現実世界では、犬、机、パソコン、自転車など身の回りにオブジェクトの例を 見ることができます。 このような現実世界のオブジェクトには、 すべて「状態」と「振る舞い」を持つという特徴を備えています。 例えば、犬には状態 (名前、毛色、種類、空腹) があり、 振る舞い (吠える、歩く、お手をする、ひっかく) があります。

プログラムの世界では、オブジェクトは変数で状態を持ち、 メソッドと呼ばれるオブジェクトに関する処理をひとまとめにした処理単位で 振る舞いを実現します。

定義: オブジェクトは、変数とメソッドをまとめたプログラムの部分単位である。

プログラムの世界でオブジェクトを用いると、 現実世界の様々な対象物をプログラムの中に書くことができます。 電話帳の個人データを表すオブジェクトや、 RPGにおける登場人物やアイテムを表すオブジェクトなどです。 また、オブジェクトは画面上のアイコン、ボタンやウィンドウなど GUI (Graphical User Interface) の表示部品を表すためにも使われます。 さらに、抽象的な概念を表すのにも使われます。 マウスのボタンが押されたという行為、 プログラムでエラーが起ったという事態、などです。

ここでは携帯電話の例を用いてオブジェクト指向の考え方を説明しましょう。 エッセンシャル Java pp.118-119 を参照してください。

どのようなものがオブジェクトになるかを見出し、 その特長を分析することは重要な問題です。 ここでは、オブジェクトになるための 2 つの特性について説明します。

1. 属性

オブジェクトは、固有の姿、形、性質などを持っています。 このようなオブジェクトの状態は、属性 (Attribute) として表現されます。

物理的なオブジェクトの属性としては、大きさ、色、重さなどがあります。 携帯電話をオブジェクトとして考えると、機種名、色、電話会社、 電話番号、アドレス帳などが属性です。

これらの属性の値がそのオブジェクトのアイデンティティとなるわけです。 オブジェクトは属性の値によって 他のオブジェクトに対して明確に区別することができます。 オブジェクトは固有の属性値を持つ唯一の存在であると 考えることができます。 このような「固有である」という性質も オブジェクトの重要な特性の一つなのです。

概念的なオブジェクトについて考えてみましょう。 マウスを操作したという行為については、 マウスの位置、押されたボタン、クリックかダブルクリックかドラッグかの別 という属性が考えられます。 また、エラーという事態については、 エラーの原因、エラーの起こったプログラム上の場所などの属性が考えられます。

Java では属性を変数として表します。 この変数のことを属性、あるいはフィールドと呼びます。 (まれにメンバ変数と呼ばれることもありますが、 これは C++ 等の別の言語での呼び方)

2. 操作

各オブジェクトは固有の振る舞いを持ちます。 これを操作 (Operation) と呼びます。 操作は一般的に動詞で表現されます。

携帯電話というオブジェクトの振る舞いとして、 電話に出る、電話をかける、メイルを送信する、 アドレス帳を検索するといった操作を挙げることができます。

概念的なオブジェクトの場合は、 例えば、会議をオブジェクトとすると、 開催場所を設定する、開催日時を設定する、議題を設定する といった操作を考えることができます。

Java では操作をメソッド (Method) として表します。 メソッドとはプログラムの一部分をひとまとまりにして、 ある機能を実現するように一つの単位としてまとめたものです。 (まれにメンバ関数と呼ばれることもありますが、 これは C++ 等の別の言語での呼び方)

メソッドを実行するためには、以下の情報が必要です。

  1. メソッドを実行するオブジェクト名
  2. 実行するメソッドの名前
  3. メソッドに必要なパラメータ

携帯電話の例を見てみましょう。

  1. 携帯電話というオブジェクトに対して、
  2. 電話をかけるというメソッドを実行するように指示する。
  3. その際のパラメータは相手の電話番号である。

このように、メソッドを実行する際には、 オブジェクトに対してメッセージを送るという考え方をします。

クラスとインスタンス

クラス

我々は、実体のモノをある特徴によりグループ化し、 そのグループに対して共通的な名前をつけることで、 モノに対する共通の認識を得ています。 このグループのことを「クラス (Class) 」と言います。

クラスは、同じ特性を持つオブジェクトの集合に名前をつけたものです。 オブジェクトの特性を抽象化したものとも言えます。

これまで説明してきた携帯電話は、 ある特定のひとつの携帯電話を想定してきました。 しかし、世の中には携帯電話は何万台もあります。 それらひとつひとつに、電話会社や電話番号、カメラの有無など 固有の属性がありますが、電話としての性質は共通です。

このようなオブジェクトは携帯電話というクラスに 属していると考えることができます。

Javaでは、オブジェクトを利用するためには、 まずプログラムの中でクラスを記述し、 次にクラスに属するオブジェクトを生成します。 このため、クラスはオブジェクトの設計図である と考えられます。

現実世界において設計図からたくさんの製品が作られるように、 クラスという設計図から、 多くのオブジェクトを生成し利用することが可能になります。

インスタンス

インスタンス (Instance) とは、 クラスから生成されたそのクラスに属するオブジェクトのことです。

クラスからオブジェクトと生成することを インスタンス化すると言います。

一般に、 クラスを定義しただけでは、そのオブジェクトを利用することはできません。 そのクラスの実体であるインスタンスを生成して、 はじめてそのオブジェクトが利用可能になるのです。

問題

「鉄道」というクラスを考えてみます。 鉄道には、都営地下鉄、京成線、北総線などがありますが、 これらは鉄道クラスのインスタンスでしょうか。

問題

「パソコン」というクラスを考えてみます。 パソコンにはキーボード、ディスプレイ、ディスクドライブなどがありますが、 これらはパソコンクラスのインスタンスでしょうか。

Javaによるオブジェクト指向プログラミング

実際にプログラムでオブジェクトを利用するには、 次の 3 つのことを行います。

  1. クラスを定義する。
  2. クラスからオブジェクトを生成する。
  3. オブジェクトのメソッドを実行し処理を行なう。

クラスは、 ある種のオブジェクト全てに共通する変数とメソッドを定義した設計図です。 クラスを自分で一から書くこともありますし、 他人やシステムがあらかじめ用意しているクラスを利用することもあります。

クラスは状態と動作を持つプログラムの部品です。 オブジェクトの状態はその属性として表され、 その動作はメソッドとして記述されます。

例題として、電大生を表すクラスを考えてみます。 このクラスの名前は TDUStudent とします。 このクラスには次の属性があるものとします。

また、次のメソッドがあるものとします。

このようなプログラムは以下のように書くことができます。 ファイル名は TDUStudentMain.java としています。

class TDUStudent {
    int id;
    String name;
    int math;
    int english;
    int physics;

    void setIdentifier(int i, String n) {
        id = i;
        name = n;
    }

    void setScore(int m, int e, int p) {
        math = m;
        english = e;
        physics = p;
    }

    int average() {
        int avg;

        avg = (math + english + physics) / 3;
        return avg;
    }

}

class TDUStudentMain {
    public static void main(String[] args) {
        TDUStudent alice;
        alice = new TDUStudent();

        TDUStudent bob;
        bob = new TDUStudent();

        alice.setIdentifier(1, "Alice");
        alice.setScore(80, 90, 100);

        bob.setIdentifier(2, "Bob");
        bob.setScore(90, 70, 60);

        int a, b;
        a = alice.average();
        System.out.println("Alice's average is " + a);
        b = bob.average();
        System.out.println("Bob's average is " + a);

    }
}

クラス TDUStudent のオブジェクトの利用

このプログラムの実行するには、コマンドプロンプトから、

 
$ java TDUStudentMain

と入力します。 プログラムはコマンド java に続けて指定された クラス TDUStudentMainpublic static void main の部分から実行が始まります。

まず

TDUstudent alice;

alice をクラス TDUStudent のインスタンスを格納する変数として宣言しています。 次に、

alice = new TDUStudent();

で クラス TDUStudent のインスタンスを新たに生成し、 変数 alice に代入しています。

new 演算子は、 指定されたクラスのインスタンスを生成し、 変数に代入できるようにします。

また、この 2 行は次のようにまとめることもできます。

TDUStudent alice = new TDUStudent();

同様に、別の新たなクラス TDUStudent のインスタンスを生成し、 変数 bob に代入しています。

alice.setIdentifier(1, "Alice");

この行はインスタンス alicesetIdentifier というメソッドを、 1"Alice" という情報を与え実行する、という意味です。 メソッド setIdentifier によって、 学籍番号と名前を alice の属性に登録するように メッセージを送っているのです。

1"Alice" はメソッドでの処理に必要なパラメータです。 これらを引数 (ひきすう, Argument) と呼びます。 引数は、メソッド名の後ろにカッコで囲み、コンマで区切って並べます。 メソッドに与える引数がない場合、カッコの中を空欄にします。 カッコ自身を省略することはできません。

メソッド実行の文法は次のようになります。

インスタンス名 . メソッド名 ( 引数 ... ) ;

次に、

alice.setScore(80, 90, 100);

この行では同様に、alice に対してメソッド setScore を実行することで、 数学、英語、物理の各点数が alice の属性に登録されています。

bob についても同様です。

alicebob はクラス TDUStudent に属する具体的な人物を表しています。 このようにひとつのクラスから複数の実体を生成することができます。

a = alice.average();

この行では、alice に対してメソッド average を実行し、 その結果を変数 a に代入しています。 メソッド average では alice の平均点を計算する処理が行われています。 メソッドは処理を行い、その結果の値を返すことができます。 ここでは変数 a に平均値の計算結果を代入しています。

メソッドの実行は式として扱われます。 したがって、実行結果の値を変数に代入したり、表示したりすることができます。 その際には変数の型とメソッドの返す値の型が一致している必要があります。

bob についても同様です。

クラス TDUStudent のクラス定義

次に、 class TDUStudent 以降の記述を見てみます。 ここではクラス TDUStudent の定義が書かれています。

クラスの定義では、変数宣言とメソッドの宣言の 2 つを行います。 クラスの変数はそのオブジェクトの属性を表し、 メソッドにはそのオブジェクトの振る舞いを書きます。

TDUStudent という名前のクラス定義の始めは次のように書きます。

class TDUStudent {

次に、

    int id;
    String name;
    int math;
    int english;
    int physics;

これらの行で id, name, math, english, physics の各属性を宣言しています。 これは今まで学んだ変数宣言と同様の書き方です。 これらの変数はクラスの定義の中では通常の変数と同様に使うことができます。

    void setIdentifier(int i, String n) {

から始まる中カッコで囲まれた部分がメソッド setIdentifier の宣言です。

setIdentifier は、メソッドの実行された相手から、 int 型整数 i と文字列 n を受け取り、 i を属性 id に、n を属性 name に代入しています。

    void setScore(int m, int e, int p) {

も同様です。 int 型整数の m, e, p を受け取り、 それぞれを属性 math, english, physics に代入しています。

    int average() {

は、属性 math, english, physics の各値の平均値を計算するメソッドです。 メソッドを実行した相手から受け取るパラメータがない場合、 カッコ内に何も書く必要はありません。ただしカッコ自身は省略できません。

また、このメソッドは処理結果である平均値を、 return 文でメソッドを実行した相手に返しています。

オブジェクト指向のプログラムの特長

クラスからオブジェクトを生成すれば、 同じ性質のオブジェクトをいくつでも生成でき、 何度でも利用することができるようになります。 また、メソッドの実行によって、 複雑な処理を簡単に何度でも実行できるようになります。

オブジェクト指向のメリットはこれだけではありません。

オブジェクトを使うことによって、 モノを中心にプログラムを書くことができ、 プログラムで扱うデータとそれに対する操作を わかりやすく記述することができます。

一般に、大きなプログラムをひとまとめに書くと理解が難しくなります。 人間はある程度以上に複雑なものを、 すべてを一度に理解することは困難なのです。 このような場合の原則は、全体をいくつかの部分に分けて、 それぞれを別々に理解することです。 オブジェクト指向プログラムはプログラムの中で登場するモノに着目し、 プログラムをオブジェクトの単位で分けることができます。 オブジェクト指向は、人間が現実世界で物事を理解する時の考え方に 近い概念であるため、プログラムの理解が容易になるのです。

オブジェクトは他のプログラムとは独立して仕事をします。 オブジェクトを利用する際は、 オブジェクトが所定の仕事をしてくれると信頼して、 いわばブラックボックスとして考えてしまえば良いのです。

カプセル化と情報隠蔽

メソッド setIdentifier では、 特別な計算処理を行っているわけではなく、 与えられたデータを属性に登録しているだけです。 このような場合、 main の中でメソッド setIdentifier を実行する代わりに、

alice.id = 1;
alice.name ="Alice"

と書くこともできます。 実は、この書き方は勧められるものではありません。 名前を変更せずに、学籍番号だけを変更してしまったり、 その逆を行うこともできてしまいます。 これは多くの場合間違いです。 同じ学籍番号に間違った名前が登録されたり、 勝手に、学生の学籍番号を変更することができてしまうからです。

属性を直接操作するのではなく、 必要な操作だけをメソッドとして用意することで、 想定しないような使われ方をして問題が生じることのないように することが可能です。 オブジェクトは、 データとその操作が一体化されたものなのです。 このような概念をカプセル化と言います。

また、処理を行う際にメソッドを用いることで、 オブジェクトの中にどういう変数があり、 どのような計算が行われているかといった、 オブジェクトの内部構造や処理手順を把握する必要はありません。 知っていれば良いのはメソッドの使い方だけです。 このような概念を情報隠蔽と言います。

文法のまとめ

クラス定義の文法

実は、今まで class 〜 で始めていたプログラム自身もクラスのひとつなのです。 一般に、 Java のプログラムはいくつかのクラス定義を並べた形をしています。

クラス定義の文法は次のとおりです。

class クラス名 {

    int member;                      ←属性とする変数の宣言
    ...                                (属性のないクラスの場合省略可)


    int method(int arg, ... ) {      ←メソッドの宣言
        ...                            (メソッドのないクラスの場合省略可)
    }
    ...
}

メソッド宣言の文法

クラス定義の中にメソッド宣言を書くことができます。 メソッドのないクラスにはメソッド宣言は省くことができ、 複数のメソッドのあるクラスには、 メソッド宣言を次々に並べて書くこともできます。

メソッド宣言の文法的な形は次のとおりです。

結果の型  メソッド名 ( 引数の宣言 )  {
    ...... メソッドの本体
}

「結果の型」は、メソッドがどんな型の値を返すのかを設定します。 整数を返すのであれば int 、 浮動小数点数を返すのであれば double と書きます。 値を返さないメソッドの場合 void と書きます。

「引数の宣言」の部分には、引数の型と変数名を並べて書きます。 書き方は普通の変数宣言とほぼ同様ですが、 セミコロンは書きません。引数が複数個ある場合は、コンマで区切ります。 また、引数のないメソッドの場合、カッコの中を空欄にします。

セミコロンがないので、 同じ型の引数であっても int a,b のような書き方は間違いです。 引数ひとつごとに int a, int b のようにし、 別々に型を書かなければいけません。

メソッド宣言の最初の一行の例をいくつか示しておきます。

int result(int a) 
void locate(int x, int y) 
void printInfo()
double measure(double x, int n)

メソッドの本体の書き方はこれまでのプログラムと同じです。 通常の変数宣言や、文を並べて書けば良いのです。

引数

引数 (ひきすう) は、メソッドの実行元からメソッド内へ値を引き渡す手段です。

メソッドの中から見た引数、 例えば setIdentifier のメソッド宣言における変数 i, n を 仮引数 (かりひきすう) と呼びます。 一方、実行元から引数に渡すもの、例えば main の中の alice.setIdentifier(1, "Alice"); での 1"Alice" のことを実引数 (じつひきすう) と呼びます。

メソッドの中では、 仮引数は、実引数によって初期値が決まっているというだけで、 普通の変数と同様に使うことができます。

引数を使った値の受渡しについては、少し変則的な決まりがあります。 詳しい説明は後まわしにしますが、 次のことを頭の隅に入れておいてください。

引数を使ってメソッドから実行元に値を返したい場合もあります。 整数と実数の場合、これは今のところ不可能であると考えてください。 引数は実行元からメソッド内へ一方通行で値を渡すことだけができ、 一度値が渡された後は、それぞれは別の変数として使うことになります。 一方で値を変更しても、その影響が他方に及ぶことはありません。

ただし、配列、文字列、およびインスタンスを引数とした場合、 これらの値を変えると、実行元での値も同時に変化します。 つまり、配列、文字列、インスタンスの場合は、 引数を通して値を返すことができます。

return 文

メソッドから値を返すことを記述するための文が return 文です。 return 文の形式は次のとおりです。

return  式 ;

return の後に返したい値を求める式を書きます。 この式は、 そのメソッドの先頭で宣言した結果の値と同じ型の式である必要があります。

値を返さないメソッド、すなわちメソッドの宣言の先頭で void と書いたメソッドの場合、 単に return; と書くだけで、 後の式は書く必要ありません。 また、 return 文を省略しても、 メソッド宣言の中カッコの最後まで処理が達すれば、 そのメソッドの処理は終了します。 ただし、値を返すメソッドの場合 return 文を省略することはできません。

return 文を実行すると、その時点でメソッドの実行は終わりになり、 メソッドが実行された相手にプログラムの処理が戻ります。 return という言葉には「返す」と「戻る」両方の意味が含まれている と考えれば良いでしょう。