質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.60%

継承先クラスで継承元クラスのpublic finalフィールドを初期化したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,433

t-miyazaki

score 60

public class Base {
  public final int zzz;
  public Base() {
  }
  public Base(int a, int b, int c) {
    //長い処理
    }
}

public class Derived extends Base {

}

このような関係になっているときに、Baseクラスのpublic final intフィールドzzzをDerivedクラスで初期化することはできますでしょうか。
Base(int,int,int)コンストラクタは、Baseクラスをそのまま使うときに呼び出すものですのでDerivedから呼ぶことはできません。
DerivedクラスではBaseクラスの初期化処理だけを変更して、それ以外のメソッドはそのまま流用したいという使い方をしたいと思っています。
BaseクラスのBase()コンストラクタでzzzに何の値も入れないと怒られますし、Derivedクラスではzzzに値を入れることができないという状態です。
クラス構造を変更するしか方法がないのでしょうか?
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

0

できません。
( Derived  のコンストラクタで super( a, b, c) を呼ぶならできますが、呼べないというお話ですので)
そういうことができないということを保証してくれるのが final のありがたい機能です。

そもそも何のために zzz を final にしているのですか?
final というのはコンストラクタで一度きりしか値が設定されないようにしたいから指定するもので、
そういう記述をしたプログラマの意図に沿うためにわざわざ、エラーが出るようになっているのですから、
それを無理に変えることは、当初のあなたの(zzz を final にした)目的と矛盾しませんか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/06 21:44

    そうですね。
    本来ならzzzをprivateにしてgetterのみを定義することでzzzを参照できるようにすべきでしたが、めんどくさかったのでfinalフィールドにしたところ、Baseを拡張した別のクラスが必要となってDerivedを作成したのですが、zzzの値は初期化時に設定するものでしたので、今回のような問題が発生したということです。
    できないということで、そこのところを見直したいと思います。ご回答ありがとうございました。

    キャンセル

0

Base(int,int,int)コンストラクタは、Baseクラスをそのまま使うときに呼び出すものですのでDerivedから呼ぶことはできません。 

であればそもそも継承して使うという事自体が間違っているのでは?
機能拡張するからこその継承であって、それを禁止するのなら継承という手段を使うのは適切ではないという事になります。
Baseクラスでzzzを初期化しないのならそもそもzzzというfinalフィールドが存在するべきではありませんし、値を変更できるようにしたいなら最低でもzzzに代入する数値を受け取るコンストラクタが必要です。
もう一つのコンストラクタBase(int, int, int)もそのコンストラクタを経由するようにもできますし、zzzに代入する値を渡すためのコンストラクタは設計上絶対必要だと思いますよ。

public Base {
  public final int zzz;
  public Base() {
    zzz = 0;  //何か代入しなければいけない。もしくはこのコンストラクタ自体を削除する
  }

  //zzzを任意の値で初期化するためのコンストラクタ
  public Base(int z){
    zzz = z;
  }

  public Base(int a, int b, int c) {
    this(a);  //zzz初期化のコンストラクタ起動
    //代入する値が何らかの長い計算によって出す必要があるなら、
    //private staticなメソッドでも作ってそちらに計算を一任してこのaの部分に渡す
    //あるいはコンストラクタの形ではなく、ファクトリメソッドでこのパターンを用意
  }
}

public Derived extends Base {
  public Derived(int z){
    super(z);  //zzzを初期化するコンストラクタを呼び出す
    //その他処理
  }
}

追記
ファクトリメソッドでの書き方

class Car {

  // Carの性能を表す変数(読み取り専用)
  public final int spec;

  Car() {
    spec = 0;
  }

  // ファクトリメソッドで生成することを想定するためprivateにしたかったが、
  // 継承して使えなくなるためprotectedにする
  protected Car(int s) {
    spec = s;
  }

  //ファクトリメソッド
  public static Car createCar(int a, int b, int c) {
    //長い計算
    return new Car(計算結果);
  }

  // Carの多数のメソッド
}

class SuperCar extends Car {

  SuperCar(int s) {
    super(s);
  }

  //SuperCarのファクトリメソッド
  public static SuperCar createSuperCar(仮パラメータ) {
    //計算する
    return new SuperCar(計算結果);
  }

  // SuperCarの多数のメソッドは、Carと共通
  // だからSuperCarはCarを継承させた。
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/07 21:33 編集

    解決済みの質問に回答ありがとうございます。
    Base, Derivedというクラス名では分かりにくいため、当初の状況を次の架空のクラスを用いて説明します。
    ```Java
    class Car {
    // Carの性能を表す変数(読み取り専用)
    public final int spec;

    Car() {
    // dummy
    spec = 0;
    }

    // Carとして使うためのコンストラクタ
    Car(int a, int b, int c) {
    // 長い処理
    // 計算によりspecを算出して設定
    spec = 計算値;
    }

    // Carの多数のメソッド
    }

    class SuperCar extends Car {

    // SuperCarとして使うためのコンストラクタ
    SuperCar() {
    // ここでspecを計算するが、計算方法は
    // SuperCar独自の方法となる
    spec = 計算値;
    // ↑だがここで代入はできない
    }

    // SuperCarの多数のメソッドは、Carと共通
    // だからSuperCarはCarを継承させた。
    }
    ```
    はじめにCar(車)というクラスが有り、Carには性能を表す数値を格納するspecというフィールドがあります。これは読み取り専用にしたかったのでpublic finalフィールドにしました。specはCarのパラメータによって決まり、そのパラメータはCar(int,int,int)コンストラクタで渡されるため、その中でspecを計算して設定します。
    その後、SuperCarという別のクラスが必要になり、ほとんどのメソッドはCarと共通のためCarを継承させました。ただし、specの計算方法がCarとは異なるため、specの値をSuperCarのコンストラクタ内で設定できればと思いました。
    基底クラスにspecを初期化するためのコンストラクタを用意すればいい話でありますが、そのようなpublic finalフィールドがspec以外にも多数あるため、とりあえずダミーの値を設定する空コンストラクタCar(void)を呼んでおいて、後々でフィールドの値を設定したくなりました。そういった経緯で質問をした次第でございます。
    クラス設計が良くないことは承知しておりますが、リファクタリングは後回しにして、とりあえず動く実装を作りたいという状況であったため、何か方法があるかと思いました。
    方法としてはクラス設計を見なおせばいい話ではありますが、別の解決方法としては、リフレクションを使うことで無理やりpublic finalフィールドを書き換えることができました。

    キャンセル

  • 2016/02/07 21:49

    > そのようなpublic finalフィールドがspec以外にも多数あるため
    それならなおのことそれらのフィールドをprivateにしてアクセッサで取得・設定する仕組みにするべきです。リファクタリング云々ではなく、そうでないと動かすことすらままなりません。
    また、specに入れる値が異なる計算方法によって提供されるのなら、それこそファクトリメソッドにして設定したほうがいいのではないでしょうか?
    ファクトリメソッドでの書き方を後ほど回答編集で書きたいと思います。

    キャンセル

  • 2016/02/07 22:48

    そうですね。
    プログラム設計時にSuperCarが必要になることが想定できていれば、そのときにしかるべき配慮をしていたと思いますが、そうではなかったということです。今更public finalフィールドをgetterにするにしても、すでに多くのコードがこれを呼び出しており、多数のコードを書き換える必要が有るため大変です。また、これは個人的な考えなのですが、単なる書き方の問題ですが、アクセサを使って obj.getSpec() と書くよりもフィールドの参照で obj.spec と書きたいと思っています。この事についてはC#であればgetプロパティだけを設定することにより、このように書けるのですが、Javaにはプロパティは無いのでそのようには書けません。
    いずれにしても本スレッドの目的は、継承先でpublic finalフィールドを初期化できるか否か、ということを聞きたかったので、ファクトリメソッドの件については参考にさせていただきます。

    キャンセル

  • 2016/02/08 01:13

    それを書き換えるだけならエディターの置換機能を使えば済む話ではないでしょうか?
    幸いfinalなので、specを取得で使っていても設定には使っていないはずなので、"spec"をまるごと"getSpec()"などに全置換すれば解決すると思いますよ。
    書き方云々よりも、カプセル化の概念に慣れるためにアクセサを使ったほうがいいです。

    キャンセル

0

行儀の悪いコードであることは百も承知ですが、当初やりたかったことは
Javaのリフレクションを用いることにより実現できました。

import java.lang.reflect.Field;

class Base {
    public final int zzz;

    public Base() {
        // dummy
        zzz = 0;
    }

    public Base(int a, int b, int c) {
        // 長い処理
        zzz = 123;
    }
}

class Derived extends Base {

    public Derived() {
        try {
            Field f = getClass().getSuperclass().getDeclaredField("zzz");
            f.setAccessible(true);
            f.set(this, 345); // 新しい値を設定
        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        Derived der = new Derived();
        System.out.println(der.zzz);
    }

}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る