Java多重継承


168

Javaの多重継承問題を解決する方法を完全に理解するために、私は明確にする必要のある古典的な質問をします。

私はクラスを持っているとしましょうAnimal、これはサブクラスを持っているBirdHorse、私はクラスにする必要があるPegasusから延びBirdかつHorse以来、Pegasus鳥と馬の両方です。

これは古典的なダイヤモンドの問題だと思います。これを解決する古典的な方法を私が理解できることからAnimalBirdHorseクラスをインターフェイスにしてPegasus、それらから実装することができます。

まだ鳥や馬のオブジェクトを作成できるという問題を解決する別の方法があるのか​​と思っていました。動物を作成できる方法があれば、それも素晴らしいことですが、必須ではありません。


6
手動でクラスを作成し、それらをメンバー(継承ではなく構成)として保存できると思います。Proxy(docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html)クラスでは、これもオプションになるかもしれませんが、インターフェースも必要です。
ガーボルBakos

4
@RAMでは、Birdを拡張するべきではなく、Birdが飛行できるようにする動作を備えています。:D問題の解決
Yogesh

11
丁度。CanFlyインターフェースについてはどうでしょう。:-)
驚いたココナッツ

28
これは間違ったアプローチだと思います。あなたは動物を持っています-馬、鳥。そして、あなたはプロパティを持っています-フライング、肉食動物。ペガサスは馬ではなく、飛ぶことができる馬です。プロパティはインターフェイスにある必要があります。ですからpublic class Pegasus extends Horse implements Flying
Boris the Spider 14

12
なぜあなたはそれが間違っていると考え、生物学の規則を守っていないのか理解し、懸念を認めますが、実際に銀行と関係がある私が構築する必要があるプログラムに関して、これは私にとって最良のアプローチでした。私は実際の問題を投稿したくなかったので、それはルールに反するため、例を少し変更しました。おかげで...
シェリー

回答:


115

public interface Equidae馬やpublic interface Avialae鳥などの動物のクラス(生物学的意味のクラス)のインターフェイスを作成できます(私は生物学者ではないので、用語が間違っている可能性があります)。

その後、まだ作成することができます

public class Bird implements Avialae {
}

そして

public class Horse implements Equidae {}

そしてまた

public class Pegasus implements Avialae, Equidae {}

コメントから追加:

重複するコードを減らすために、実装する動物の一般的なコードのほとんどを含む抽象クラスを作成できます。

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

更新

詳細をもう1つ追加します。ブライアンは発言、これはOPがすでに知っていたものです。

ただし、強調したいのは、インターフェイスでの「多重継承」問題を回避することをお勧めします。既に具象型(Birdなど)を表すインターフェイスを使用することはお勧めしませんが、動作(他の人はダックタイピングも良いですが、私は鳥の生物学的分類であるAvialaeを意味します。また、のように大文字の「I」で始まるインターフェース名を使用することはお勧めしませんIBird。それが質問との違いです。インターフェイスを使用して継承階層を構築し、有用な場合は抽象クラスを使用し、必要に応じて具象クラスを実装し、必要に応じて委任を使用します。


9
これは、OPがQであなたができることを彼らが知っていると正確に言っていることです
ブライアンローチ

4
ペガサスはすでに馬(飛ぶ)なので、馬を拡張してAvialaeを実装すれば、さらに多くのコードを再利用できると思います。
Pablo Lozano、2014

8
よくわかりませんが、ペガサスはまだ見ていません。ただし、その場合はAbstractHorse、シマウマや他の馬のような動物を作成するためにも使用できるを使用したいと思います。
Moritz Petersen、

5
@MoritzPetersen本当に抽象化を再利用し、意味のある名前を付けたい場合は、おそらくAbstractEquidaeより適切ですAbstractHorse。ゼブラに抽象的な馬を伸ばさせるのは奇妙なことです。ところで、いい答えです。
afsantos 2014

3
ダックタイピングの実装には、Duckクラス実装Avialaeも含まれますか?
mgarciaisaia 2014

88

オブジェクトを組み合わせるには、2つの基本的なアプローチがあります。

  • 最初は継承です。すでに継承の制限を特定しているので、ここで必要なことを実行できないことを意味します。
  • 2つ目は構成です。継承に失敗したため、コンポジションを使用する必要があります。

これが機能する方法は、動物オブジェクトがあるということです。そのオブジェクト内に、必要なプロパティと動作を提供するオブジェクトをさらに追加します。

例えば:

  • 動物を拡張するIFlier
  • 動物を拡張しますIHerbivore、IQuadrupedを実装します
  • ペガサスアニマルを拡張しますIHerbivore、IQuadruped、IFlier

これIFlierで次のようになります。

 interface IFlier {
     Flier getFlier();
 }

したがって、Bird次のようになります。

 class Bird extends Animal implements IFlier {
      Flier flier = new Flier();
      public Flier getFlier() { return flier; }
 }

これで、継承のすべての利点が得られます。コードを再利用できます。IFlierのコレクションを持つことができ、ポリモーフィズムのその他すべての利点を使用できます。

ただし、コンポジションからの柔軟性もすべて備えています。それぞれのタイプに、好きなだけ多くの異なるインターフェースと複合バッキングクラスを適用できますAnimal。各ビットの設定方法を必要なだけ制御できます。

作曲に対する戦略パターンの代替アプローチ

何をどのように行っているかに応じた代替アプローチは、Animal基本クラスに内部動作を含めて、さまざまな動作のリストを保持することです。その場合、最終的には戦略パターンに近いものを使用することになります。これは、コードを単純化するという点で利点をもたらします(たとえばHorseQuadrupedまたはについて何も知る必要がないHerbivore)が、インターフェイスアプローチも行わないと、多態性などの多くの利点が失われます。


同様の代替はまた、これらは、Java(あなたは、コンバータのメソッドを使用する必要がありますなど)で使用するのは自然ではないが、このイントロがアイデアを取得することが有用であるかもしれない、型クラスを使用している場合があります:typeclassopedia.bitbucket.orgを
ガーボルBakos

1
これは、受け入れられた回答で推奨されているアプローチよりもはるかに優れたソリューションです。たとえば、後で「くちばし色」をBirdインターフェースに追加する場合、問題が発生します。ペガサスは、馬や鳥から要素を取り、完全に馬や鳥ではない複合体です。このシナリオでは、コンポジションの使用は完全に理にかなっています。
JDB

しかしgetFlier()、あらゆる種類の鳥に対して再実装する必要があります。
SMUsamaShah 2014年

1
@ LifeH2Oそれらを共有機能のブロックに分割し、それを提供します。つまり、あなたが持っているかもしれないしMathsTeacherEnglishTeacher両方を継承TeacherしているChemicalEngineerMaterialsEngineerなど、継承しEngineerます。Teacherそして、Engineerの両方を実装ComponentPersonそしてちょうどのリストがあるComponentのを、あなたは彼らに右与えることができComponent、そのため秒Person。つまりperson.getComponent(Teacher.class)person.getComponent(MathsTeacher.class)など
Tim B

1
これが最良の答えです。経験則として、継承は「is」を表し、インターフェースは「can」を表します。ペガサスは飛んで歩くことができる動物です。鳥は飛べる動物です。馬は歩ける動物です。
Robear 2016年

43

私は愚かな考えを持っています:

public class Pegasus {
    private Horse horseFeatures; 
    private Bird birdFeatures; 

   public Pegasus(Horse horse, Bird bird) {
     this.horseFeatures = horse;
     this.birdFeatures = bird;
   }

  public void jump() {
    horseFeatures.jump();
  }

  public void fly() {
    birdFeatures.fly();
  }
}

24
それは機能しますが、ペガサスは馬を持っているように見えるので、そのようなアプローチ(ラッパー)は好きではありません。代わりに馬です。
Pablo Lozano、2014

ありがとうございました。私は自分でアイデアを投稿せざるを得なかった。私はそれが愚かであることを少し知っていますが、なぜそれが愚かであるのかわかりませんでした...
Pavel Janicek 14

3
それは、ティムBの回答からの作曲アプローチの未精製バージョンのようなものです。
ステファン14

1
これは多かれ少なかれ受け入れられた方法ですが、IJumpsのように、HorseとPegasusによって実装される「ジャンプ」メソッドや、BirdとPegasusによって実装される「フライ」メソッドを持つIFliesも必要です。
MatsT 2014

2
@パブロいいえ、ペガサスはhorseFeaturesとHAS birdFeaturesを持っています。答えを+1するのは、コードがシンプルに保たれるためです。
JaneGoodall 2015年

25

ダックタイピングのコンセプトを提案してもいいですか?

ほとんどの場合、PegasusにBird and Horseインターフェースを拡張させる傾向がありますが、アヒルのタイピングは実際には動作を継承する必要があることを示唆しています。コメントですでに述べたように、ペガサスは鳥ではありませんが、飛ぶことができます。したがって、あなたのペガサスはFlyable-インターフェイスを継承し、Gallopableです。

この種のコンセプトはStrategy Patternで利用されています。与えられた例では、実際にアヒルの継承は、方法を示しますFlyBehaviourQuackBehaviour、まだ、アヒルがあることができ例えばRubberDuck、飛ぶことができません。彼らはまたDuckBirdエクステンドをクラスにしたかもしれませDuckんが、貧しい人も含めてすべてが飛行できるため、ある程度の柔軟性を放棄したでしょうRubberDuck


19

技術的に言えば、一度に拡張できるクラスは1つだけで、複数のインターフェースを実装できますが、ソフトウェアエンジニアリングを実践する場合は、一般に答えられない問題固有のソリューションを提案します。ちなみに、不必要な継承動作を防ぐために具象クラスを拡張せず、抽象クラスだけを拡張するのは良いOOプラクティスです。「動物」などのようなものはなく、動物オブジェクトは使用されず、具象動物のみが使用されます。


13

2014年2月の時点でまだ開発段階にあるJava 8では、デフォルトのメソッドを使用して、一種のC ++のような多重継承を実現できます。また、公式ドキュメントよりも作業を開始しやすいいくつかの例を示すこのチュートリアルを参照することもできます。


1
ただし、BirdとHorseの両方にデフォルトのメソッドがある場合でも、ひし形の問題が発生し、Pegasusクラスで個別に実装する必要があります(またはコンパイラエラーが発生します)。
MikkelLøkke2014

@MikkelLøkke:Pegasusクラスは、あいまいなメソッドのオーバーライドを定義する必要がありますが、いずれかのスーパーメソッド(または選択した順序で両方)に委譲するだけでそれらを実装できます。
ホルガー

12

馬はハーフドアを乗り越えることができないため、ハーフドアのある厩舎に馬を置いておくことは安全です。そのため、私は馬のタイプのアイテムをすべて受け入れ、半ドアのある厩舎に置く馬小屋サービスをセットアップしました。

それでは、馬でさえ飛ぶことができる動物のような馬ですか?

以前は多重継承についてよく考えていましたが、15年以上プログラミングをしてきたので、多重継承の実装についてはもう気になりません。

多くの場合、多重継承を対象とした設計に対処しようとしたときに、問題のドメインを理解できなかったことを後でリリースしました。

または

アヒルのように見えたり、アヒルのようにガクガク鳴ったりしますが、バッテリーが必要な場合は、抽象化が間違っている可能性があります


私があなたの類推を正しく読んだ場合、最初はインターフェースが素晴らしいように見えます。これは、設計の問題を回避できるためです。たとえば、インターフェースを介してクラスを強制することにより、他の誰かのAPIを使用できます。しかし、数年後、問題は最初からデザインが悪いことに気付きました。
CS

8

Javaには多重継承がないため、多重継承の問題はありません。これは、実際の多重継承問題(ダイアモンド問題)を解決するための仕様です。

問題を軽減するためのさまざまな戦略があります。最もすぐに達成できるのは、Pavelが提案するCompositeオブジェクト(本質的にはC ++による処理方法)です。C3線形化(または類似のもの)による多重継承がJavaの将来のカードにあるかどうかはわかりませんが、疑問です。

あなたの質問が学問である場合、正しい解決策は鳥と馬がより具体的であり、ペガサスが単に鳥と馬を組み合わせたものであると仮定するのは誤りです。ペガサスには鳥や馬と共通の固有の特性がある(つまり、共通の祖先がある可能性がある)と言う方が正しいでしょう。これは、モリッツの回答が指摘するように十分にモデル化できます。


6

それはあなたのニーズとあなたの動物のクラスがあなたのコードでどのように使用されることに大きく依存すると思います。

Pegasusクラス内でHorseとBirdの実装のメソッドと機能を利用できるようにしたい場合は、PedagasをBirdとHorseの構成として実装できます。

public class Animals {

    public interface Animal{
        public int getNumberOfLegs();
        public boolean canFly();
        public boolean canBeRidden();
    }

    public interface Bird extends Animal{
        public void doSomeBirdThing();
    }
    public interface Horse extends Animal{
        public void doSomeHorseThing();
    }
    public interface Pegasus extends Bird,Horse{

    }

    public abstract class AnimalImpl implements Animal{
        private final int numberOfLegs;

        public AnimalImpl(int numberOfLegs) {
            super();
            this.numberOfLegs = numberOfLegs;
        }

        @Override
        public int getNumberOfLegs() {
            return numberOfLegs;
        }
    }

    public class BirdImpl extends AnimalImpl implements Bird{

        public BirdImpl() {
            super(2);
        }

        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return false;
        }

        @Override
        public void doSomeBirdThing() {
            System.out.println("doing some bird thing...");
        }

    }

    public class HorseImpl extends AnimalImpl implements Horse{

        public HorseImpl() {
            super(4);
        }

        @Override
        public boolean canFly() {
            return false;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }

        @Override
        public void doSomeHorseThing() {
            System.out.println("doing some horse thing...");
        }

    }

    public class PegasusImpl implements Pegasus{

        private final Horse horse = new HorseImpl();
        private final Bird bird = new BirdImpl();


        @Override
        public void doSomeBirdThing() {
            bird.doSomeBirdThing();
        }

        @Override
        public int getNumberOfLegs() {
            return horse.getNumberOfLegs();
        }

        @Override
        public void doSomeHorseThing() {
            horse.doSomeHorseThing();
        }


        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }
    }
}

別の可能性は、動物を定義するために継承の代わりにエンティティコンポーネントシステムアプローチを使用することです。もちろん、これは、動物の個々のJavaクラスがなく、コンポーネントによってのみ定義されることを意味します。

Entity-Component-Systemアプローチのいくつかの疑似コードは次のようになります。

public void createHorse(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 4);
    entity.setComponent(CAN_FLY, false);
    entity.setComponent(CAN_BE_RIDDEN, true);
    entity.setComponent(SOME_HORSE_FUNCTIONALITY, new HorseFunction());
}

public void createBird(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 2);
    entity.setComponent(CAN_FLY, true);
    entity.setComponent(CAN_BE_RIDDEN, false);
    entity.setComponent(SOME_BIRD_FUNCTIONALITY, new BirdFunction());
}

public void createPegasus(Entity entity){
    createHorse(entity);
    createBird(entity);
    entity.setComponent(CAN_BE_RIDDEN, true);
}

4

インターフェイス階層を作成し、選択したインターフェイスからクラスを拡張できます。

public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

次に、特定のインターフェースを拡張することにより、必要に応じてクラスを定義します。

public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}

1
それとも彼はちょうどすることができます:パブリッククラスペガサスは、動物を実装馬、鳥拡張
バットマン

OPはこのソリューションをすでに認識しており、彼はそれを行う別の方法を探しています
Yogesh '17

@バットマン、もちろん彼はカンムできますが、階層を拡張したい場合は、このアプローチに従う必要があります
リチャーズ

IBirdおよびIHorse実装する必要がありますIAnimal代わりにAnimal
oliholz

@ヨゲシュ、あなたは正しいです。私は彼がそれを述べている場所を見落としました。「初心者」として、私は今何をすべきか、答えを削除するか、そこに残しますか?、ありがとう。
リチャーズ

4

えーと、あなたのクラスは他の1つだけのサブクラスになることができますが、それでも望むだけ多くのインターフェースを実装することができます。

ペガサスは実際には馬(これは馬の特別なケースです)であり、飛行することができます(この特別な馬の「スキル」です)。一方で、ペガサスは鳥であり、歩くことができ、4足歩行です-それはすべて、あなたがコードを書くのがいかに簡単かによって異なります。

あなたの場合のようにあなたは言うことができます:

abstract class Animal {
   private Integer hp = 0; 
   public void eat() { 
      hp++; 
   }
}
interface AirCompatible { 
   public void fly(); 
}
class Bird extends Animal implements AirCompatible { 
   @Override
   public void fly() {  
       //Do something useful
   }
} 
class Horse extends Animal {
   @Override
   public void eat() { 
      hp+=2; 
   }

}
class Pegasus extends Horse implements AirCompatible {
   //now every time when your Pegasus eats, will receive +2 hp  
   @Override
   public void fly() {  
       //Do something useful
   }
}

3

インターフェイスは多重継承をシミュレートしません。Javaクリエーターは多重継承を間違っていると考えたため、Javaにはそのようなことはありません。

2つのクラスの機能を1つに統合する場合は、オブジェクト構成を使用します。すなわち

public class Main {
    private Component1 component1 = new Component1();    
    private Component2 component2 = new Component2();
}

また、特定のメソッドを公開する場合は、それらを定義して、対応するコントローラーへの呼び出しを委任できるようにします。

ここでインターフェースが便利になるかもしれません- Component1インターフェースInterface1Component2実装してを実装するInterface2場合、定義できます

class Main implements Interface1, Interface2

そのため、コンテキストで許可されている場所でオブジェクトを交換して使用できます。

だから私の視点では、あなたはダイヤモンドの問題に入ることができません。


直接メモリポインタ、符号なしの型、および演算子のオーバーロードが間違っていないのと同じように、それは間違っていません。仕事を完了する必要はありません。Javaは、簡単に取得できる無駄のない言語として設計されました。物事を作り上げて最高のものを期待しないでください。これは知識の分野であり、推測ではありません。
ギンビー2014


3
  1. 定義インタフェースを機能を定義します。複数の機能に対して複数のインターフェースを定義できます。これらの機能は、特定の動物またはによって実装できます。
  2. 静的および非公開のデータ/メソッドを共有することにより、継承を使用してクラス間の関係を確立します。
  3. Decorator_patternを使用して、機能を動的に追加します。これにより、継承クラスと組み合わせの数を減らすことができます。

より理解するために以下の例を見てください

デコレータパターンを使用する場合


2

複雑さを軽減し、言語を単純化するために、Javaでは多重継承はサポートされていません。

A、B、Cが3つのクラスであるシナリオを考えます。CクラスはAおよびBクラスを継承します。AクラスとBクラスに同じメソッドがあり、それを子クラスオブジェクトから呼び出す場合、AまたはBクラスのメソッドを呼び出すことはあいまいになります。

コンパイル時エラーはランタイムエラーよりも優れているため、2つのクラスを継承すると、Javaはコンパイル時エラーをレンダリングします。したがって、同じ方法でも異なる方法でも、コンパイル時エラーが発生します。

class A {  
    void msg() {
        System.out.println("From A");
    }  
}

class B {  
    void msg() {
        System.out.println("From B");
    }  
}

class C extends A,B { // suppose if this was possible
    public static void main(String[] args) {  
        C obj = new C();  
        obj.msg(); // which msg() method would be invoked?  
    }
} 

2

Javaでの複数継承の問題を解決するには→インターフェイスを使用

J2EE(コアJAVA)KVR氏による注釈Page 51

日-27

  1. インターフェイスは基本的に、ユーザー定義のデータ型を開発するために使用されます。
  2. インターフェースに関しては、多重継承の概念を実現できます。
  3. インターフェイスを使用すると、ポリモーフィズム、動的バインディングの概念を実現できるため、メモリ空間と実行時間の順番でJAVAプログラムのパフォーマンスを向上させることができます。

インターフェースは、純粋に未定義のメソッドのコレクションを含む構成体であるか、インターフェースは純粋に抽象的なメソッドのコレクションです。

[...]

日-28:

クラスへのインターフェースの機能を再利用するための構文1:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
    variable declaration;
    method definition or declaration;
};

上記の構文でclsnameは、「n」個のインターフェースから機能を継承しているクラスの名前を表します。'Implements'は、インターフェイスの機能を派生クラスに継承するために使用されるキーワードです。

[...]

構文2は、「n」個のインターフェースを別のインターフェースに継承します。

interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{     
    variable declaration cum initialization;
    method declaration;
};

[...]

構文3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
  variable declaration;
  method definition or declaration;
};
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.