いろいろガンガンいこうぜ。体も大事に

普段はJavaでAndroidアプリ開発しているプログラマーのブログです。

Javaのコンストラクターなどの初期化について (1)

Javaコンストラクターなどの初期化について整理してみようと思います。

  • title(String型)
  • message(String型)
  • description(String型)

という3つのString型のフィールドを持ち,コンストラクタでそれらを初期化するクラスを作ります。 下記は良くない実装です。

package com.mrstar.sample_initialize;

public class PrimaryConstructorSample {
    private final String title;
    private final String message;
    private final String description;

    public PrimaryConstructorSample(String title, String message, String description){
        this.title = title;
        this.message = message;
        this.description = description;
    }

    public PrimaryConstructorSample(String title, String message){
        this.title = title;
        this.message = message;
        this.description = "Default Description.";
    }

    public PrimaryConstructorSample(String title){
        this.title = title;
        this.message = "Default Message";
        this.description = "Default Description.";
    }
    // ~~ 以下省略 ~~ //
}

この実装は重複があります。 例えば,文字列型の引数を2つとるコンストラクタと3つとるコンストラクタ,それぞれ二カ所でdescriptionフィールドに"Default Description."という,文字列リテラルを代入しています。"Default Description."を違う文字列に変えることにしたとします。片方を変更したは良いがもう片方を忘れてしまようなことが起きてしまう可能性があります。

package com.mrstar.sample_initialize;

public class PrimaryConstructorSample {
    private final String title;
    private final String message;
    private final String description;

    public PrimaryConstructorSample(String title, String message, String description){
        this.title = title;
        this.message = message;
        this.description = description;
    }

    public PrimaryConstructorSample(String title, String message){
        this(title, message, "Default Description.");
    }

    public PrimaryConstructorSample(String title){
        this(title, "Default Message");
    }
    // ~~ 以下省略 ~~ //
}

引数が1つのコンストラクタと2つのコンストラクタは,引数を3つのコンストラクタに処理を委譲しています。 一番上の3つのString型をとるコンストラクタのように,他のコンストラクタからthisで呼ばれるコンストラクタをプライマリコンストラクタと呼ぶようです。プライマリコンストラクタではないコンストラクタでは,プライマリコンストラクタと違う部分のみを記述すれば良くなります。これによりコードの重複が消えました。

少しコンストラクタの引数に処理を加えて,フィールドを変えてみます。 下記はコンパイルエラーになります。

    // コンパイルエラー
    public PrimaryConstructorSample(String title, String message){
        String decoratedMessage = String.format("「%s」", message);
        this(title, decoratedMessage);
    }

コンストラクタ内でthis(またsuperの呼び出しも),コンストラクタの先頭でしか呼べないからです。

さて,先頭でしかthisを呼び出せないのでメソッド化してみます。 しかし,これもコンパイルエラーになってしまいます。

    // コンパイルエラー
    private String getDecoratedMessage(String message){
        return String.format("「%s」", message);
    }

    public PrimaryConstructorSample(String title, String message){
        this(title, getDecoratedMessage(message), "Sample description.");
    }

thisの呼び出し(superの呼び出し)の際には,インスタンスメソッドは呼び出せません。 インスタンスメソッドの呼び出しは,this.getDecoratedMessage(message)と等価ですが,コンストラクタ内のthisの呼び出しが終わるまで,thisのメソッドの呼び出し・フィールドの参照はできません。

    private static String getDecoratedMessage(String message){
        return String.format("「%s」", message);
    }

これでコンパイルエラーはとれます。