ここまでのサンプルプログラムでは、既存の Pointなどのクラスを利用してきました。ここからは、自ら新しいクラスを作成していきます。この章で扱うプログラムのおおよその仕様を以下に定めます。
名前を実装するのは少し後にすることとして、まずは犬クラスと猫クラス、そしてそれらを動作確認するためのクラスを作成します。クラスは、基本的には 1つのクラスを 1ファイルずつで記述しますので、今回は 3つのファイルを作成します。
ソースコードは以下の通り。
K101/Cat.java
/** * 猫を表すクラスです。 */ public class Cat { /** * 鳴きます。 */ public void cry() { System.out.println("名無しの猫「ニャー」"); } }
K101/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 鳴きます。 */ public void cry() { System.out.println("名無しの犬「ワン」"); } /** * お座りします。 */ public void sitDown() { System.out.println("名無しの犬は座りました。"); } }
K101/K101.java
/** * Dogクラスと Catクラスを利用します。 */ public class K101 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog(); Dog dog2 = new Dog(); Cat cat1 = new Cat(); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // 猫のインスタンスメソッドを実行 cat1.cry(); } }
実行結果は以下の通り。
名無しの犬「ワン」 名無しの犬は座りました。 名無しの犬「ワン」 名無しの猫「ニャー」
まず、数を数えられることが大切です。Catクラスと Dogクラスについて、以下の数を正確に数えられるでしょうか。
その他に以下のことが理解できるでしょうか。
この後、デフォルトコンストラクタについて説明し、続いてこの Dogクラスと Catクラスを内容を充実させていきます。
ところで、「こんな簡単なプログラムをなんでこんな複雑に記述しているの?」と思うかもしれません。回答としては、これは実行結果が重要なのではない。オブジェクト指向を学ぶための材料に過ぎない、ということを理解していただければと思います。
Catクラスにも Dogクラスにもコンストラクタの記述は特段ありませんでした。Javaでは、コンストラクタがひとつも記述されていない場合、デフォルトコンストラクタが自動的に補完されます。具体的には、以下のようなコードが埋め込まれたことと同じになります。
public クラス名() { ; // 何もしない }
今の時点では「何のこと?」と思われるかもしれませんが、もうすぐ後ろで、複数のコンストラクタを記述していきます。コンストラクタをまったく記述しなかった場合にのみ、このデフォルトコンストラクタが補完されることを覚えておいてください。逆に言えば、コンストラクタを自分で記述した場合には、デフォルトコンストラクタの補完は行われません。
新しくプロジェクト K102を作成し、いろいろ実験していきます。ここでは、インスタンスフィールドを使用することにしました。
ソースコードは以下の通り。
K102/Cat.java
/** * 猫を表すクラスです。 */ public class Cat { /** * 名前。 */ public String name = "名無しの猫"; /** * 鳴く回数。 */ public int cryCount = 1; /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ニャー"); } System.out.println("」"); } }
K102/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ public String name = "名無しの犬"; /** * 鳴く回数。 */ public int cryCount = 1; /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K102/K102.java
/** * Dogクラスと Catクラスを利用します。 */ public class K102 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog(); dog1.name = "ポチ"; dog1.cryCount = 2; Dog dog2 = new Dog(); dog2.name = "クロ"; dog2.cryCount = 5; Cat cat1 = new Cat(); cat1.cryCount = 3; // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // 猫のインスタンスメソッドを実行 cat1.cry(); } }
実行結果は以下の通り。
ポチ「ワンワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 名無しの猫「ニャーニャーニャー」
まず、数を数えられることが大切です。Catクラスと Dogクラスについて、以下の数を正確に数えられるでしょうか。
インスタンスフィールドは、自身のクラスのインスタンスフィールドをthis.~
の形で利用できていることが分かるでしょうか。このthis
は予約語になります。なお、this.~
の記述は必須ではなく、省略できる場合もありますが、本ウェブサイトでは一貫して省略をせず、すべて記述することとします。これはインスタンスフィールドを、staticなフィールドや引数、ローカル変数と混同し、混乱することを防ぐためです。一般的にはしばしば省略されます。今回はインスタンスフィールドを利用しましたが、同様の記述によってインスタンスメソッドを呼び出すこともできます(後述の K105参照)。
利用する側(mainメソッド)では、Pointクラスの x, yのように nameや cryCountを利用できていることが理解できるでしょうか。
Pointクラスの x, yもそうだったのですが、先のプログラムでは、クラスを利用する側(mainメソッド)がインスタンスフィールドに直接アクセスできました。直接アクセスできると、大規模なプログラムを作る際にはいろいろと問題が生じる可能性があり、セッター/ゲッターと呼ばれるインスタンスメソッドを介してアクセスできるようにすることが望ましいとされています。セッター/ゲッターと呼ばれるインスタンスメソッドを作成し、インスタンスフィールドを privateにすることをカプセル化と呼びます。
セッター/ゲッターを用いると、便利になる例については後の「例外」で解説します。また、private予約語についての詳細については、後の「アクセス修飾子」のところで解説します。
カプセル化の基本は以下の通りです。
今回は、Catクラスと Dogクラスのインスタンスフィールド nameと cryCounterをそれぞれカプセル化しようと思います。今回はソースコードを短くするために、セッターだけ作成します(ゲッターは作成していません)。
ソースコードは以下の通り。
K103/Cat.java
/** * 猫を表すクラスです。 */ public class Cat { /** * 名前。 */ private String name = "名無しの猫"; /** * 鳴く回数。 */ private int cryCount = 1; /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ニャー"); } System.out.println("」"); } }
K103/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name = "名無しの犬"; /** * 鳴く回数。 */ private int cryCount = 1; /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K103/K103.java
/** * Dogクラスと Catクラスを利用します。 */ public class K103 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog(); dog1.setName("ポチ"); dog1.setCryCount(2); Dog dog2 = new Dog(); dog2.setName("クロ"); dog2.setCryCount(5); Cat cat1 = new Cat(); cat1.setCryCount(3); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // 猫のインスタンスメソッドを実行 cat1.cry(); } }
実行結果は以下の通り。
ポチ「ワンワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 名無しの猫「ニャーニャーニャー」
まず、数を数えられることが大切です。Catクラスと Dogクラスについて、以下の数を正確に数えられるでしょうか。
Catクラスと Dogクラスともに setNameメソッド、setCryCountメソッドというセッターを用意し、またインスタンスフィールドのアクセス修飾子を privateにしたことで、セッターと呼ばれるインスタンスメソッドを介して値を設定していることが分かります。なお、カプセル化を実現してしまったので、メインメソッド内に以下の記述をするとビルドエラーになります。
dog1.name = "ポチ"; // ビルドエラー
Pointクラスには 3種類のコンストラクタがありました。先のプログラムでは、まだコンストラクタがデフォルトコンストラクタの 1種類しかありませんでしたので、これを増やしてみます。なお、コンストラクタを自作するにあたって、野良猫は存在するので「名無しの猫」は認めるとしても、野良犬は今の日本にはほとんど存在しないので「名無しの犬」は認めないことにします。
ソースコードは以下の通り。
K104/Cat.java
/** * 猫を表すクラスです。 */ public class Cat { /** * 名前。 */ private String name = "名無しの猫"; /** * 鳴く回数。 */ private int cryCount = 1; /** * コンストラクタ。 */ public Cat() { ; } /** * コンストラクタ。 * @param name 名前 */ public Cat(String name) { this.name = name; } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Cat(String name, int cryCount) { this.name = name; this.cryCount = cryCount; } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ニャー"); } System.out.println("」"); } }
K104/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name = "名無しの犬"; /** * 鳴く回数。 */ private int cryCount = 1; /** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { this.name = name; } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.name = name; this.cryCount = cryCount; } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K104/K104.java
/** * Dogクラスと Catクラスを利用します。 */ public class K104 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); Cat cat1 = new Cat(); Cat cat2 = new Cat("タマ"); Cat cat3 = new Cat("ミケ", 4); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // 猫のインスタンスメソッドを実行 cat1.cry(); cat2.cry(); cat3.cry(); } }
実行結果は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 名無しの猫「ニャー」 タマ「ニャー」 ミケ「ニャーニャーニャーニャー」
まず、数を数えられることが大切です。Catクラスと Dogクラスについて、以下の数を正確に数えられるでしょうか。
コンストラクタを準備したことで、インスタンスを利用する側(メインメソッド)はシンプルに記述ができるようになりました。もちろん、これまでのセッター(インスタンスメソッド)を利用することも可能です。
コンストラクタに関して、とても重要なことは、コンストラクタを自分で記述したことによって、デフォルトコンストラクタは生成されないということです。デフォルトコンストラクタは、コンストラクタを一切記述しなかった場合のみ補完されるものであって、今回のようにコンストラクタを記述した場合には補完されません。したがって、Dogクラスには、「引数なしのコンストラクタは無い」ということになります。言い換えれば、以下の記述をするとビルドエラーになります。
dog1 = new Dog(); // ビルドエラー
「名無しの犬」はもう存在できないのです。
代入文がコンストラクタとインスタンスメソッドのあちこちに散在していることは、風通し的にあまり宜しくないということで、少し改善してみます。nameへの代入文(正確にはインスタンスの参照の変更)および cryCounterへの代入文は、それぞれセッターのメソッドに集約することにしました。コンストラクタでは、this.~
を用いてインスタンスメソッドを呼び出しています。また、コンストラクタから他のコンストラクタへthis(~)
の呼び出しをすることによって、記述をシンプルにさせています。
ソースコードは以下の通り。
K105/Cat.java
/** * 猫を表すクラスです。 */ public class Cat { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ。 */ public Cat() { this("名無しの猫", 1); } /** * コンストラクタ。 * @param name 名前 */ public Cat(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Cat(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ニャー"); } System.out.println("」"); } }
K105/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K105/K105.java
/** * Dogクラスと Catクラスを利用します。 */ public class K105 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); Cat cat1 = new Cat(); Cat cat2 = new Cat("タマ"); Cat cat3 = new Cat("ミケ", 4); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // 猫のインスタンスメソッドを実行 cat1.cry(); cat2.cry(); cat3.cry(); } }
実行結果は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 名無しの猫「ニャー」 タマ「ニャー」 ミケ「ニャーニャーニャーニャー」
実行結果そのものは K104と違いありません。ただ、CatクラスとDogクラスのソースコードの風通しを良くしただけです。
コンストラクタからインスタンスメソッドを呼び出しています。this.~
のことで、具体的には Catクラスの 35行目と 36行目、Dogクラスの 28行目と 29行目がそれにあたります。
コンストラクタの中で別のコンストラクタを呼び出すコードは、this(~)
です。この記述は、コンストラクタの 1行目にしか記述することができません。例えば、以下のように 1行目以外に記述しようとするとビルドエラーになります。
/** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { (何らかの処理); this(name, 1); // ビルドエラー }