Java多重继承


168

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

可以说我有类Animal此有子类BirdHorse我需要做一个类Pegasus,从扩展BirdHorsePegasus既是一只鸟和一匹马。

我认为这是经典的钻石问题。从我能理解经典的方式来解决,这是使AnimalBirdHorse类接口,并实现Pegasus从他们。

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


6
我认为您可以手动创建类并将其存储为成员(使用组合而不是继承)。使用Proxy(docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html)类,这可能是一个选择,尽管您也将需要接口。
的Gabor Bakos的

4
@RAM则它不应扩展Bird而是具有使其能够飞行的行为。:D问题已解决
Yogesh 2014年

11
究竟。一个接口CanFly怎么样。:-)
惊讶的椰子

28
我认为这是错误的做法。你有动物-马,鸟。而且您拥有属性-飞行,食肉兽。飞马不是马鸟,它是会飞的马。该属性应在接口中。这样public class Pegasus extends Horse implements Flying
蜘蛛鲍里斯(Boris the Spider)

12
我理解为什么您认为这是错误的,并且不遵循生物学规则,并且感谢您的关注,但是对于我需要构建的程序(实际上与银行有关),这对我来说是最好的方法。因为我不想发布我的实际问题,因为这违反了规则,所以我稍微修改了示例。不过谢谢...
Sheli 2014年

Answers:


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

更新资料

我想再添加一个细节。正如Brian所言,这是OP已经知道的。

但是,我想强调的是,我建议使用接口绕过“多继承”问题,并且不建议使用已经表示具体类型(例如Bird)但更多行为的接口(其他参考)鸭子打字,这也很好,但我的意思是:鸟类的生物学类别,Aviaalae)。我也不建议使用以大写的“ I”开头的接口名称,例如IBird,它仅告诉您为什么需要接口。这就是问题的区别:使用接口构造继承层次结构,在有用时使用抽象类,在需要的地方实现具体的类,并在适当时使用委托。


9
这......也正是OP说,他们所知道的,你可以在问:
布赖恩·罗奇

4
由于Pegasus已经是一匹骏马了,我认为,如果扩展Horse并实现Avialae,您可以重用更多代码。
Pablo Lozano 2014年

8
我不确定,我还没看过飞马。但是,在那种情况下,我更喜欢使用AbstractHorse,也可以用来构建斑马或其他类似马的动物。
莫里兹·彼得森

5
@MoritzPetersen如果您真的想重用抽象并给出有意义的名称,可能AbstractEquidae比更加合适AbstractHorse。有一匹斑马扩展一匹抽象的马会很奇怪。好的答案,顺便说一句。
afsantos

3
实现鸭类打字也会涉及到一个Duck类的实现Avialae吗?
mgarciaisaia 2014年

88

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

  • 首先是继承。正如您已经确定的那样,继承的局限性意味着您无法在此处执行所需的操作。
  • 第二个是成分。由于继承失败,因此您需要使用组合。

它的工作方式是您有一个Animal对象。然后,在该对象内添加其他对象,这些对象将提供所需的属性和行为。

例如:

  • 伸出动物的工具,如果
  • 延伸动物工具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的集合,并可以使用多态性的所有其他优势,等等。

但是,您还可以享受到Composition的所有灵活性。您可以根据需要将任意数量的不同接口和复合支持类应用于每种类型的Animal并且可以根据需要控制每个位的设置方式。

战略模式替代组合方法

根据您的操作方式和方式,另一种方法是使Animal基类包含一个内部集合,以保留不同行为的列表。在那种情况下,您最终会使用更接近策略模式的内容。这样做确实在简化代码方面具有优势(例如Horse,您无需了解QuadrupedHerbivore),但是如果您也不执行接口方法,则会失去很多多态性的优势,等等。


类似的替代方案也可能是使用类型类,但这些都不是那么自然在Java中使用(你必须使用转换器等方法),这个介绍可能是有用的想法:typeclassopedia.bitbucket.org
的Gabor Bakos的

1
与公认的答案中推荐的方法相比,这是一个更好的解决方案。例如,如果以后我想在Bird界面中添加“喙颜色”,那我就有问题了。飞马是一种合成物,从马和鸟中汲取元素,但既不是马也不是鸟。在这种情况下,使用合成非常合理。
JDB仍记得Monica

但是getFlier()必须为每种鸟类重新实现。
SMUsamaShah 2014年

1
@ LifeH2O将它们拆分为共享功能的块,然后提供它们。例如,你可能会MathsTeacherEnglishTeacher中继承TeacherChemicalEngineerMaterialsEngineer等继承EngineerTeacher并且Engineer都实现了Component。该Person则只是有列表Component秒,你可以给他们正确Component的,且s Person。即person.getComponent(Teacher.class)person.getComponent(MathsTeacher.class)
添乙

1
这是最好的答案。根据经验,继承表示“是”,而接口表示“可以”。飞马是可以飞行和行走的动物。鸟是会飞的动物。马是可以行走的动物。
Robear

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
它将起作用,但是我不喜欢这种方法(Wrappper),因为那时飞马似乎拥有一匹马,而是一匹马。
Pablo Lozano 2014年

谢谢。我忍不住发表了这个主意。我有点知道这很愚蠢,但是我没看到为什么这很愚蠢……
Pavel Janicek 2014年

3
这几乎就像蒂姆·B(Tim B)的答案中未合成的合成方法。
斯蒂芬2014年

1
尽管您还应该拥有类似的东西,例如带有由Horse和Pegasus实现的“跳转”方法的IJumps和具有由Bird和Pegasus实现的“ fly”方法的IFlies,但是这或多或少是公认的方法。
MatsT 2014年

2
@Pablo no,飞马座HAS horseFeatures和HAS birdFeatures。为答案+1是因为它使代码保持简单,这与笨拙的类生成适当的Java解决方案不同。
JaneGoodall 2015年

25

我可以提出鸭型的概念吗?

最有可能的是,您倾向于使Pegasus扩展Bird和Horse的接口,但是鸭子的输入实际上表明您应该继承行为。正如评论中已经提到的,飞马不是鸟,但它会飞。因此,您的Pegasus应该继承Flyable-interface并说Gallopable-interface。

战略模式中运用了这种概念。给出的示例实际上向您展示了鸭子是如何继承FlyBehaviour和的,QuackBehaviour并且仍然会有鸭子(例如)RubberDuck不能飞行。他们本可以将Duck扩展Bird级别定为A 级,但后来他们将放弃一些灵活性,因为每个Duck人甚至包括穷人都将能够飞行RubberDuck


19

从技术上讲,您一次只能扩展一个类并实现多个接口,但是在进行软件工程时,我宁愿建议一个通常不负责任的针对特定问题的解决方案。顺便说一句,这是一个很好的面向对象的实践,不要扩展具体的类/仅扩展抽象的类以防止发生不必要的继承行为-不存在诸如“动物”之类的东西,并且不使用动物对象,而仅使用具体的动物。


13

在截至2014年2月仍处于开发阶段的Java 8中,您可以使用默认方法来实现某种类似于C ++的多重继承。您也可以看一下本教程,其中显示了一些示例,这些示例比正式文档更容易上手。


1
但是请注意,如果“鸟和马”都具有默认方法,则仍然会遇到菱形问题,并且必须在Pegasus类中单独实现它(否则会出现编译器错误)。
MikkelLøkke2014年

@MikkelLøkke:Pegasus类必须为模棱两可的方法定义替代,但是可以通过委派给任何一个超级方法(或按选定的顺序两者)来实现它们。
Holger 2014年

12

将马保持在半开门的马is中是安全的,因为马无法越过半开门。因此,我设置了一个马房服务,接受任何类型的马,并将其放入带有半扇门的马stable中。

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

我过去经常考虑多重继承,但是由于我从事编程已有15年以上的时间,所以我不再关心实现多重继承。

很多时候,当我试图应对一种针对多重继承的设计时,我后来才发布来,因为我错过了对问题域的理解。

要么

如果它看起来像鸭子,又像鸭子,却需要电池,那么您的抽象可能就错了


如果我正确地理解了您的类比,您是说接口看起来很棒,因为它们可以解决设计问题,例如,您可以通过强制类通过它们的接口来使用其他人的API。但是几年后,您意识到问题首先是设计不良。
CS

8

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

有多种缓解问题的策略。Pavel建议的Composite对象(实际上是C ++的处理方式)是最容易实现的对象。我不知道Java未来是否会通过C3线性化(或类似方法)实现多重继承,但我对此表示怀疑。

如果您的问题是学术性的,那么正确的解决方案是“鸟和马”更具体,并且假设“飞马”只是鸟和马的组合是错误的。说飞马座具有与鸟和马相同的某些固有属性(也就是说,它们可能具有共同的祖先)会更正确。正如莫里茨的回答所指出的,这可以被充分建模。


6

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

如果您希望能够在Pegasus类中利用Horse和Bird实现的方法和功能,则可以将Pegasus实现为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类,而是仅由它们的组件定义。

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

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
或者,他也可以:公共课程飞马(Pegasus)扩展动物工具马,鸟
蝙蝠侠(Batman)

OP已经意识到了这种解决方案,他正在寻找替代方案
Yogesh 2014年

@Batman,他当然可以,但是如果他想扩展等级制度,他将需要遵循这种方法
richardtz 2014年

IBirdIHorse应实施IAnimal而不是Animal
oliholz 2014年

@Yogesh,你是对的。我忽略了他陈述的地方。作为“新手”,我现在应该怎么做,删除答案,还是留在那儿?,谢谢。
richardtz 2014年

4

嗯,您的类只能是另外1个的子类,但仍然可以根据需要实现许多接口。

飞马实际上是一匹马(这是马的特例),它能够飞翔(这是这匹特殊马的“技能”)。从另一方面来说,您可以说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
   }
}

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被设计为一种易于使用的精益语言。不要编造东西并希望最好,这是一个知识领域,而不是猜测。
Gimby 2014年


3
  1. 定义用于定义功能的接口。您可以为多个功能定义多个接口。这些功能可以通过特定的动物鸟类来实现
  2. 使用继承通过共享非静态和非公共数据/方法在类之间建立关系。
  3. 使用Decorator_pattern动态添加功能。这将使您减少继承类和组合的数量。

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

何时使用装饰图案?


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

2

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

J2EE(核心JAVA)由KVR先生撰写的笔记第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”个接口继承功能的类的名称。“实现”是一个关键字,用于将接口的功能继承到派生类。

[...]

语法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;
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.