首页 文章

Java多重继承

提问于
浏览
161

为了完全理解如何解决Java的多重继承问题,我有一个经典的问题需要澄清 .

让我说我有类 Animal 这有子类 BirdHorse 我需要创建一个从 BirdHorse 延伸的类 Pegasus ,因为 Pegasus 既是鸟又是马 .

我认为这是经典的钻石问题 . 根据我的理解,解决这个问题的经典方法是制作 AnimalBirdHorse 类接口并从中实现 Pegasus .

我想知道是否有另一种方法来解决我仍然可以为鸟类和马创造物体的问题 . 如果有一种方法可以创造动物,那将是伟大的但不是必要的 .

16 回答

  • 112

    你可以为动物类(生物学意义上的类)创建接口,例如马的 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 {}
    

    更新

    我想补充一点细节 . 作为Brian remarks,这是OP已经知道的事情 .

    但是,我想强调一点,我建议绕过接口的"multi-inheritance"问题,并且我不建议使用以大写'I'开头的接口名称,例如 IBird ,它只是告诉你为什么需要一个接口 . 这就是问题的不同之处:使用接口构造继承层次结构,在有用时使用抽象类,在需要时实现具体类,并在适当时使用委托 .

  • 25

    将对象组合在一起有两种基本方法:

    • 第一个是 Inheritance . 正如您已经确定继承的局限性意味着您无法在此处执行所需操作 .

    • 第二个是 Composition . 由于继承失败,您需要使用组合 .

    这种方式的工作方式是你有一个Animal对象 . 然后在该对象中添加进一步的对象,这些对象提供您需要的属性和行为 .

    例如:

    • Bird extends Animal implements IFlier

    • Horse extends Animal implements IHerbivore, IQuadruped

    • Pegasus extends Animal implements IHerbivore, IQuadruped, IFlier

    现在 IFlier 看起来像这样:

    interface IFlier {
         Flier getFlier();
     }
    

    所以 Bird 看起来像这样:

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

    现在你拥有继承的所有优点 . 您可以重复使用代码 . 您可以拥有一组IFliers,并可以使用多态性等所有其他优点 .

    但是,您也拥有Composition的所有灵活性 . 您可以根据需要为每种类型的 Animal 应用尽可能多的不同接口和复合支持类 - 您需要对每个位的设置进行尽可能多的控制 .

    Strategy Pattern alternative approach to composition

    根据您正在做什么以及如何做的另一种方法是让 Animal 基类包含一个内部集合来保存不同行为的列表 . 在这种情况下,您最终会使用更接近战略模式的东西 . 这确实在简化代码方面具有优势(例如 Horse 不需要知道关于 QuadrupedHerbivore 的任何内容),但如果你不进行接口方法,则会失去多态性等许多优点 .

  • 3

    我有一个愚蠢的想法:

    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();
      }
    }
    
  • 12

    我可以建议Duck-typing的概念吗?

    很可能你倾向于让Pegasus延伸一个Bird and a Horse界面,但鸭子打字实际上暗示你应该宁愿继承行为 . 正如评论中已经说明的那样,飞马座不是一只鸟,但它可以飞翔 . 所以你的Pegasus应该继承 Flyable -interface并让我们说一个 Gallopable -interface .

    Strategy Pattern中使用了这种概念 . 给出的示例实际上向您展示了鸭子如何继承 FlyBehaviourQuackBehaviour 并且仍然可以有鸭子,例如 RubberDuck ,不能飞 . 他们本可以使 Duck 延伸 Bird -class然后他们会放弃一些灵活性,因为每个 Duck 都能够飞,甚至是穷人 RubberDuck .

  • 87

    从技术上讲,您一次只能扩展一个类并实现多个类接口,但在进行软件工程时,我宁愿建议一个问题特定的解决方案通常不负责任 . 顺便说一句,这是一个很好的OO实践,不扩展具体类/只扩展抽象类来防止不必要的继承行为 - 没有"animal"这样的东西,也没有使用动物对象而只使用具体的动物 .

  • 4

    将马放在有半门的马厩里是安全的,因为马不能越过半门 . 因此,我设置了马匹服务,接受任何类型的马类,并把它放在一个半门的马厩里 .

    那种像马一样的动物甚至可以飞马吗?

    我过去常常考虑多重继承,但是现在我已经编程了超过15年,我不再关心实现多重继承了 .

    通常情况下,当我试图应对一个指向多重继承的设计时,我后来发布了我错过了解问题域 .

    要么

    如果它看起来像一只鸭子,像鸭子一样嘎嘎叫,但它需要电池,你可能有错误的抽象 .

  • 8

    在Java 8中,自2014年2月起仍处于开发阶段,您可以使用default methods来实现一种类似C的多重继承 . 您还可以查看this tutorial,其中显示了一些比官方文档更容易开始使用的示例 .

  • 4

    Java没有多重继承问题,因为它没有多重继承 . 这是设计,以解决真正的多重继承问题(钻石问题) .

    有不同的策略可以缓解这个问题 . 最容易实现的是Pavel建议的Composite对象(基本上是C如何处理它) . 我不知道通过C3线性化(或类似)的多重继承是否适用于Java的未来,但我对此表示怀疑 .

    如果你的问题是学术性的,那么正确的解决方案是鸟和马更具体,假设飞马只是一只鸟和马的组合是错误的 . 说Pegasus具有与鸟类和马匹相同的某些内在特性(即它们可能具有共同的祖先)会更为正确 . Moritz的答案指出,这可以充分模仿 .

  • 3

    我认为这在很大程度上取决于您的需求,以及您的动物类如何在您的代码中使用 .

    如果您希望能够在Pegasus课程中使用您的马和鸟实施的方法和功能,那么您可以将Pegasus实施为鸟和马的composition

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

    另一种可能性是使用Entity-Component-System方法而不是继承来定义您的动物 . 当然,这意味着您不会拥有动物的单独Java类,而是仅由它们的组件定义 .

    实体 - 组件 - 系统方法的一些伪代码可能如下所示:

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

    您可以拥有一个接口层次结构,然后从选定的接口扩展您的类:

    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 {
    }
    
  • 18

    嗯,你的类可以只是另外一个的子类,但是,你可以根据需要实现尽可能多的接口 .

    Pegasus实际上是一匹马(它是一匹特殊的马),能够飞行(这是这匹特殊马的“技能”) . 另一方面,你可以说,Pegasus是一只可以行走的鸟,并且是四腿的 - 这完全取决于你如何更容易编写代码 .

    就像你的情况一样,你可以说:

    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
       }
    }
    
  • 2

    接口不模拟多重继承 . Java创建者认为多重继承是错误的,因此在Java中没有这样的东西 .

    如果要将两个类的功能合并为一个 - 使用对象组合 . 即

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

    如果要公开某些方法,请定义它们并让它们将调用委托给相应的控制器 .

    这里的接口可能会派上用场 - 如果 Component1 实现接口 Interface1Component2 实现 Interface2 ,你可以定义

    class Main implements Interface1, Interface2
    

    这样您就可以在上下文允许的情况下交替使用对象 .

    所以在我看来,你无法进入钻石问题 .

  • 2

    正如您已经意识到的那样,Java中的类的多重继承是不可能的,但是可以使用接口 . 您可能还想考虑使用组成设计模式 .

    几年前我写了一篇关于作文的非常全面的文章......

    https://codereview.stackexchange.com/questions/14542/multiple-inheritance-and-composition-with-java-and-c-updated

  • 2

    为了降低复杂性并简化语言,java中不支持多重继承 .

    考虑A,B和C是三个类的场景 . 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?  
        }
    }
    
  • 12

    为了解决Java中多重继承的问题→使用了接口

    J2EE (core JAVA) Notes By Mr. K.V.R Page 51

    Day - 27接口基本上用于开发用户定义的数据类型 . 关于接口,我们可以实现多重继承的概念 . 通过接口,我们可以实现多态,动态绑定的概念,因此我们可以在内存空间和执行时间的转换中提高JAVA程序的性能 . 接口是一个构造,它包含纯粹未定义的方法的集合,或者接口是纯抽象方法的集合 . [...] Day - 28:语法-1,用于将接口的功能重用于类:[abstract] class <clsname> implements <intf 1>,<intf 2> ......... <intf n>
    {
    变量声明;
    方法定义或声明;
    };
    在上面的语法中,clsname表示继承“n”个接口的特征的类的名称 . 'Implements'是一个关键字,用于继承派生类的接口功能 . [...]语法-2继承'n'个接口到另一个接口:interface <intf 0 name> extends <intf 1>,<intf 2> ......... <intf n>
    {
    变量声明和初始化;
    方法声明;
    };
    [...]语法-3:[abstract] class <派生类名> extends <基类名> implements <intf 1>,<intf 2> ......... <intf n>
    {
    变量声明;
    方法定义或声明;
    };

  • 41
    • 定义 interfaces 以定义功能 . 您可以为多个功能定义多个接口 . 这些功能可以通过特定的 AnimalBird 实现 .

    • 使用 inheritance 通过共享非静态和非公共数据/方法在类之间 Build 关系 .

    • 使用Decorator_pattern动态添加功能 . 这将允许您减少继承类和组合的数量 .

    请看下面的示例以便更好地理解

    When to Use the Decorator Pattern?

相关问题