Java 多重继承

Posted

技术标签:

【中文标题】Java 多重继承【英文标题】:Java Multiple Inheritance 【发布时间】:2014-03-16 11:20:58 【问题描述】:

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

假设我有类Animal,它有子类BirdHorse,我需要创建一个从BirdHorse 扩展的类Horse,因为Pegasus 都是一只鸟还有一匹马。

我认为这是经典的钻石问题。据我所知,解决这个问题的经典方法是使AnimalBirdHorse 类接口并从它们实现Pegasus

我想知道是否有另一种方法可以解决我仍然可以为鸟和马创建对象的问题。如果有一种方法可以创造动物,那也很棒,但不是必需的。

【问题讨论】:

我认为您可以手动创建类并将它们存储为成员(组合而不是继承)。对于 Proxy (docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html) 类,这可能是一个选项,尽管您也需要接口。 @RAM 那么它不应该扩展 Bird 而是具有使其能够飞行的行为。 :D 问题解决了 没错。 CanFly 接口怎么样。 :-) 我认为这是错误的做法。你有动物——马,鸟。你有属性 - 飞行,食肉动物。飞马不是马鸟,而是会飞的马。属性应该在接口中。所以public class Pegasus extends Horse implements Flying. 我理解您为什么认为这是错误的并且不遵守生物学规则并感谢您的关注,但关于我需要建立的实际上与银行有关的程序,这是最好的接近我。由于我不想发布我的实际问题,因为这违反了规则,所以我稍微更改了示例。不过还是谢谢... 【参考方案1】:

您可以为动物类(生物学意义上的类)创建接口,例如用于马的public interface Equidae 和用于鸟类的public interface Avialae(我不是生物学家,所以这些术语可能是错误的)。

那么你仍然可以创建一个

public class Bird implements Avialae 

public class Horse implements Equidae 

还有

public class Pegasus implements Avialae, Equidae 

从 cmets 添加:

为了减少重复代码,您可以创建一个抽象类,其中包含您要实现的动物的大部分通用代码。

public abstract class AbstractHorse implements Equidae 

public class Horse extends AbstractHorse 

public class Pegasus extends AbstractHorse implements Avialae 

更新

我想再补充一个细节。作为Brian remarks,这是 OP 已经知道的。

但是,我想强调的是,我建议绕过接口的“多继承”问题,并且我不建议使用已经表示具体类型(例如 Bird)但更多是行为的接口(其他人指的是鸭子打字,这也很好,但我的意思是:鸟类的生物学类别,Avialae)。我也不建议使用以大写“I”开头的接口名称,例如IBird,它只是没有说明您需要接口的原因。这就是问题的不同之处:使用接口构建继承层次结构,在有用时使用抽象类,在需要时实现具体类,并在适当时使用委托。

【讨论】:

这正是 OP 所说的他们知道你可以在 Q 中做的事情。 由于 Pegasus 已经是一匹马(会飞),我认为如果扩展 Horse 并实现 Avialae,您可以重用更多代码。 我不确定,我还没有看到飞马。但是,在这种情况下,我更喜欢使用AbstractHorse,它也可以用来建造斑马或其他类似马的动物。 @MoritzPetersen 如果你真的想重用抽象并给出有意义的名称,可能AbstractEquidaeAbstractHorse 更合适。让斑马延伸一匹抽象的马会很奇怪。顺便说一句,很好的答案。 实现duck-typing也需要一个Duck类实现Avialae【参考方案2】:

从技术上讲,您一次只能扩展一个类并实现多个接口,但在接触软件工程时,我宁愿提出一个一般无法回答的问题特定解决方案。顺便说一句,这是一种很好的 OO 实践,扩展具体类/仅扩展抽象类以防止不需要的继承行为 - 没有“动物”之类的东西,也没有使用动物对象但只有具体的动物。

【讨论】:

【参考方案3】:

接口不模拟多重继承。 Java 的创建者认为多重继承是错误的,所以 Java 中没有这样的事情。

如果您想将两个类的功能合二为一 - 使用对象组合。即

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

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

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

class Main implements Interface1, Interface2

这样您就可以在上下文允许的情况下互换使用对象。

所以在我看来,你不能陷入钻石问题。

【讨论】:

它没有错,就像直接内存指针、无符号类型和运算符重载一样;只是没有必要完成工作。 Java 被设计成一种易于上手的精益语言。不要编造任何东西并希望最好,这是一个知识领域,而不是猜测。【参考方案4】:

嗯,您的类只能是其他 1 个的子类,但您仍然可以实现任意数量的接口,只要您愿意。

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

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

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
   

【讨论】:

【参考方案5】:

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

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 

【讨论】:

或者他可以:公共类 Pegasus 扩展 Animal Implements Horse , Bird OP 已经知道这个解决方案,他正在寻找替代方法 @Batman,他当然可以,但如果他想扩展层次结构,他需要遵循这种方法 IBirdIHorse 应该实现 IAnimal 而不是 Animal @Yogesh,你是对的。我忽略了他陈述的地方。作为一个“新手”,我现在该怎么办,删除答案,还是留在那里?,谢谢。【参考方案6】:

我有一个愚蠢的想法:

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

【讨论】:

它会起作用,但我不喜欢这种方法 (Wrappper),因为这样看起来 Pegasus 有一匹马,而不是一匹马。 谢谢。我忍不住发布了这个想法。我有点知道这很愚蠢,但我不明白为什么它很愚蠢...... 这几乎就像 Tim B 的回答中未改进的组合方法。 这或多或少是公认的方法,尽管你也应该有类似 IJumps 的东西,它带有由 Horse 和 Pegasus 实现的“jump”方法,以及带有“fly”方法的 IFlies由 Bird 和 Pegasus 实现。 @Pablo no,飞马有 horseFeatures 和 BirdFeatures。为答案 +1,因为它使代码保持简单,不像笨拙的、类生成的、正确的 Java 解决方案。【参考方案7】:

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

您很可能倾向于让 Pegasus 扩展 Bird 和 Horse 接口,但鸭子类型实际上表明您应该继承 行为。正如 cmets 中已经说明的,飞马不是鸟,但它可以飞。因此,您的 Pegasus 应该继承 Flyable-interface 并假设为 Gallopable-interface。

Strategy Pattern 中使用了这种概念。给定的示例实际上向您展示了鸭子如何继承 FlyBehaviourQuackBehaviour 并且仍然可以有鸭子,例如RubberDuck,它不能飞。他们也可以让Duck 扩展Bird 类,但是他们会放弃一些灵活性,因为每个Duck 都能飞,即使是可怜的RubberDuck

【讨论】:

【参考方案8】:

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

如果您希望能够在 Pegasus 类中使用 Horse 和 Bird 实现的方法和特性,那么您可以将 Pegasus 实现为 Bird 和 Horse 的 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);

【讨论】:

【参考方案9】:

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

第一个是继承。由于您已经确定了继承的限制,这意味着您无法在此处做您需要的事情。 第二个是作曲。由于继承失败,您需要使用组合。

它的工作方式是你有一个 Animal 对象。然后,您可以在该对象中添加更多对象,以提供所需的属性和行为。

例如:

Bird 扩展 Animal 实现 IFlier Horse 扩展 Animal 实现 IHerbivore, IQuadruped Pegasus 扩展 Animal 实现 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 应用任意数量的不同接口和复合支持类 - 对每个位的设置方式进行尽可能多的控制。

策略模式替代组合方法

根据您的工作方式和方式,另一种方法是让Animal 基类包含一个内部集合,以保存不同行为的列表。在这种情况下,您最终会使用更接近策略模式的东西。这确实在简化代码方面具有优势(例如,Horse 不需要了解有关 QuadrupedHerbivore 的任何信息),但如果您不使用接口方法,您将失去很多优势多态性等。

【讨论】:

一个类似的替代方案也可能是使用类型类,尽管在 Java 中使用这些类不是很自然(你必须使用转换器方法等),这个介绍可能对理解这个想法很有用: typeclassopedia.bitbucket.org 这是一个比已接受答案中推荐的方法更好的解决方案。例如,如果我以后想在 Bird 界面中添加“喙颜色”,我就有问题了。 Pegasus 是一个复合体,从马和鸟中获取元素,但既不是完全的马也不是鸟。在这种情况下使用组合非常有意义。 但是getFlier() 必须为每一种鸟重新实现。 @LifeH2O 将它们分成共享功能块,然后给它们。即你可能有MathsTeacherEnglishTeacher都继承TeacherChemicalEngineerMaterialsEngineer等继承EngineerTeacherEngineer 都实现了 Component。然后Person 就只有Components 的列表,您可以为Person 赋予他们正确的Components。即person.getComponent(Teacher.class)person.getComponent(MathsTeacher.class) 这是最好的答案。根据经验,继承代表“是”,接口代表“可以”。飞马是一种可以飞行和行走的动物。鸟是一种会飞的动物。马是一种会走路的动物。【参考方案10】:

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

有不同的策略来缓解这个问题。最直接可实现的一个是 Pavel 建议的 Composite 对象(本质上是 C++ 处理它的方式)。我不知道通过 C3 线性化(或类似方法)的多重继承是否会在 Java 的未来出现,但我对此表示怀疑。

如果您的问题是学术性的,那么正确的解决方案是鸟和马更具体,假设飞马只是鸟和马的组合是错误的。说飞马与鸟和马有某些共同的内在特性会更正确(也就是说,它们可能有共同的祖先)。正如 Moritz 的回答所指出的那样,这可以充分建模。

【讨论】:

【参考方案11】:

在 Java 8 及更高版本中,您可以使用default methods 来实现一种类似于 C++ 的多重继承。 您还可以查看this tutorial,其中显示了一些应该比官方文档更容易开始使用的示例。

【讨论】:

请注意,如果您的 Bird 和 Horse 都有默认方法,您仍然会遇到菱形问题,并且必须在您的 Pegasus 类中单独实现它(或得到编译器错误)。 @Mikkel Løkke:Pegasus 类必须为模棱两可的方法定义重写,但可以通过委托给任一超级方法(或按选定顺序委托两者)来实现它们。【参考方案12】:

正如您已经知道的那样,Java 中类的多重继承是不可能的,但使用接口却可以。您可能还想考虑使用组合设计模式。

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

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

【讨论】:

【参考方案13】:

把马放在有半扇门的马厩里是安全的,因为马不能越过半扇门。因此,我设置了一个马房服务,可以接受任何类型的马匹,并将其放在带半扇门的马厩中。

那么像马一样可以飞的动物吗?

我曾经对多重继承有很多想法,但是现在我已经编程超过 15 年了,我不再关心实现多重继承。

通常,当我尝试处理指向多重继承的设计时,我后来发布了我错过了理解问题域的版本。

If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction.

【讨论】:

如果我没看错你的类比,你是说一开始界面看起来很棒,因为它们可以让你解决设计问题,例如您可以通过强制您的类通过他们的接口来使用其他人的 API。但几年后,您意识到问题出在糟糕的设计上。【参考方案14】:

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

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

第 27 天

    接口主要用于开发用户定义的数据类型。 关于接口我们可以实现多重继承的概念。 通过接口我们可以实现多态、动态绑定的概念,因此我们可以提高 JAVA 程序的性能 内存空间和执行时间的变化。

接口是一个结构,它包含纯粹的集合 未定义的方法或接口是纯抽象的集合 方法。

[...]

第 28 天:

将接口的功能重用到类的语法 1:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>

    variable declaration;
    method definition or declaration;
;

在上面的语法中,clsname 代表类的名称,它是 从“n”个接口继承特性。 “实施”是 一个关键字,用于将接口的功能继承到 派生类。

[...]

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

【讨论】:

【参考方案15】:

为了降低复杂度和简化语言,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?  
    
 

【讨论】:

【参考方案16】:
    定义接口以定义功能。您可以为多种功能定义多个接口。这些功能可以通过特定的AnimalBird来实现。 使用继承通过共享非静态和非公开数据/方法来建立类之间的关系。 使用Decorator_pattern 动态添加功能。这将允许您减少继承类和组合的数量。

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

When to Use the Decorator Pattern?

【讨论】:

【参考方案17】:

问题没有解决。为了充分建模并防止代码复制,您需要多重继承或混合。具有默认函数的接口是不够的,因为您不能在接口中保存成员。 接口建模会导致子类或静态代码中的代码复制,这都是邪恶的。

您所能做的就是使用自定义构造并将其拆分为更多组件并将它们组合在一起......

玩具语言

【讨论】:

以上是关于Java 多重继承的主要内容,如果未能解决你的问题,请参考以下文章

Java提高篇——Java实现多重继承

多重继承,虚基类

C++的多重继承

Java复习笔记4--实现多重继承

Java中的多重继承

多继承 与 多重继承