web-dev-qa-db-ja.com

Javaコンストラクターの継承

なぜJavaコンストラクターが継承されないのか疑問に思っていました。次のようなクラスがあるときは知っています。

public class Super {

  public Super(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    this.serviceA = serviceA;
    //etc
  } 

}

後でSuperから継承すると、Javaはデフォルトのコンストラクターが定義されていないことを訴えます。解決策は明らかに次のようなものです:

public class Son extends Super{

  public Son(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    super(serviceA,serviceB,serviceC);
  }

}

このコードは繰り返しであり、DRYではなく、役に立たない(IMHO)...ですので、再び質問があります。

なぜJavaはコンストラクターの継承をサポートしないのですか?この継承を許可しないことに利点はありますか?

176
Pablo Fernandez

コンストラクタwere inherited ...を仮定すると、すべてのクラスが最終的にObjectから派生するため、everyクラスはパラメータなしのコンストラクタになります。それは悪い考えです。正確に何を期待しますか:

FileInputStream stream = new FileInputStream();

する?

かなり一般的な「パススルー」コンストラクターを簡単に作成する方法が必要になる可能性がありますが、デフォルトではないはずです。サブクラスを構築するために必要なパラメーターは、多くの場合、スーパークラスで必要なパラメーターとは異なります。

194
Jon Skeet

スーパーから継承すると、実際にこれが起こります。

public class Son extends Super{

  // If you dont declare a constructor of any type, adefault one will appear.
  public Son(){
    // If you dont call any other constructor in the first line a call to super() will be placed instead.
    super();
  }

}

「スーパー」にはデフォルトのコンストラクターがないため、一意のコンストラクターを呼び出す必要があるため、これが理由です。

さて、Javaがコンストラクターの継承をサポートしていない理由を推測しようとしています。おそらく、コンストラクターは具体的なインスタンスについて話している場合にのみ意味があり、そうしないと何かのインスタンスを作成できないはずです(ポリモーフィズムによって)どのように定義されているかわかりません。

28

サブクラスオブジェクトの構築は、スーパークラスの構築方法とは異なる方法で行われる可能性があるためです。サブクラスのクライアントがスーパークラスで利用可能な特定のコンストラクターを呼び出せるようにしたくない場合があります。

愚かな例:

class Super {
    protected final Number value;
    public Super(Number value){
        this.value = value;
    }
}

class Sub {
    public Sub(){ super(Integer.valueOf(0)); }
    void doSomeStuff(){
        // We know this.value is an Integer, so it's safe to cast.
        doSomethingWithAnInteger((Integer)this.value);
    }
}

// Client code:
Sub s = new Sub(Long.valueOf(666L)): // Devilish invocation of Super constructor!
s.doSomeStuff(); // throws ClassCastException

またはさらに簡単:

class Super {
    private final String msg;
    Super(String msg){
        if (msg == null) throw new NullPointerException();
        this.msg = msg;
    }
}
class Sub {
    private final String detail;
    Sub(String msg, String detail){
        super(msg);
        if (detail == null) throw new NullPointerException();
        this.detail = detail;
    }
    void print(){
        // detail is never null, so this method won't fail
        System.out.println(detail.concat(": ").concat(msg));
    }
}
// Client code:
Sub s = new Sub("message"); // Calling Super constructor - detail is never initialized!
s.print(); // throws NullPointerException

この例から、「これらのコンストラクターを継承したい」または「これらのコンストラクターを除くすべてのコンストラクターを継承したい」と宣言する何らかの方法が必要であり、デフォルトのコンストラクター継承も指定する必要があることがわかります。誰かがスーパークラスに新しいコンストラクターを追加する場合の設定...または「継承」したい場合はスーパークラスからコンストラクターを繰り返す必要があります。これは間違いなくそれを行うより明白な方法です。

11
gustafc

コンストラクターは実装の詳細であるため、インターフェース/スーパークラスのユーザーが実際に呼び出すことができるものではありません。インスタンスを取得するまでに、インスタンスはすでに構築されています。逆もまた同様です。オブジェクトを作成する時点では、定義により、現在割り当てられている変数はありません。

すべてのサブクラスに継承されたコンストラクターを強制することの意味を考えてください。私は、クラスが親であるという理由だけで、特定の数の引数を持つコンストラクタを「魔法のように」持つよりも、変数を直接渡す方が明確だと主張します。

5
Andrzej Doyle

デビッドの答えは正しいです。私は、あなたのデザインが台無しになっているという神からのサインを受け取っているかもしれず、「息子」は「スーパー」のサブクラスであってはならないが、代わりに、スーパーは実装の詳細が最もよく表現されていることを付け加えたいby having Sonが提供する機能を、一種の戦略として。

編集:ジョン・スキートの答えは最高です。

2

コンストラクターはポリモーフィックではありません。
すでに構築されたクラスを扱う場合、オブジェクトの宣言されたタイプ、またはそのサブクラスのいずれかを扱うことができます。それが継承が有用な理由です。
コンストラクターは常に特定のタイプ、たとえばnew String()で呼び出されます。仮説的なサブクラスには、これに関する役割はありません。

2
Rik

(スーパー)クラスは、その構築方法を完全に制御する必要があるためです。プログラマがクラスのコントラクトの一部としてデフォルト(引数なし)コンストラクタを提供する意味がないと判断した場合、コンパイラはコンストラクタを提供すべきではありません。

0
David R Tribble

基本的には、必要に応じて単にsuperを呼び出すことができるという意味で、コンストラクターを継承します。デフォルトで発生した場合、他の人が述べた理由でエラーが発生しやすいというだけです。コンパイラは、適切な場合と適切でない場合を推測できません。

コンパイラーの仕事は、複雑さと意図しない副作用のリスクを減らしながら、可能な限りの柔軟性を提供することです。

0
clearlight