查看原文
其他

漫话:如何给女朋友解释为什么Java不支持多继承?

脚本之家 2022-04-23

The following article is from 漫话编程 Author 漫话编程

 关注脚本之家”,与百万开发者在一起

来源 | 漫话编程(ID:mhcoding)

要提到多继承,首先要从继承开始说起。

继承

面向对象的编程语言有三个重要的基本特性:封装、继承和多态。而很多人认为继承是Java面向对象编程技术的一块基石。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。

加入,我们已经定义了一个Car类,这个Car中包含了轮胎、发动机、底盘、方向盘等属性,还具有行走、加油、开窗等行为。

而如果我们想要定义一辆Bus,想要复用这些属性和行为,就可以通过继承来实现。

通过使用继承,我们使得Bus类和Car类之间存在了一定的关系,而我们通常称呼Car是Bus的父类,Bus是Car的子类。

在Java中,使用extends关键字来实现继承。

如上面Car与Bus,当写继承语句时,class Bus extends Car{ } 其中Bus类是子类,Car类是父类。

多继承

上面我们提到的Bus和Car之间的关系其实是一种单继承,指的是一个类只继承自一个父类。

在软件开发中,还有一种多继承(多重继承)的情况,顾名思义,就是一个类同时继承自多个父类。

比如维基百科中关于多继承举了一个例子:

例如,可以创造一个“哺乳类动物”类别,拥有进食、繁殖等的功能;然后定义一个子类型“猫”,它可以从父类继承上述功能,不需重新编写程序,同时增加属于自己的新功能,例如“追赶老鼠”。

但是,"猫"还可以作为"宠物"的子类,拥有一些宠物独有的能力。

作为面向对象语言,C++是支持多重继承的。

但是,多年以来,多重继承一直都是一个敏感的话题,反对者指它增加了程序的复杂性与含糊性。

Java不支持多继承

很多人知道,Java是不支持多重继承的,这里要提一下,这里的继承特指的是使用extends关键字的这种继承行为。

那么为什么Java不支持多重继承呢?

关于这个问题,Java的创始人James Gosling曾经回答过,他表示:

"Java之所以不支持一个类继承多个类,主要是因为在设计之初我们听取了来自C++和Objective-C登阵营的人的意见。因为多继承会产生很多歧义问题。"

Gosling老人家提到的歧义问题,其实是C++因为支持多继承之后带来的菱形继承问题

假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。


这时候,因为D同时继承了B和C,并且B和C又同时继承了A,那么,D中就会因为多重继承,继承到两份来自A中的属性和方法。

这时候,在使用D的时候,如果想要调用一个定义在A中的方法时,就会出现歧义。

因为这样的继承关系的形状类似于菱形,因此这个问题被形象地称为菱形继承问题。

而C++为了解决菱形继承问题,又引入了虚继承

因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。

所以,在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现(Java 8之前),这就避免了 C++ 中多继承的歧义问题。

Java 8支持多继承

Java不支持多继承,但是是支持多实现的,也就是说,同一个类可以同时实现多个类。

我们知道,在Java 8以前,接口中是不能有方法的实现的。所以一个类同时实现多个接口的话,也不会出现C++中的歧义问题。因为所有方法都没有方法体,真正的实现还是在子类中的。

那么问题来了。

Java 8中支持了默认函数(default method ),即接口中可以定义一个有方法体的方法了。

public interface Pet {

    public default void eat(){
        System.out.println("Pet Is Eating");
    }
}

而又因为Java支持同时实现多个接口,这就相当于通过implements就可以从多个接口中继承到多个方法了,这不就是变相支持了多继承么。

那么,Java是怎么解决菱形继承问题的呢?我们再定义一个哺乳动物接口,也定义一个eat方法。

public interface Mammal {

    public default void eat(){
        System.out.println("Mammal Is Eating");
    }
}

然后定义一个Cat,让他分别实现两个接口:

public class Cat implements Pet,Mammal {

}

这时候,编译期会报错:

errorclass Cat inherits unrelated defaults for eat() from types Mammal and Pet

这时候,就要求Cat类中,必须重写eat()方法。

public class Cat implements Pet,Mammal {
    @Override
    public void eat() {
        System.out.println("Cat Is Eating");
    }
}

所以可以看到,Java并没有帮我们解决多继承的歧义问题,而是把这个问题留给开发人员,通过重写方法的方式自己解决。


参考资料:

https://www.zhihu.com/question/24317891


关于作者漫话编程,是一个通过漫画+音频的形式讲解枯燥的编程知识的公众号。致力于让编程变得更有乐趣。

end


观看视频,参加留言送书活动 

↓↓↓

小编会从视频评论区挑选认真的留言
赠送【脚本之家小程序 积分【可兑换礼物

计算机优秀书籍每周销售排行榜

学习 Java 语言,你必须知道的 Java 简史

1月份Github上最热门的Java开源项目

996问题已经失控,委员建议进行监管!网友拍手称快...

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存