複合データ

今回の内容

今回は複数のデータをひとかたまりのデータとして取り扱う「複合データ」というものを紹介します。これまでに紹介したデータの種類は整数、実数、文字列、論理値、またはその配列でしたが、これらを複合した(組み合わせた)新しいデータをプログラムから利用できるようになります。

1つのプログラムでは数多くのデータを取り扱いますが、その中にはセットにして使わなければいけないものもあります。下記はこれまでに何回か例として出した「クイズ」に関するデータですが、これには6個のデータがあります。

問題文 正解
10 + 20 = ? 30
リンゴの色は? (1) 青 (2) 赤 2
うるう年の1年の日数は? 366

この「問題文」と「正解」は問題ごとにセットにして取り扱わなければなりません。たとえば「うるう年の1年の日数は?」という問題文に「2」という正解を組み合わせるとクイズとして成立しなくなります。

図 1 問題文と正解の組合せ

今回紹介する複合データは、「問題文と正解」といったような複数のデータをひとかたまりのデータとして取り扱うための仕組みです。この2つを複合して「クイズの問題」という新しいデータの種類をプログラムに導入すると、問題文とそれに対する正解が食い違ったりするなどの問題が起こらなくなります。


図 2 複合データによる問題文と正解

Javaでこのような複合データを取り扱うには「クラス」という機能を利用します。これは「どのようなデータを組み合わせて複合データを作るか」という複合データの形式をコンピューターに教えるためのもので、たとえば先ほどのクイズでは「問題文と正解を組み合わせてクイズの問題を作る」ということをあらかじめクラスとして宣言しておきます。


図 3 クラス

「クラス」はあくまで複合データの形式を決めるためのもので、そこから「インスタンス」というものを生成してから使います。それぞれのインスタンスはクラスで定めた形式と同じデータを組み合わせた複合データで、今回はクラスを使って「問題文と正解を持つ複合データを生成する」ということをプログラムで書きます。

図 4 インスタンスの生成

つまり、Javaではクラスで複合データの形式を書き、クラスからインスタンスを生成して複合データとして使います。このクラスやインスタンスは現実の問題をプログラムで取り扱う場合に非常に便利で、世の中に存在するJavaでプログラミングされた多くの実用的なソフトウェアは、この「クラス」や「インスタンス」を多用しています。

今回の作業

今回の例題、練習問題、課題は以下のパッケージに作成してください。

パッケージの名前
j1.lesson08

クラスの作成方法については、「クラスを作成する」を参照してください。

複合データ

クラスの宣言

クラスは複合データの形式を表していて、そこから生成するインスタンスがどのようなデータを持つのかをあらかじめ決めておくことができます。たとえば、「書籍の情報」というものを表すクラスは次のような形式を表すと思います。

性質 説明
タイトル 書籍のタイトル
著者 著者の氏名
出版社 出版社の名前
ページ数 合計ページ数
価格 日本円での税抜価格

現実世界の書籍にはここに挙げていない無数の性質がありますが、プログラムから取り扱う場合には必要な性質だけを考えてクラスを作ることになります。もう少し単純な例として先ほどの「クイズの問題」について考えると、これは次のような形式を表します。

性質 説明
問題文 問題を出題する際の文章
正解 問題の正解

それでは、この「クイズの問題」というクラスを利用したプログラムを実際に書いてみましょう。Javaのプログラムで複合データを使うには、次の3つのステップが必要です。

  1. クラスを宣言する
  2. クラスを元にインスタンスを生成する
  3. インスタンスを使って複合したそれぞれのデータを操作する

まずはプログラムにクラスを宣言しましょう。今までと同様の方法でソースプログラムQuiz.javaを作成し、プログラムの末尾に次のように「class Question { }」という記述を追加します。

リスト: Quiz(クラスの宣言)
...
public class Quiz {
    ...
}

class Question {
}

これで「Question」という「クイズの問題」を表す新しいクラスを宣言できました。しかしまだこのクラスは空なので「クイズの問題」に関する形式を表していません。次はどのようなデータを複合するかを「フィールド」として宣言します。フィールドは複合するそれぞれのデータを記憶させるための変数のようなもので、これまでに紹介した変数と同様の方法で、先ほど書いたQuestionクラスの中に宣言します。

リスト: Quiz (フィールドの宣言)
class Question {
    String statement;
    int correct;
}

今回は「問題文」を文字列型のstatementというフィールドで、「正解」を整数型のcorrectというフィールドで宣言しました。このとき、「int correct = 0;」のようにフィールドの初期値を指定してもかまいませんが、あまり意味はないので省略しています。

以上で「クイズの問題」というクラスを作成できましたが、これはあくまでクイズの問題に関する「形式」を表すもので、具体的なクイズについては別に作成することになります。その前に、クラスを宣言する方法について一度まとめておきます。

クラスを宣言するには、プログラムの末尾に次のような形式で書きます。

class <クラスの名前> {
    <フィールドの宣言>
}

それぞれのフィールドの宣言は、次のように型と名前を指定します。これは通常の変数と同じ形式です。

<フィールドの型> <フィールドの名前>;

以上でクラスの宣言は終了です。次はこのクラスを使って具体的な「クイズの問題」を作成してみましょう。

練習: クラスの宣言

作成するクラスの名前
ProfileViewer

プログラムを作成し、そこに自分や知り合いのプロフィール情報を表すProfileクラスを宣言しなさい。Profileクラスには名前や年齢など、プロフィールに必要なデータの形式(名前、年齢など)を2~5個程度のフィールドとして宣言すること。

インスタンスの生成

先ほどのクラスは「複合データの形式」を表していて、プログラムから複合データそのものを扱う際には、クラスから「インスタンス」というものを生成してから使います。このインスタンスは「具体的な複合データ」を表すもので、たとえばクイズの問題の「インスタンス」には、次のように「問題文」や「正解」に対して具体的なデータが与えられます。

  • 「10 + 20 = ?」という問題文で、正解は30
  • 「リンゴの色は? (1) 青, (2) 赤」という問題文で、正解は2
  • 「うるう年の1年の日数は?」という問題文で、正解は365

それでは、実際に「クイズの問題」クラスのインスタンスを作成してみましょう。startメソッドの先頭に次のように書きます。

リスト: Quiz(インスタンスの生成)
void start() {
    Question question = new Question();
}

「Question question = …;」というのは通常の変数の宣言と同じ方法ですが、intやStringなどを指定する場所にQuestionというクラスの名前を指定しています。また、=の右側では、配列を生成した際と少しだけ似た「new Question()」のように書いています。

まず重要なのは、クラスを宣言すると「<クラスの名前> <変数の名前> = …;」といったように、クラスの名前を型の部分に指定して変数を宣言できるようになるという点です。これはただの変数宣言なのでquestion以外に好きな名前を付けられますが、Questionクラスのインスタンスを利用する場合には「Question <変数の名前>」のように型の部分にQuestionクラスの名前を指定して下さい。

次に、「new Question()」は「Questionクラスのインスタンスを生成する」という命令です。生成したインスタンスは整数の100や文字列の"Hello"などと同様に、値として利用できます。このため、この値は型にQuestionクラスを指定した変数に記憶させることも可能です。

上記のような方法で宣言したクラスのインスタンスを生成できました。一度まとめると、次のような形です。

<クラスの名前> <変数の名前> = new <クラスの名前>();

インスタンスの生成だけならば「new Question()」の部分だけで十分です。しかし、通常のプログラムではインスタンスを生成して何もしないことはほとんどないと思いますので、上記のように変数に記憶させるところまで一気に書いてしまうのがよいと思います。

練習: インスタンスの生成

利用するクラスの名前
ProfileViewer

先ほどの練習で作成したプログラムのうちstartメソッドを書き換えて、そこでProfileクラスのインスタンスを生成し、変数に記憶させなさい。

フィールドの利用

生成したインスタンスには、まだ具体的な問題文や解答が設定されていません。次に、これらのデータを設定する方法を紹介します。ここでは、次のような具体的なデータを考えてみましょう。

性質 具体的な値
問題文 (statement) 10 + 20 = ?
正解 (correct) 30

これらのデータをインスタンスに設定するには、次のようにインスタンスの後ろに . (ドット)を書き、さらにフィールドの名前を指定します。これらは変数への代入と同じ書き方で「フィールドに値を代入する」と言います。

リスト: Quiz(フィールドの設定)
void start() {
    Question question = new Question();
    question.statement = "10 + 20 = ?";
    question.correct = 30;
}

これらのデータをプログラムから利用するには、設定したときと同じように「question.statement」や「question.correct」のように書きます。それぞれ、問題文を表すstatementフィールドと、解答を表すcorrectフィールドに代入した値を利用します。

リスト: Quiz(フィールドの利用)
void start() {
    Question question = new Question();
    question.statement = "10 + 20 = ?";
    question.correct = 30;
    String input = JOptionPane.showInputDialog(question.statement);
    int answer = Integer.parseInt(input);
    if (answer == question.correct) {
        JOptionPane.showMessageDialog(null, "正解です");
    }
    else {
        JOptionPane.showMessageDialog(null, "不正解です");
    }
}

以上で、Questionクラスを使ってクイズを出題する単純なプログラムが完成しました。Questionクラスはそのまま使うのではなく、「new Question()」という書き方でインスタンスを生成してから使います。問題文や解答を利用するには生成したインスタンスに対して「instance.statement」や「instance.correct」のように、それぞれのフィールドに値を代入します。

まとめると、インスタンスのフィールドを利用するには次のように書きます。

<変数の名前>.<フィールドの名前>

練習: フィールドの利用

利用するクラスの名前
ProfileViewer

先ほど練習で作成したプログラムを書き換え、変数に記憶させたProfileクラスのインスタンスについて、すべてのフィールドにあなた(または架空の人間)のプロフィールを設定しなさい。また、それぞれのフィールドに代入した値をメッセージダイアログに表示しなさい。

インスタンスを引数に取るメソッド

次のプログラムを見てみましょう。これは先ほどまでのクイズを出題するプログラムを少し書き換えたものですが、実行しても変更前と同じ動作をするようになっています。

リスト: Quiz(インスタンスを引数にとるメソッド)
package j1.lesson08;

import javax.swing.JOptionPane;

public class Quiz {

    public static void main(String[] args) {
        new Quiz().start();
    }

    void start() {
        Question question = new Question();
        question.statement = "10 + 20 = ?";
        question.correct = 30;
        showQuestion(question);
    }

    void showQuestion(Question question) {
        String input = JOptionPane.showInputDialog(question.statement);
        int answer = Integer.parseInt(input);
        if (answer == question.correct) {
            JOptionPane.showMessageDialog(null, "正解です");
        }
        else {
            JOptionPane.showMessageDialog(null, "不正解です");
        }
    }
}

class Question {
    String statement;
    int correct;
}

一番大きな変更は、クイズを出題する部分をshowQuestionメソッドに切り出している点です。変数と同様に、型の部分にQuestionクラスの名前を指定した仮引数を利用できます。

リスト: Quiz(showQuestionの宣言)
void showQuestion(Question question) {
    String input = JOptionPane.showInputDialog(question.statement);
    int answer = Integer.parseInt(input);
    if (answer == question.correct) {
        JOptionPane.showMessageDialog(null, "正解です");
    }
    else {
        JOptionPane.showMessageDialog(null, "不正解です");
    }
}

startメソッドでは、showQuestionメソッドを起動しています。このとき、実引数にはQuestionクラスのインスタンスを指定しています。これで、「"10 + 20 = ?"という問題文で、正解は30」という問題がshowQuestionメソッドに渡され、問題が出題されます。

リスト: Quiz(showQuestionの起動)
void start() {
    Question question = new Question();
    question.statement = "10 + 20 = ?";
    question.correct = 30;
    showQuestion(question);
}

このように、インスタンスはメソッドの引数として渡すことも可能です。変数やメソッドの仮引数には整数(int)や文字列(String)だけでなく、自分で定義したクラスの名前(Questionなど)も指定できます。また、メソッドを起動する際にも、これまでと同様に仮引数にあった値を指定すればよいので、Question型の仮引数に対してQuestionクラスのインスタンスを渡せます。

練習: インスタンスを引数に取るメソッド

利用するクラスの名前
ProfileViewer

練習で作成したプログラムを書き換え、Profileクラスのインスタンスの内容をメッセージダイアログに表示するメソッドを新しく作成しなさい。startメソッドからはそのメソッドを起動してインスタンスの内容を表示すること。

インスタンスを返すメソッド

次は、インスタンスを生成して返すメソッドについて見てみましょう。先ほどまでのプログラムを書き換えています。

リスト: Quiz(インスタンスを返すメソッド)
package j1.lesson08;

import javax.swing.JOptionPane;

public class Quiz {

    public static void main(String[] args) {
        new Quiz().start();
    }

    void start() {
        Question question = inputQuestion();
        showQuestion(question);
    }

    Question inputQuestion() {
        String statement = JOptionPane.showInputDialog("問題文を入力");
        String correct = JOptionPane.showInputDialog("正解番号を入力");
        Question question = new Question();
        question.statement = statement;
        question.correct = Integer.parseInt(correct);
        return question;
    }

    void showQuestion(Question question) {
        String input = JOptionPane.showInputDialog(question.statement);
        int answer = Integer.parseInt(input);
        if (answer == question.correct) {
            JOptionPane.showMessageDialog(null, "正解です");
        }
        else {
            JOptionPane.showMessageDialog(null, "不正解です");
        }
    }
}

class Question {
    String statement;
    int correct;
}

今回の変更では、インスタンスを生成する部分をinputQuestionメソッドに切り出し、生成したインスタンスを返すようにしています。重要なのは戻り値の型で、最初に宣言したQuestionクラスの名前を指定しています。

リスト: Quiz(inputQuestionの宣言)
Question inputQuestion() {
    String statement = JOptionPane.showInputDialog("問題文を入力");
    String correct = JOptionPane.showInputDialog("正解番号を入力");
    Question question = new Question();
    question.statement = statement;
    question.correct = Integer.parseInt(correct);
    return question;
}

このメソッドの中では、問題文と正解を入力するダイアログを表示し、そこで入力された値をそれぞれのフィールドに設定しています。また、生成したインスタンスを最終的にreturn文に渡しています。

startメソッドではこのinputQuestionメソッドを起動しています。このinputQuestionメソッドはQuestionクラスのインスタンスを返すので、型にQuestionを指定した変数に記憶させています。

リスト: Quiz(inputQuestionの起動)
void start() {
    Question question = inputQuestion();
    showQuestion(question);
}

メソッドの仮引数と同様に、メソッドの戻り値型にもQuestionなどの宣言したクラスの名前を指定できました。そのようなメソッドは、対応するクラスのインスタンスをreturn文に指定すれば、メソッドの起動元に生成したインスタンスを返せます。

このように、Questionなどの宣言したクラスの名前は、そのインスタンスの「型」としてプログラム中で利用できます。これは、たとえば変数や仮引数の宣言、メソッドの戻り値、次に紹介する配列の要素などにこの「型」を指定することで、宣言したクラスのインスタンスをそれぞれの場所で利用できるようになります。

練習: インスタンスを返すメソッド

利用するクラスの名前
ProfileViewer

練習で作成したプログラムを書き換え、Profileクラスのインスタンスを生成して返すメソッドを作成しなさい。このメソッドの中ではProfileクラスで宣言したそれぞれのフィールドに、入力ダイアログで入力した値を代入してから返すこと。また、startメソッドを書き換えて、作成したメソッドを利用するようにしなさい。

クラスと配列

最後に、これまでの内容と配列を組み合わせたプログラムを見てみましょう。これも先ほどのプログラムを書き換えたものです。

リスト: Quiz(クラスと配列)
package j1.lesson08;

import javax.swing.JOptionPane;

public class Quiz {

    public static void main(String[] args) {
        new Quiz().start();
    }

    void start() {
        Question[] questions = new Question[3];

        for (int i = 0; i < questions.length; i++) {
            questions[i] = inputQuestion();
        }

        for (int i = 0; i < questions.length; i++) {
            Question question = questions[i];
            showQuestion(question);
        }
    }

    Question inputQuestion() {
        String statement = JOptionPane.showInputDialog("問題文を入力");
        String correct = JOptionPane.showInputDialog("正解番号を入力");
        Question question = new Question();
        question.statement = statement;
        question.correct = Integer.parseInt(correct);
        return question;
    }

    void showQuestion(Question question) {
        String input = JOptionPane.showInputDialog(question.statement);
        int answer = Integer.parseInt(input);
        if (answer == question.correct) {
            JOptionPane.showMessageDialog(null, "正解です");
        }
        else {
            JOptionPane.showMessageDialog(null, "不正解です");
        }
    }
}

class Question {
    String statement;
    int correct;
}

今回はinputQuestionメソッドやshowQuestionメソッドの内容を変更していません。startメソッドだけを書き換えています。

リスト: Quiz(インスタンスの配列)
void start() {
    Question[] questions = new Question[3];
    for (int i = 0; i < questions.length; i++) {
        questions[i] = inputQuestion();
    }
    for (int i = 0; i < questions.length; i++) {
        Question question = questions[i];
        showQuestion(question);
    }
}

このプログラムで重要なのは、Question[]というQuestionのインスタンスを要素に取る配列を利用している点です。これまでの配列は整数(int)、実数(double)、文字列(String)のいずれかでしたが、今回は自分で定義したクイズの問題(Question)に対する配列を利用しています。「new Question[3]」と書きましたので、Questionクラスのインスタンスを3つだけ記憶させられる配列を作っています。

配列とクラス(インスタンス)を組み合わせると、次のような表形式のデータをプログラムから簡単に表せます。このような表形式は「テーブル」と呼ばれ、コンピューターの世界ではよく利用されています。たとえばコンピューターにインストールされた音楽プレーヤーは、曲の名前やアーティストの名前などを列に取り、それぞれの行に具体的な曲の情報が表示されていると思います。また、みなさんの出席情報や成績情報なども、このようなテーブル情報で作成しておくと集計が楽に行えますので、そうしていると思います。

インデックス 問題文 正解
0 10 + 20 = ? 30
1 リンゴの色は? (1) 青 (2) 赤 2
2 うるう年の1年の日数は? 366

このように、Javaでは様々な種類の配列を生成できます。今回はQuestionクラスのインスタンスを要素に取る配列を生成しましたが、別のクラスのインスタンスについても同じことが言えます。従来のintやStringなどと合わせて、配列の生成方法は次のような共通の形式で書けます。

<要素の種類>[] <変数の名前> = new <要素の種類>[<配列の大きさ>];

復習しておくと、このプログラムで使っているfor文は配列の要素を順番に利用するための典型的なパターンです。1つ目のfor文では配列の各要素にインスタンスを代入し、2つ目のfor文では各要素をメソッドの実引数に指定しています。

リスト: 繰り返しによる配列の生成と要素の設定
Question[] questions = ...;
for (int i = 0; i < questions.length; i++) {
    ... questions[i] ...
}

以上のように、クラスやインスタンスと配列を組み合わせることができました。この組合せはJavaを使う上で非常に大切なので、何度も練習しましょう。

練習: クラスと配列

利用するクラスの名前
ProfileViewer

練習で作成したプログラムのstartメソッドを書き換え、Profileクラスの配列を生成してそこに複数人のプロフィール情報(Profileクラスのインスタンス)を代入しなさい。全てのプロフィール情報を代入し終わったら、それぞれのプロフィール情報を順番にメッセージダイアログに表示しなさい。

まとめ

今回は複数のデータを組み合わせた複合データをプログラムで利用するために、「クラス」と「インスタンス」について紹介しました。

クラスはプログラムの末尾に次のような形式で宣言します。

class <クラスの名前> {
    <フィールドの宣言>
}

クラスは複合データがどのようなデータの組合せであるかという「形式」を表すもので、組み合わせるデータはフィールドとして宣言します。

<フィールドの型> <フィールドの名前>;

クラスはあくまで形式であり、プログラムから使える具体的なデータではありません。複合データをプログラムから利用するには、クラスからその具体例である「インスタンス」を生成して使います。生成したインスタンスを変数に記憶させる場合には、クラスと同じ名前の型で変数を宣言します。

<クラスの名前> <変数の名前> = new <クラスの名前>();

クラスからインスタンスを生成したら、それぞれのフィールドに値を代入します。また、同じ形式でフィールドを参照することもできます。

<変数の名前>.<フィールドの名前>

生成したインスタンスを配列に記憶させることもできます。配列は整数(int)や実数(double)、文字列(String)などのほかに、クラスと同じ名前の型を要素の型の指定できます。クラスと同じ名前の型を指定した場合、その配列に記憶させられるのは同じクラスのインスタンスです。

<要素の型>[] <変数の名前> = new <要素の型>[<配列の大きさ>];

以上が今回のまとめです。クラスは抽象的で難しい考え方ですが、うまく使えば現実世界の問題をプログラムで違和感なく処理できるようになります。クラスを利用したプログラムをたくさん書いて、少しでも早く慣れるようにしましょう。