怒Mは思いつきでモノを言う

やったことメモなどなど

Javaのコンストラクタの挙動について確認した。

この記事はJava Advent Calendar 2017の18日目です。

はじめに

Javaについて勉強を初めて数ヶ月が経ちました。

私の所属している会社では1年目の社員はOracle Certified Java Programmer, Silver SE 8 認定資格を取得します。

新人の頃に受験し合格しましたが、Sun Certified Programmer for the Java 2 Platform 1.4です。

私のところにも新人が配属されるようになり、試験勉強の面倒を見る機会が増えてきている中でちゃんと教えれる必要があるなーと感じ、先日Oracle Certified Java Programmer, Silver SE 8 認定資格を取得してみました。

合格はできましたが試験結果のレポートには間違った問題の傾向をレポートで記載されていたので、その中でコンストラクタの動作についての項目がありました。

今回、自分自身の復習の意味を込めてコンストラクタの仕様について確認したことを残します。

確認方法

コンストラクタの仕様の確認方法は「The Java® Language Specification Java SE 8 Edition」を参照しています。

日本語訳のドキュメントは無いんですね・・・英語成績2の身としてはコンストラクタの章を読むのにも一苦労で、Google翻訳と英和辞典が大活躍です。高校の時の英語教師にテスト返却時に「なめてんのか」と言われたのはトラウマいい思い出です。

その他、手元にある書籍「Java本格入門」「Javaエンジニア養成読本」とか参考になりそうな箇所を探しながら、Google検索しながらです。

Java SE 9 Edition の内容は確認していません。

コンストラクタ(Constructor)

コンストラクタとはクラスのオブジェクトを作成するときに実行される処理のことです。

コンストラクタ宣言

コンストラクタ宣言にはいくつかの決まりがあります。


  1. コンストラクタの名前はクラス名と同一であること
  2. コンストラクタ宣言では戻り値は定義しないこと
  3. コンストラクタはオーバーロードできる
  4. コンストラクタのアクセス修飾子はpublic,protected,private,修飾子なし(パッケージプライベート)を指定できる
  5. コンストラクタにはthrowsを定義できる
  6. コンストラクタは継承できない

abstract public class Meat {

    // 1. コンストラクタの名前はクラス名と同一であること
    // 2. コンストラクタ宣言では戻り値は定義しないこと
    public Meat() { 
        System.out.println("Constructor Meat()");
    }

    // 3. コンストラクタはオーバーロードできる
    public Meat(int num) {
        System.out.println("Constructor Meat(int num)");
    }

    // 4. コンストラクタのアクセス修飾子は指定できる
    // public, protected, private, 修飾子なし
    protected Meat(int num1, int num2) {
        System.out.println("Constructor Meat(int num1, int num2)");
    }

    // 5. コンストラクタにはthrowsを定義できる
    Meat (String s1, String s2) throws RuntimeException {
        System.out.println("Constructor Meat(String s1, String s2)");
    }

    // 6. コンストラクタは継承できない(抽象化できないということで良い?)
    // コンパイルエラー
    abstract Meat(String s);
}

とりあえず、こんな感じでしょうか。

コンストラクタを1つも作らなかった場合は、コンパイラが自動でデフォルトコンストラクタを追加してくれます。 デフォルトコンストラクタは引数なし・処理なしのコンストラクタです。

注意点としては、1つでもコンストラクタを明示的に作っている場合はデフォルトコンストラクタは作られないということです。

コンストラクタの実装

コンストラクタの実装に関してもルールがあります。 試験問題で間違えていたのは、この辺りだったかと思います。


  1. クラス内の別のコンストラクタを呼び出す場合はthis()を使う(引数を渡せます)
  2. 子クラスから親クラスのコンストラクタを呼び出す場合はsuper()を使う(引数を渡せます)
  3. コンストラクタ内で別のコンストラクタを明示的に呼び出す場合は必ず一番最初に呼び出す
  4. 子クラスのコンストラクタ内で親クラスのコンストラクタを明示的に呼び出さない場合はsuper()が自動的に呼び出される
  5. コンストラクタは再帰的に呼び出せない

// 親クラス
abstract public class Meat {

    public Meat() {
        System.out.println("Constructor Meat()");
    }
    
    public Meat(int num) {
        System.out.println("Constructor Meat(int num)");
    }
    
    protected Meat(int num1, int num2) {
        System.out.println("Constructor Meat(int num1, num2)");
    }
    abstract public String toString();
}

// 子クラス
public class Beef extends Meat {

    public Beef() {
        // 4. 子クラスのコンストラクタ内で親クラスのコンストラクタを
        // 明示的に呼び出さない場合はsuper()が自動的に呼び出される
        System.out.println("Constructor Beef()");
    }
    
    public Beef(int num) {
        // 1. クラス内の別のコンストラクタを呼び出す場合は`this()`を使う(引数を渡せます)
        this();  
        System.out.println("Constructor Beef(int num)");
    }
    
    public Beef(int num1, int num2) {
        // 2. 子クラスから親クラスのコンストラクタを呼び出す場合はsuper()を使う(引数を渡せます)
        // 3. コンストラクタ内で別のコンストラクタを明示的に呼び出す場合は必ず一番最初に呼び出す
        super(10); 
        System.out.println("Constructor Beef(int num1, int num2)");
    }

    // 5. コンストラクタは再帰的に呼び出せない
    // コンパイルエラーになる
    public Beef(String s) {
        this(s);
    }

ルール3とルール4についての理解が足りていない感じです。
コメントをいただきまして、理解できました!(2017/12/24追記)

    public Beef(){
        super();
        this(10);  // ここでコンパイルエラー
    }

これはルール3に基づいているので理解はできます。

    public Beef(int num) {
        this();  
        System.out.println("Constructor Beef(int num)");
    }

けど、上記のコンストラクタの場合は、this()の前にルール4に基づきsuper()が自動的に呼び出されます。
上記のコンストラクタの場合は、this()の前にsuper()が呼び出されているように見えますが、
Beef()super()が呼ばれているだけなので、Beef(int num)super()が呼ばれているわけではありません。

実際の動きを確認します。

    public static void main(String[] args) {
        System.out.println("beef1 -------->");
        Beef beef1 = new Beef();
        System.out.println("beef2 -------->");
        Beef beef2 = new Beef(10);
        System.out.println("beef3 -------->");
        Beef beef3 = new Beef(30, 40);
    }
MacBook-Pro:bin $ java constructor.sample.Beef
beef1 -------->
Constructor Meat()
Constructor Beef()
beef2 -------->
Constructor Meat()
Constructor Beef()
Constructor Beef(int num)
beef3 -------->
Constructor Meat(int num)
Constructor Beef(int num1, int num2)

必ず、親クラスのコンストラクタが呼び出され、その後に子クラスのコンストラクタが呼び出されていることがわかります。

最後に

しかし、なぜthis()super()を2つ明示的に呼び出せないのか、その辺の仕様が記載されている箇所まで「The Java® Language Specification Java SE 8 Edition」を読めていません。・・・ちゃんと書いてあることを信じて、もう少し読み進めます。 理解できました!

今回、かなり中途半端に終わってしまったので、この記事は今後メンテしていく予定です。