Java中的多重继承问题

继承是面向对象编程 (OOP) 语言(如Java)的主要功能之一。它是一种以增强软件设计中类重用能力的方式组织类的基本技术。多重继承是众多继承类型之一,是继承机制的重要原则。但是,它因在类之间建立模棱两可的关系而臭名昭著。因此,Java设计师放弃了这个想法,尽管不是完全放弃。本文探讨了继承的一些概念,以及 Java 中多重继承的复杂性。

概述

继承创建子类的层次结构,其中子类扩展父类的功能。这样做不仅是为了继承超类的功能,也是为了通过继承的类赋予新的含义。这种功能的扩展通常是通过重写超类的功能、添加新的方法和属性来完成的。在 Java 中,对于可以从超类派生的子类的数量没有实际限制。但是,层次结构必须遵循线性方式。

因此,在创建子类时,我们指定它使用现有类的成员,而不是声明所有新的类成员。现有类称为超类,新类称为子类,其中每个子类都能够在继承层次结构中进一步扩展成为超类。此层次结构如图 1 所示。


图 1:用于说明继承的 UML 图(由 Creately 提供的 UML 图工具提供)

继承的类型

一般而言,各种面向对象的编程语言支持多种类型的继承,例如单级继承、多级继承、多级继承、多路径继承、分层和混合继承。请注意,这些类型差异纯粹是学术性的;OOP 语言在实践中使用其中一种或多种类型。

  • 单级继承:在这里,子类派生自单个超类。
  • 多级继承:这是单级继承的扩展,但每个子类都有能力成为未来子类的超类。每个继承都保持一条线性路径。
  • 多重继承:此处,子类派生自多个基类。Java 不支持这种类型的继承,但C++支持它。
  • 多路径继承:这是多重继承的扩展,其中子类派生自基类和基类的多个子类。很明显,Java也不支持这种类型的继承。
  • 分层继承:在这种类型的继承中,可以从单个超类创建多个派生类,并且可以在树结构中扩展进程。
  • 混合继承:这只不过是多种继承类型的组合。

Java 中的多级单一继承

Java 支持单一继承,其中每个类都只派生自一个直接超类。派生的每个子类都有可能成为未来子类的超类。在单级继承中,子类继承父类的属性。我们还可以从单个父类创建多个子类,该子类可能是另一个父类的子类。因此,多级单继承本质上意味着我们可以将单级类层次结构的概念扩展到多个层次。

一个快速的例子

此示例是图 1 中所示的 UML 图的快速实现。

package com.mano.examples;
public class Company {
   public static void main(String[] args) {
      Staff staff = new Staff();
      staff.pay();
   }
}

package com.mano.examples;
import java.util.ArrayList;
import java.util.List;
public class Staff {
   private List<StaffMember> staffMembers = new ArrayList<>();
   public Staff(){
      staffMembers.add(new ExecutiveEmployee
         ("Mickey","Disney Land","1234","987654321",1236.56));
      staffMembers.add(new Employee
         ("Donald","Disney Land","2233","87459985",5603.48));
      staffMembers.add(new RegularEmployee
         ("Zairo","Disney Land","2244","87452658",12.36));
      staffMembers.add(new Apprentice
         ("Minnie","Disney Land","963258741"));
      ((ExecutiveEmployee)staffMembers.get(0)).bonusReceived(1200);
      ((RegularEmployee)staffMembers.get(2)).updateHours(5);
   }
   public void pay(){
      for(StaffMember m: staffMembers){
         System.out.println(m.toString());
         System.out.println("Earnings: "+ m.earnings());
         System.out.println("------------------------------");
      }
   }
}

package com.mano.examples;
abstract public class StaffMember {
   protected String name;
   protected String address;
   protected String phone;
   public StaffMember(String n, String a, String p){
      name = n;
      address = a;
      phone = p;
   }
   @Override
   public String toString() {
      return "StaffMember{" +
          "name='" + name + ''' +
          ", address='" + address + ''' +
          ", phone='" + phone + ''' +
          '}';
   
   }
   public abstract double earnings();
}

package com.mano.examples;
public class Employee extends StaffMember {
   protected String empId;
   protected double rate;
   public Employee(String n, String a, String p,String id,
         double r){
      super(n,a,p);
      empId = id;
      rate = r;
   }
   @Override
   public String toString() {
      return "Employee{" +
         "empId='" + empId + ''' +
         ", rate=" + rate +
         super.toString() +
         '}';
   }
   @Override
   public double earnings() {
      return rate;
   }
}

package com.mano.examples;
public class Apprentice extends StaffMember {
   public Apprentice(String n, String a, String p){
      super(n,a,p);
   }
   public double earnings(){
      return 0.0;
   }
   @Override
   public String toString() {
      return super.toString();
   }
}

package com.mano.examples;
public class ExecutiveEmployee extends Employee {
   private double bonus;
   public ExecutiveEmployee(String n, String a, String p,
         String id, double r){
      super(n,a,p,id,r);
      bonus = 0;
   }
   public void bonusReceived(double b){
      bonus = b;
   }
   public double earnings(){
      return super.earnings() + bonus;
   }
   @Override
   public String toString() {
      return "ExecutiveEmployee{" +
         "bonus=" + bonus + super.toString()+
         '}';
   }
}

package com.mano.examples;
public class RegularEmployee extends Employee{
   private int hoursWorked;
   public RegularEmployee(String n, String a, String p,
         String id, double r){
      super(n,a,p,id,r);
      hoursWorked = 0;
   }
   public void updateHours(int h){
      hoursWorked+=h;
   }
   public double earnings(){
      return rate*hoursWorked;
   }
   @Override
   public String toString() {
      return "RegularEmployee{" +
         "hoursWorked=" + hoursWorked +super.toString()+
         '}';
   }
}

多重继承

Java 不支持多重继承。多重继承是指从多个直接超类派生的类。这增加了阶级之间关系的复杂性和模糊性。如果我们考虑函数覆盖中发生的情况,这个问题是显而易见的。假设有两个类,A 和 B,每个类定义一个名为func() 的函数。现在,假设我们定义了另一个类 C,它继承自 A 和 B(多重继承),但假设这个类不会覆盖名为func() 的函数。

现在,如果我们执行以下操作:

C c = new C();
c.func();

我们可以确定调用哪个成员函数func() 吗?它是由 A 类还是 B 类定义的函数?如我们所见,C 类从 A 和 B 双重继承了这个函数。这会产生歧义,编译器正在修复以解决问题。这个问题有解决方案,但它非常复杂;因此,Java 决定通过不允许多重继承来远离问题的原因。

使用接口进行多重继承

尽管 Java 不允许多重继承,但它允许接口的多重实现。所以,在某种程度上,这个想法仍然存在。现在,什么是接口?

接口描述一组方法,但不为所有方法提供任何具体实现。我们可以借助提供方法具体实现的类来实现接口。通过这种方式,一个类可以实现多个接口,因此可以提供从一个或多个接口派生的方法的具体实现。实现一个或多个接口的类与接口类型形成is-a关系。这也意味着保证从类实例化的对象提供接口声明的功能。从此类派生的任何子类也提供相同的功能。

接口对于为许多可能不相关的类提供通用功能特别有用。因此,实现相同接口的类的对象可以响应所有接口中描述的方法调用。

从 Java 8 开始,接口通过其完整实现支持默认方法。众所周知,一个类可以实现多个接口;因此,如果多个接口包含具有相同方法签名的默认方法,则实现的类应指定要使用或重写的特定方法。

一个快速的例子

package com.mano.examples;
public interface Interface1 {
   default void func(){
      System.out.println("from interface 1");
   }
}

package com.mano.examples;
public interface Interface2 {
   default void func(){
      System.out.println("from interface 2");
   }
}

package com.mano.examples;
public class MyClass implements Interface1, Interface2 {
   public void func(){
      // Invoking Interface1 func() method
      Interface1.super.func();
      // Invoking Interface2 func() method
      Interface2.super.func();
   }
   public static void main(String[] args){
      MyClass c=new MyClass();
      c.func();
   }
}

结论

多重继承的经典问题之一称为钻石问题。这可以通过称为虚拟继承的继承机制来解决。但是,经验表明,Java并没有因为完全不允许多重继承而损失太多。事实上,Java 编译器通过为我们可以轻松解决的问题提供复杂的解决方案来摆脱它们。尽管如此,还是有一些OOP语言,如C++,支持它。

### Java 不支持多重继承的原因 Java不支持多重继承主要是为了简化语言设计并提高程序的可维护性[^3]。具体来说: #### 菱形问题(Diamond Problem) 当一个类可以从两个不同的路径继承同一个方法时,就会出现菱形问题。例如,在C++中,如果存在如下结构: ```cpp class A { public: void method() {} }; class B : public A {}; class C : public A {}; class D : public B, public C {}; // 这里会出现二义性 ``` 在这种情况下,编译器无法确定`D.method()`应该调用哪个版本的方法——来自B还是C?虽然可以通过显式的限定来解决这个问题,但这增加了开发人员的理解成本和潜在错误的风险。 为了避免此类复杂情况的发生,Java选择了只允许单重继承,并通过接口机制提供了另一种方式来实现类似的功能而不引入上述风险。 ### 接口作为替代方案 尽管Java不允许一个类直接从多个超类派生,但它确实允许多个接口的同时实现。这种方式不仅能够达到类似于多继承的效果,还避免了许多与传统意义上的多继承相关联的问题。 考虑下面的例子: ```java interface BackPageAware { void backpageAction(); } interface InformationAware { String getInfo(); } // 实现这两个接口而不是继承它们 class MyBean implements BackPageAware, InformationAware { @Override public void backpageAction() { System.out.println("Back page action performed."); } @Override public String getInfo() { return "Some information"; } } ``` 在这个例子中,`MyBean` 类实现了 `BackPageAware` 和 `InformationAware` 两个接口,从而获得了所需的行为特性,而无需面对真正的多继承所带来的挑战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值