漫话:如何给女朋友解释为什么Java不支持多继承?
要提到多继承,首先要从继承开始说起。
面向对象的编程语言有三个重要的基本特性:封装、继承和多态。而很多人认为继承是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是不支持多重继承的,这里要提一下,这里的继承特指的是使用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不支持多继承,但是是支持多实现的,也就是说,同一个类可以同时实现多个类。
我们知道,在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 {
}
这时候,编译期会报错:
error: class 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
关于作者:漫话编程,是一个通过漫画+音频的形式讲解枯燥的编程知识的公众号。致力于让编程变得更有乐趣。
推荐阅读:
喜欢我可以给我设为星标哦