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

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

 [[384385]]

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

继承

面向对象的编程语言有三个重要的基本特性:封装、继承和多态。而很多人认为继承是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 ),即接口中可以定义一个有方法体的方法了。

  1. public interface Pet { 
  2.  
  3.     public default void eat(){ 
  4.         System.out.println("Pet Is Eating"); 
  5.     } 

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

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

  1. public interface Mammal { 
  2.  
  3.     public default void eat(){ 
  4.         System.out.println("Mammal Is Eating"); 
  5.     } 

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

  1. public class Cat implements Pet,Mammal { 
  2.  

这时候,编译期会报错:

  1. error: class Cat inherits unrelated defaults for eat() from types Mammal and Pet 

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

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

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

参考资料:

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

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

本文转载自微信公众号「漫话编程」,可以通过以下二维码关注。转载本文请联系漫话编程公众号。

 

责任编辑:武晓燕 来源: 漫话编程
相关推荐

2021-06-07 12:11:20

JavaRunning状态

2020-03-16 14:08:59

线程熔断限流

2019-03-12 09:43:14

反向代理正向代理服务器

2021-05-10 19:58:06

MySQLUTF-8数据库

2019-10-09 10:45:16

云计算Web互联网

2021-09-14 12:00:11

VR字节跳动

2021-04-26 14:00:43

Java 数据类型

2019-12-23 10:26:02

3PC分布式2PC

2019-04-09 09:40:23

2019-04-26 14:46:18

GitGitHub局域网

2019-07-22 10:34:31

大案牍术大数据Big Data

2020-10-19 13:01:31

删库程序员思科

2020-10-12 13:27:21

计算机浏览器电脑

2019-03-06 10:59:09

宽带王者荣耀网络

2022-05-09 10:28:42

SnapsUbuntuFlatpak

2019-04-19 09:48:53

乐观锁悲观锁数据库

2020-12-28 12:37:36

缓存击穿穿透

2020-03-23 12:57:20

撞库脱库洗库

2021-12-09 10:51:47

Go继承

2019-08-13 10:34:26

鸿蒙OS跨平台Linux内核
点赞
收藏

51CTO技术栈公众号