Javaによるプログラミング入門8
コンストラクタの引数

 こんにちは。最近、夜起きていられなくなりました。これまでは、夜更けるとますます元気が出てくる典型的夜型人間だったのに。年ですかね。おかげで、ただでさえ遅い更新が、どんどん遅くなってます。待っている方がいたら、すみません。^^;)
 今回は、

  クラス コップ
    フィールド   : 中の水(の量)
    コンストラクタ : はじめに水の量を設定する
    メソッド    : 水を出す

というクラスを例に考えていきたいと思います。あまりおもしろい例ではないかもしれませんが、クラスの練習のためと、あと少し、言い残したことを説明するためです。

 このようなクラスを作ろうとしたらどうすればよいでしょう。
 まず、フィールドですが、これは「 int nakami;」 とすればよいですね。「これから使う変数 nakami は整数である」という意味でした。
 コンストラクタは フィールドを初期化するのに使えるのでした。(「初期化」とは、「最初の値を格納する」などという意味でした。)
 「コップの中身は、当然、はじめ0だろう」と思う人が多いでしょうか。それでもよいのですが、今回は、はじめの水量を10ということにしておきましょう。コンストラクタではnakamiを10にすることにします。
 水をだす関数 dasu() はとりあえず、一回に2だけ出すことにしましょうか。これらをまとめると

class Glass //コップは英語でGlassかな
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ
    void dasu(){ nakami -= 2; }  //voidは「何も報告しない」程度の意味。あとで説明する予定です。
    //「nakami -= 2;」はnakamiを2減らせという意味でした。
}

ができます。
 ここで、コンストラクタは引数を取らないようにしました。上に書いたように、「はじめのコップの中の水量は、いつも10」ということにするなら、「水量を指示してもらう」など、外からデータを受け取る必要がないからです。
 引数を取らないコンストラクタをどのように使うかは、このクラスを使った簡単なプログラムを見ればわかると思います。そのためには、mainを持つクラスを書かなければいけません。それは、たとえば、次のようなものです。

//GlassSample.java
class Glass
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ
    void dasu(){ nakami -= 2; }  //voidは「何も報告しない」程度の意味。あとで説明する予定です。
    //「nakami -= 2;」はnakamiを2減らせという意味でした。
}

class GlassSample
{
    public static void main(String[] args){
        Glass glass = new Glass(); //Glassオブジェクトの生成。(以下の解説を参照してください。)
   
    //引数を取らないコンストラクタが呼び出され、中に10の水を入れられる。
        System.out.println("コップglassをつくりました。");
        System.out.println("glassから水を出します。");
        glass.dasu();
        System.out.println("終了");
    }
}

Fig.1 GlassSampleの実行画面

 あっさり書いてしまいましたが、

    Glass glass = new Glass();

と書けば、引数を取らないコンストラクタが呼び出されるわけです。右辺のnewの後がコンストラクタの形になっているので、そう思えば難しくないですよね。これで、「引数を取らないコンストラクタ」が呼び出され(つまり実行され)、「水量10のGlassオブジェクト」が生成されることになるのです。

 ただし、このクラスにはいくつか不満があります。(何の役に立つかといういつもの問題はおいといて、、、。)まず、コップの中身を表示していないので、中身が本当に減ったのかどうかもわかりません。
 そこで、dasuという関数内で、残りの水の量も表示することにします。

class Glass
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ

    void dasu(){
        nakami -= 2;
        System.out.println("水を出しました。");
        System.out.println("現在のコップの中身は" + nakami + "です。");
    }
}

 このクラスを使って、コップから水を3回出すプログラムを書いてみましょう。

//GlassSample2.java
class Glass
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ

    void dasu(){
        nakami -= 2;
        System.out.println( "水を出しました。");
        System.out.println( "現在のコップの中身は" + nakami + "です。");
    }
}

class GlassSample2
{
    public static void main(String[] args) {
        Glass glass = new Glass(); //Glassオブジェクトの生成。
   
    //引数を取らないコンストラクタが呼び出され、中に10の水を入れられる。
        System.out.println( "コップglassをつくりました。");
        System.out.println( "glassから水を3回出します。");
        glass.dasu();
        glass.dasu();
        glass.dasu();
        System.out.println("終了");
    }
}

ですね。

Fig.2 GlassSample2の実行画面

 これでコップの中の水の量もわかるプログラムになりました。何度も水を出すにはmain()の中の繰り返しを増やせばよいわけです。
 しかし、このように何度も水を出していると、最後には水がなくなるはずです。その場合はもう水は出せないはずですね。これもコードに書いてみます。それは、

class Glass
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ
    void dasu(){
        if(nakami >= 2){
            nakami -= 2;
            System.out.println( "水を出しました。");
            System.out.println("現在のコップの中身は" + nakami + "です。");
        }
        else{
            System.out.println("そんなに水がありません。");
            System.out.println("現在コップの中には" + nakami + "入っているだけです。");
        }
   
}
}

などすればよいでしょう。if(nakami >= 2)は「もしnakamiが2以上ならば」などと読み、elseは「そうでなければ」などと読み、それぞれ条件が正しい場合にはそのあとの中カッコの中が実行されるのでした。コップの中の水が2より小さくなれば、もうそんなに水を出せないので、nakamiが2以上かどうかをチェックするようにしたのです。元気のある人はこれを利用するプログラムを考えてみてください。


 ところで、前回までのクラスの作り方を真似すると、はじめにnakamiをプログラマかユーザが決められるようにできますね。これはコンストラクタに引数をつければよいのでした。ついでに、dasuの方もユーザが出す量を決められるように、引数をつけてみましょう。

class Glass
{
    private int nakami;
    Glass(int x){ nakami = x; }    //引数を取るコンストラクタ
    //ユーザかプログラマに渡される値(xで表される)をnakamiに格納

    void dasu(int x){
         if(nakami >= x){
             nakami -= x;
             System.out.println( "水を出しました。");
            System.out.println( "現在のコップの中身は" + nakami + "です。");
         }
         else{
             System.out.println( "そんなに水がありません。");
            System.out.println( "現在コップの中には" + nakami + "入っているだけです。");
        }
    }
}

これを使ったプログラム例は、もうつくれるでしょうか。一応書きます。例えば、

//GlassSample3.java
import java.io.*;
class Glass
{
    private int nakami;
    Glass(int x){ nakami = x; }    //引数を取るコンストラクタ
    //ユーザかプログラマに渡される値(xで表される)をnakamiに格納

    void dasu(int x){
         if(nakami >= x){
             nakami -= x;
             System.out.println( "水を出しました。");
            System.out.println( "現在のコップの中身は" + nakami + "です。");
         }
         else{
             System.out.println( "そんなに水がありません。");
            System.out.println( "現在コップの中には" + nakami + "入っているだけです。");
        }
    }
}

class GlassSample3
{
    public static void main(String[] args) throws IOException{
        BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));
        System.out.println( "コップを生成します。どれだけ水をいれるか入力してください。");
        String str;
        int x;
        str = br.readLine();            //ユーザから入力を文字列として受け取る
        x = Integer.parseInt(str);     //その文字列を整数に変換してxに格納

        Glass glass = new Glass(x); //引数を取るコンストラクタが呼び出され、
   
    //水がxだけ入ったGlassオブジェクトが生成され、それを変数glassが指す(参照する)。
        System.out.println("さあ、glassから水を出します。いくら出しますか。入力してください。");
        str = br.readLine();            //ユーザから入力を文字列として受け取る
        x = Integer.parseInt(str);     //その文字列を整数に変換してxに格納
        glass.dasu(x);
        System.out.println("終了");
    }
}

となるでしょう。

Fig.3 GlassSample3の実行例1

Fig.4 GlassSample3の実行例2


 これまで、どのクラスにも、コンストラクタ(クラスと同名のメンバ関数)を書いてきました。これを書かないとどうなるでしょう。
 実は、コンストラクタを書かないと、「引数を取らないコンストラクタ」が自動的に付けられることになっています。ただし、このコンストラクタは「(特別なことは)何もしないコンストラクタ」です。コンストラクタが特に何もしない場合、実は、フィールドは一定の規則にしたがって初期化されます。
 このようなフィールドの初期化は、デフォルト初期化などとよばれ、数値ならゼロに、参照型変数(オブジェクトを指す変数)なら、「オブジェクトを何も指していない」という意味のnullという値に初期化されることになっているのです。

 ところで、GlassSamle3.javaのGlassのように、「引数を取るコンストラクタ」を定義してしまうと、今度は、GlassSample.javaやGlassSampe2.javaで書いたような

Glass glass = new Glass();

というコードを(mainの中などに)書くと、コンパイル時にエラーになってしまいます。
 
このコードは、外からデータを受け取らないようにオブジェクトを生成しているのに、GlassSamle3.javaのGlassには「引数(つまり外からのデータ)を(受け)取るコンストラクタ」しかないからです。(コンストラクタを1つでも書けば、「引数を取らないコンストラクタ」が自動で生成されることはなくなるのです。)
 そこで、「引数を取るコンストラクタ」を「引数を取らないコンストラクタ」に書き直してしまえば、上のコードはエラーでなくなります。
 しかし、今度は、

Glass glass = new Glass(x);

のようなコードがコンパイル時にエラーになってしまいます。(ここでxはint型変数のつもりですが、もちろん、「20」など整数値そのものを入れても同じです。)これは、外からデータを受け取ってオブジェクトを生成しようとするコードなのに、「引数(つまり外からのデータ)を(受け)取るコンストラクタ」がなくなってしまったからです。
 これはGlassをどう使うかという問題になります。もし「Glass glass = new Glass();」のように使うなら、「引数を取らないコンストラクタ」を書いておけばよく、もし「Glass glass = new Glass(x);」のように使うなら、「引数を取るコンストラクタ」を書いておけばよいのです。
 しかし、もし、両方の使い方をしたければどうすればよいのでしょう。そのようなこともあります。
 そういうときは、「引数を取らないコンストラクタ」と「引数を取るコンストラクタ」の両方を書いておけばよいのです。実は、引数の異なるコンストラクタや同名のメソッドはいくつ書いてもよいのです。したがって、コンストラクタを2つ書いておけば、適当な方が呼び出されることになるのです。
 以下に簡単なサンプルをお見せします。サンプル内では、2つのGlassオブジェクトを、1つは引数を取らないコンストラクタで、1つは引数を取るコンストラクタで生成しています。

//GlassSample4.java
import java.io.*;
class Glass
{
    private int nakami;
    Glass(){ nakami = 10; }         //引数を取らないコンストラクタ
    Glass(int x){ nakami = x; }    //引数を取るコンストラクタ
    void dasu(int x){
         if(nakami >= x){
             nakami -= x;
             System.out.println( "水を出しました。");
            System.out.println( "現在のコップの中身は" + nakami + "です。");
         }
         else{
             System.out.println( "そんなに水がありません。");
            System.out.println( "現在コップの中には" + nakami + "入っているだけです。");
        }
    }
}

class GlassSample4
{
    public static void main(String[] args) throws IOException {
        BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));
        String str;
        int x;
        System.out.println("Glassオブジェクト(水量10)を作り変数glassで表します。");
        Glass glass = new Glass();    //引数を取るコンストラクタが呼び出され、
   
    //水が10だけ入ったGlassオブジェクトが生成され、それを変数glassが指す(参照する)。
        System.out.println( "さあ、glassから水を出します。いくら出しますか。入力してください。");
        str = br.readLine();             //ユーザから入力を文字列として受け取る
        x = Integer.parseInt(str);      //その文字列を整数に変換してxに格納
        glass.dasu(x);

        System.out.println("Glassオブジェクト(水量20)を作り変数glass2で表します。");
        Glass glass2 = new Glass(20); //引数を取るコンストラクタが呼び出され、
   
    //水が20だけ入ったGlassオブジェクトが生成され、それを変数glass2が指す(参照する)。
        System.out.println( "さあ、glass2から水を出します。いくら出しますか。入力してください。");
        str = br.readLine();              //ユーザから入力を文字列として受け取る
        x = Integer.parseInt(str);      //その文字列を整数に変換してxに格納
        glass2.dasu(x);
        System.out.println("終了");
    }
}

Fig.5 Glass4.exeの実行画面

 オブジェクト生成時に、2つ書いたコンストラクタのうち、どちらが呼び出されるかは、与える値の型や個数によって決められるのです。「Glass glass = new Glass();」の場合、「データを与えていない」のだから「データを取らない」つまり「引数を取らないコンストラクタ」が呼び出され、Glass glass2 = new Glass(20);」の場合は、「整数値を与えている」のだから「整数のデータを受け取る」つまり「整数(int)の引数を取るコンストラクタ」が呼び出されるわけです。
 コンストラクタを複数書くことは普通に行われます。ここでなじんでみてください。


目次のページ
前のページ 後のページ