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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

3282閲覧

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

t-miyazaki

総合スコア71

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2016/02/06 11:54

編集2016/02/07 12:34

Java

1public class Base { 2 public final int zzz; 3 public Base() { 4 } 5 public Base(int a, int b, int c) { 6 //長い処理 7 } 8} 9 10public class Derived extends Base { 11 12}

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

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); } }

投稿2016/02/07 12:42

t-miyazaki

総合スコア71

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

java

1public Base { 2 public final int zzz; 3 public Base() { 4 zzz = 0; //何か代入しなければいけない。もしくはこのコンストラクタ自体を削除する 5 } 6 7 //zzzを任意の値で初期化するためのコンストラクタ 8 public Base(int z){ 9 zzz = z; 10 } 11 12 public Base(int a, int b, int c) { 13 this(a); //zzz初期化のコンストラクタ起動 14 //代入する値が何らかの長い計算によって出す必要があるなら、 15 //private staticなメソッドでも作ってそちらに計算を一任してこのaの部分に渡す 16 //あるいはコンストラクタの形ではなく、ファクトリメソッドでこのパターンを用意 17 } 18} 19 20public Derived extends Base { 21 public Derived(int z){ 22 super(z); //zzzを初期化するコンストラクタを呼び出す 23 //その他処理 24 } 25}

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

java

1class Car { 2 3 // Carの性能を表す変数(読み取り専用) 4 public final int spec; 5 6 Car() { 7 spec = 0; 8 } 9 10 // ファクトリメソッドで生成することを想定するためprivateにしたかったが、 11 // 継承して使えなくなるためprotectedにする 12 protected Car(int s) { 13 spec = s; 14 } 15 16 //ファクトリメソッド 17 public static Car createCar(int a, int b, int c) { 18 //長い計算 19 return new Car(計算結果); 20 } 21 22 // Carの多数のメソッド 23} 24 25class SuperCar extends Car { 26 27 SuperCar(int s) { 28 super(s); 29 } 30 31 //SuperCarのファクトリメソッド 32 public static SuperCar createSuperCar(仮パラメータ) { 33 //計算する 34 return new SuperCar(計算結果); 35 } 36 37 // SuperCarの多数のメソッドは、Carと共通 38 // だからSuperCarはCarを継承させた。 39}

投稿2016/02/07 09:31

編集2016/02/07 13:01
swordone

総合スコア20649

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t-miyazaki

2016/02/07 12:37 編集

解決済みの質問に回答ありがとうございます。 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フィールドを書き換えることができました。
swordone

2016/02/07 12:49

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

2016/02/07 13:48

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

2016/02/07 16:13

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

0

ベストアンサー

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

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

投稿2016/02/06 12:23

kozuchi

総合スコア1193

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t-miyazaki

2016/02/06 12:44

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問