深入理解OPP的三大特性:继承、封装、多态

推荐文章

1 java为什么不支持多继承

1 继承

1.1 菱形继承会使得最底层的子类调用高层父类的方法时产生歧义,如果没有这个高层的父类,还会产生歧义吗?

菱形继承:

假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。这时候,因为D同时继承了B和C,并且B和C又同时继承了A,那么,D中就会因为多重继承,继承到两份来自A中的属性和方法。这时候,在使用D的时候,如果想要调用一个定义在A中的方法时,就会出现歧义。因为这样的继承关系的形状类似于菱形,因此这个问题被形象地称为菱形继承问题

在你描述的情况下,也就是所谓的菱形继承问题(也称为钻石继承问题),确实存在调用父类方法时的歧义。然而,如果没有这个最高层的父类(类A),即使类D同时继承了类B和类C,但是如果类B和类C没有共享一个父类,那么类D不会继承到相同的方法或属性,因此调用方法时不会产生歧义。

也就是说,如果类B和类C独立地继承了类A(它们没有共享的父类),那么类D就能明确地知道从哪个父类继承的方法应该被调用,因此就不存在菱形继承的问题。

这是因为在这种情况下,类D的两个父类(类B和类C)并没有共享一个更高层级的父类,所以不存在重复继承的问题。类D会继承来自类B的所有属性和方法,同时也会继承来自类C的所有属性和方法,但是不存在任何重叠或者冲突,因此不会有歧义。

另外,值得一提的是,在Java语言中,类不能多重继承,这也避免了菱形继承的问题。Java允许一个类实现多个接口,但是一个类只能直接继承一个父类。这样的设计规则使得在Java中不存在多重继承导致的歧义问题。

1.2 所以针对D extends B,C是不允许对吗,即使B,C没有共享的父类A

在Java中,类D不能同时继承类B和类C。Java不支持多重继承。Java的设计者们选择这样做是为了使语言更简单,避免复杂和模糊的情况,例如上述的菱形继承问题。然而,Java允许通过接口实现多重继承的效果。

1.3 A继承B,B继承C,那么当A调用父类的方法时,调用的是B还是C

在你的这个例子中,类A直接继承自类B,而类B又继承自类C。当A调用一个父类方法时,具体调用的是B还是C的方法,取决于方法的覆写(override)。

假设类B覆写了类C的某个方法,如果类A调用这个方法,那么实际上会调用类B覆写后的版本,因为B是A的直接父类。

如果类B没有覆写类C的某个方法,那么类A调用这个方法时,就会调用类C的版本,因为B并没有提供自己的版本,所以按照继承链向上查找,最后找到的是类C的版本。

这种行为也被称为“多态”。在面向对象编程中,子类可以覆写父类的方法以改变其行为,但如果没有覆写,那么子类会继承父类的行为。

1.4 在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。这里的声明多继承是什么意思,为什么有声明前缀

这里的“声明多继承”实际上是指Java允许一个类实现多个接口。在Java中,接口用关键字"implements"声明,而类的继承用关键字"extends"声明。当我们说一个类"实现"一个接口,实际上是在"声明"这个类将提供接口中所有方法的具体实现。所以,当一个类实现多个接口时,它就在"声明"它将提供这些接口中所有方法的实现,也就是"声明多继承"。

1.5 为什么声明多继承没有菱形继承问题

"声明多继承"没有菱形继承问题,是因为接口仅仅声明了方法,但没有提供实现。当一个类实现一个接口时,它必须提供该接口所有方法的实现。这意味着,如果一个类实现了两个接口,那么即使这两个接口有相同的方法,这个类也必须为这两个接口中的方法提供实现。由于这个类提供了所有的实现,所以不存在哪个实现应该被调用的问题。这就是为什么在Java中,使用接口可以实现多继承的效果,但却不会出现菱形继承问题。

1.6 java8之后允许接口中有方法实现,这会导致菱形继承问题吗

从Java 8开始,接口可以包含默认方法,也就是说,接口可以提供方法的默认实现。这看起来可能会引入菱形继承问题,但是实际上,Java有明确的规则来处理可能出现的冲突。

规则如下:

  1. 类总是优先于接口。如果一个类或者一个父类提供了一个方法的实现,那么这个实现会被使用。

  2. 如果有多个默认方法,并且类没有提供实现,那么需要显式地选择哪个接口的默认方法要使用。这可以通过在类中覆写这个方法并在新的方法中使用接口名.super.方法名()的方式来完成。

例如,如果有两个接口,InterfaceA和InterfaceB,它们都提供了相同的默认方法,而一个类ClassC同时实现了这两个接口并没有覆写这个方法,那么Java编译器会报错,因为它不能确定应该使用哪个接口的默认方法。你需要在ClassC中显式地指定应该使用哪个接口的默认方法。

所以,尽管Java 8引入了接口中的默认方法,但是通过这些规则,Java依然避免了菱形继承问题。

1.6.1 声明多继承DEMO:

这是一个例子,说明如何解决两个接口具有相同默认方法的情况。

我们有两个接口,都有一个名为printMessage的默认方法:

interface InterfaceA {
    default void printMessage() {
        System.out.println("Hello from InterfaceA");
    }
}

interface InterfaceB {
    default void printMessage() {
        System.out.println("Hello from InterfaceB");
    }
}

ClassC同时实现了两个接口,因此需要解决默认方法的冲突。它可以通过覆写冲突的方法并明确调用想要的默认方法来实现:

class ClassC implements InterfaceA, InterfaceB {
    @Override
    public void printMessage() {
        InterfaceA.super.printMessage(); // 这里选择了使用InterfaceA的默认方法
    }
}

然后,我们可以在主程序中测试ClassCprintMessage方法:

public class Main {
    public static void main(String[] args) {
        ClassC c = new ClassC();
        c.printMessage(); // 输出:Hello from InterfaceA
    }
}

这样,虽然ClassC实现了两个具有相同默认方法的接口,但通过明确选择一个接口的默认方法来使用,我们成功避免了菱形继承问题。

1.6.2 实现类中使用接口的默认方法,为什么是 InterfaceA.super.printMessage(); 带了一个super()? InterfaceA.super不是表示InterfaceA的父类吗?

在Java中,InterfaceA.super.method()语法是一种特殊的语法,用于在实现类中调用接口的默认方法。super关键字通常是指向父类的,但在这种上下文中,它是指向接口的。这样的语法设计可能让人感到有些混淆,但这是Java 8引入接口默认方法后,为了解决可能出现的名称冲突和菱形继承问题,而提供的一种明确的语法规定。

1.7 接口为什么支持多继承?

接口之所以支持多继承,是因为接口的主要目的是定义一个可以被多个类共享的公共行为规范。当我们说一个类"实现"一个接口,其实就是说这个类遵循了这个接口定义的行为规范。因此,允许一个接口继承多个其他接口,就是允许我们将多个行为规范合并到一个新的接口中。这种设计提高了代码的复用性,并允许我们根据需要创建更复杂、更具体的行为规范。此外,接口的多继承并不会引发菱形继承问题,因为接口不包含状态(即,接口没有字段),并且在引入默认方法之前,接口也不包含实现。

2 多态

2.1 什么是多态

多态:多态是允许你将父对象设置为与子对象等效的能力,可以理解为同一行为具有多个不同表现形式或形态的能力。多态性是面向对象中最重要的概念,是通过继承和接口以及在运行时实现的。

2.2 为什么需要多态

多态是面向对象编程的一个重要特性,它具有以下几个主要优点:

  1. 代码复用与代码组织:多态可以提高代码的复用性和组织性。如果你有一个代码片段可以对多种对象进行操作,那么你只需要编写一次这段代码,然后让它在运行时决定具体操作哪种对象。这不仅可以减少代码量,还可以让代码的结构更清晰。

  2. 扩展性:多态提高了软件的扩展性。通过使用基类(或接口)的引用来操作子类对象,我们可以在不修改原有代码的基础上,添加新的子类,以实现新的功能。

  3. 解耦:多态有助于实现高内聚低耦合的设计。通过把依赖关系限制在抽象层面,不同的类之间可以相互独立地扩展和修改,只要保持同样的接口。

  4. 抽象能力:多态提供了一种将抽象概念应用于具体问题的方法,可以使程序设计更符合实际需求,也使程序更易于理解和维护。

例如,假设我们有一个Animal基类和几个子类如Dog和Cat。如果我们有一段代码需要对Animal进行操作,那么无论具体是Dog还是Cat,我们都可以用相同的代码来处理。如果未来我们需要添加更多的Animal子类,比如Bird,我们也不需要修改原有的代码。这就是多态的强大之处。

3 封装

3.1 什么是封装

封装:封装是面向对象编程中的一种将数据和操作数据的方法捆绑在一起的机制,隐藏了对象的内部实现细节。封装的目的是增加代码的安全性和简洁性,使得那些细节被隐藏在类的内部,不被外部访问,只能通过类提供的方法来访问。

你可能感兴趣的:(开发语言,菱形继承)