web-dev-qa-db-ja.com

スーパータイプが期待されるサブクラスオブジェクトを引数として渡すことによるメソッドのオーバーライド

私はJavaを学んでいるだけで、実践的なプログラマーではありません。

私がフォローしている本では、メソッドをオーバーライドする場合、引数の型は同じでなければならないが、戻り値の型は多態的に互換性がある可能性があると述べています。

私の質問は、オーバーライドするメソッドに渡された引数がスーパータイプのサブクラス型にならないのはなぜですか?

オーバーロードされたメソッドでは、オブジェクトで呼び出すメソッドはすべて、オブジェクトで定義されていることが保証されています。


提案された重複に関するメモ:

最初の提案 は、クラス階層と機能を配置する場所についてのようです。私の質問は、言語制限が存在する理由にさらに焦点を当てています。

2番目の提案 は、私が求めていることを実行する方法を説明しますが、理由はそのように実行する必要があります。私の質問はその理由に焦点を当てています。

12
user1720897

質問で最初に参照する概念は 共変の戻り値の型 と呼ばれます。

メソッドが特定のタイプのオブジェクトを返すことになっていて、オーバーライドするメソッドが実際にそのサブクラスを返す可能性があるため、共変の戻り値のタイプが機能します。 Javaのような言語のサブタイプ規則に基づいて、STのサブタイプである場合、Tが出現する場所ならどこでもSを渡すことができます。

そのため、Sを期待するメソッドをオーバーライドするときにTを返すのが安全です。

メソッドのオーバーライドが、オーバーライドされたメソッドによって要求されたもののサブタイプである引数を使用することを受け入れるというあなたの提案は、型システムの不健全さにつながるため、はるかに複雑です。

一方では、上記と同じサブタイピングルールにより、たいていの場合、それはすでにあなたがやりたいことに対して機能しています。例えば

interface Hunter {
   public void hunt(Animal animal);
}

このクラスの実装があらゆる種類の動物を受け取ることを妨げるものは何もありません。そのため、このクラスはすでに質問の基準を満たしています。

しかし、あなたが提案したように、このメソッドをオーバーライドできるとしましょう:

class MammutHunter implements Hunter {
  @Override
  public void hunt(Mammut animal) {
  }
}

これが面白い部分ですが、今これを行うことができます:

AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh

AnimalHunterのパブリックインターフェイスに従って、任意の動物を狩ることができるはずですが、MammutHunterの実装に従って、Mammutオブジェクトのみを受け入れます。したがって、オーバーライドされたメソッドはパブリックインターフェイスを満たしていません。ここでは型システムの健全性を壊しました。

ジェネリックを使用して、必要なものを実装できます。

interface AnimalHunter<T extends Animal> {
   void hunt(T animal);
}

次に、MammutHunterを定義できます

class MammutHunter implements AnimalHunter<Mammut> {
   void hunt(Mammut m){
   }
}

また、一般的な共分散と反変を使用して、必要に応じてルールを緩和できます。たとえば、哺乳類のハンターが特定のコンテキストでのみネコを狩ることができることを確認できます。

AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());

MammalHunterAnimalHunter<Mammal>を実装するとします。

その場合、これは受け入れられません:

hunter.hunt(new Mammut()):

マンムットが哺乳類の場合でも、ここで使用している反変型の制限により、受け入れられません。したがって、タイプをある程度制御して、あなたが言及したようなことを行うことができます。

18
edalorzo

問題は、オブジェクトを引数として指定してオーバーライドメソッドで行うことではありません。問題は、メソッドを使用するコードがメソッドにフィードできる引数のタイプが何であるかです。

オーバーライドするメソッドは、オーバーライドされるメソッドの規約を満たさなければなりません。オーバーライドされたメソッドが一部のクラスの引数を受け入れ、サブクラスのみを受け入れるメソッドでオーバーライドした場合、コントラクトは満たされません。それが有効ではない理由です。

より具体的なタイプの引数でメソッドをオーバーライドすることはoverloadingと呼ばれます。同じ名前のメソッドを定義しますが、引数のタイプが異なります。元のメソッドの他に、オーバーロードされたメソッドを使用できます。呼び出されるメソッドは、コンパイル時にわかっているタイプによって異なります。メソッドをオーバーロードするには、@ Overrideアノテーションを削除する必要があります。

3
Florian F