聊聊java中的多继承,解决Java8接口default方法多继承冲突问题【享学Java】

前言

众所周知,Java是一种面向对象的只允许单继承的语言,这是每个Java程序员从业者都知道定理。 本文的目的,主要从两个方面来思考Java单继承的这个问题:

  1. 为什么Java类被设计为只能单继承?
  2. 怎样曲线实现多继承的效果?

Java类为何设计为只能单继承

我们都知道Java的主要设计者是James Gosling,下面我引用它的一段话来对Java语言进行定义:

Java是一种简单的,面向对象的,分布式的,解释型的,健壮的,安全的,架构中立的,可移植的,高性能的,支持多线程的,动态语言。

该定义中和本文有关的一个关键词是:简单的。这个特性被放在了定义的第一位置,可见它的重要性。

首先我用一个最为直观的例子来举例说明多继承带来的问题:

class Father {
    public void eat() {
        System.out.println("爸爸吃饭方式...");
    }
}

class Mother {
    public void eat() {
        System.out.println("妈妈吃饭方式...");
    }
}


// 加入多继承是被允许的
class Son extends Father, Mother {
}

假如上面的实例是编译不抱错的,那么请问下面测试代码会输入什么?

public class Main {
    public static void main(String[] args) {
        new Son().eat();
    }
}

是不是顿时脑袋puzzle了?可能有的小伙伴会说打印Father类的呀,因为extends的时候Father在前,Mother在后。当然这是一种语言设计的解决方案,但是作为一个高级语言简单的通过这种顺序去控制这么重要的一个特性,显然我认为是不明智

在Java中,类是结构性的,如上示例的多继承会造成结构上的混乱,这也是多继承带来的非常著名的菱形继承问题上过大学的(开玩笑的)应该多多少少都了解点C++,它也是面向对象的语言,但是它支持多继承。这里其实是有一定的历史变迁的原因的:

  • C++语言是1983年由贝尔实验室的Bjarne Stroustrup在C语言的基础上推出的
  • Java语言是1995年由James Gosling共同正式推出的

Java整整延后了10+年时间,那可不要更高级一下吗。另外C++在使用过程中其实门槛是比较高的,其中一个重要原因就是它多继承的设计,让使用者(特别特别是新手)会经常掉入这个陷阱,即使它也提出了相应的解决办法。

请小伙伴理解这个高级的深刻含义,作为程序员对高级底层等词汇的理解应该是更加深刻的

对比之下,Java就吸取了教训,本着简单的原则,舍弃了C++中的多继承,从而也使得了Java更具有安全性和健壮性。

因此如上例子实际会编译报错:

为何Java类继承(实现)多个接口没有问题呢?

其实关于这一点,我个人认为Java语言在使用层面上已经做得很友好了。它把多个接口不叫继承extends,而叫实现implements,一下子从概念上就非常有区分度了,可谓对初学者非常之友好。

interface SmallEat {
    public void eat();
}

interface BigEat {
    public void eat();
}


class Son implements SmallEat, BigEat {

    @Override
    public void eat() {
        System.out.println("儿子自己的吃饭方式...");
    }
}

这个例子是能够正常编译通过,正常work的。对于为何接口为何能多继承解释如下:

  1. Java接口是行为性的,也就是说接口只是定义某个行为的名称
  2. 具体的实现动作,都在实现类本身这里。

因此,即使继承(实现)的多个接口中出现了同名的方法名,实现类中也有且只会有一个实现。所以并不会出现结构混乱的情况

为何接口可以多继承extends接口?

通过上面的阐述,相信这个问题的答案也就迎刃而解了。


Java类如何实现多继承的效果?

这里可以先举个例子:我们知道JavaScript的对象是不支持继承的,但是它却可以通过扩展原型链(propertype)的方式来实现继承类似的效果。

同样本节想解决的问题是,Java是不支持多继承的,那若我就是想要双亲呢? 下面用一个经典的例子来阐述如何解决双亲问题

class Father {
    public int strong(){
        return 9;
    }
}

class Mother {
    public int kind(){
        return 8;
    }
}

一个父亲和一个母亲,父亲的强壮指数是9,母亲的温柔指数是8。现在若生了一个儿子Son,理论上它应该是这样的:既有父亲的强壮,也有母亲的温柔。用代码可以表述双亲如下:

class Son {
    //通过继承增强父亲的行为属性:比父亲更强壮
    private class Father_1 extends Father {
        @Override
        public int strong() {
            return super.strong() + 1;
        }
    }

    //增强母亲的行为属性:没有母亲温柔
    private class Mother_1 extends Mother {
        @Override
        public int kind() {
            return super.kind() - 2;
        }
    }

    //===============public对外暴露Son自己的行为===============
    public int getStrong() {
        return new Father_1().strong();
    }

    public int getKind() {
        return new Mother_1().kind();
    }
}

儿子继承了父亲,变得比父亲更加强壮;同时也继承了母亲,只不过温柔指数下降了,我举的这个例子是不是也非常符合现实啊,哈哈。

此方案用到了一个基础知识点:内部类可以继承一个与外部类无关的类,保证了内部类的独立性,从而达到高内聚的编码规范

说明:其实有多种方式都能实现类似的效果,本文我介绍的是我认为是使用更接近多继承思维来解决问题~

Java8接口默认方法的多继承问题

我们知道Java8的一大新特性的是:接口中可以写default方法了。这其实是java自己就给自己出了一个问题。

接口可以书写默认方法了,然后又因为接口之间是可以多继承的,因而实质上Java 8的接口多继承其实也会涉及到实现多继承的问题。下面我们通过一个实例来看看Java它在语法层面的解决方案:

interface Father {
    default void eat() {
        System.out.println("爸爸吃饭方式...");
    }
}

interface Mother {
    default void eat() {
        System.out.println("妈妈吃饭方式...");
    }
}

爸爸妈妈都是接口定义,所以儿子Son实现两个接口理论上肯定是阔以的:

class Son implements Father, Mother {
}

不了编译报错如下:

说明:如果只实现一个接口,编译不会报错且default方法是能直接通过实例调用的。

==解决方案: ==

class Son implements Father, Mother {

    @Override
    public void eat() {
        System.out.println("儿子自己的吃饭方式~~~");

        // 注意这种语法是调用 【指定接口】的defualt方法:
        // 若接口名字没有冲突,直接super调用即可~~~
        Father.super.eat();
        Mother.super.eat();
    }
}

测试:

public class Main {
    public static void main(String[] args) {
        new Son().eat();
    }
}

输出结果:

儿子自己的吃饭方式~~~
爸爸吃饭方式...
妈妈吃饭方式...

归纳总结:解决接口default方法冲突的三步骤:

  1. 方法签名相同时,才表示出现了冲突。
  2. 类中的方法优先级最高。类或者父类中的方法实现优先级大于任何接口的默认方法
  3. 其实,子接口的默认方法优先级更高。
  4. 若最终还是无法判断,那么实现类必须通过显示复写的方式复写默认方法,然后再自己通过xxx.super.xxx()的方式来指定具体使用哪个接口的实现

总之,Java8在语言层面上,对若出现接口default方法冲突的解决方案是:不作为。其实不作为也是一种作为,它让编译器去提示调用者必须显示的override这个冲突的方法,让coder自己去决定调用逻辑~

总结

写这篇文章的原因是我自己在写default方法的时候出现了冲突,从而决定多java的多继承深入了解一下。 带着问题来驱动学习这样的效果会更好,希望本文也能给你带来帮助~

The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端迷

高阶函数详解与实战训练

当大家看到这个面试题的时候,能否在第一时间想到使用高阶函数实现?想到在实际项目开发过程中,用到哪些高级函数?有没有想过自己创造一个高阶函数呢?开始本篇文章的学习

6110
来自专栏大数据实战演练

Elasticsearch BulkProcessor 的具体实现

本文示例使用的是 Spring Boot 框架,由于该框架有默认的 Elasticsearch 版本,为了避免版本混乱或冲突,我在 pom.xml 文件内添加了...

77330
来自专栏牛客网

顺丰&百度 前端 一面 面经

2.数据库的选择,redis和其他数据库怎么同步(简历写了敲过java。。然后自己给自己挖了个坑。。。)

16720
来自专栏女程序员的日常_Lin

Iterator 、Generator(一)

调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和don...

7930
来自专栏美团技术团队

美团大规模微服务通信框架及治理体系OCTO核心组件开源

微服务通信框架及治理平台OCTO作为美团基础架构设施的重要组成部分,目前已广泛应用于公司技术线,稳定承载上万应用、日均支撑千亿级的调用。业务基于OCTO提供的标...

14710
来自专栏Java研发军团

Java性能优化的50个细节,我必须分享给你!

来源:blog.csdn.net/dongnan591172113/article/details/51790428

7520
来自专栏京程一灯

怎样成为全栈开发工程师[每日前端夜话0xAA]

在 LinkedIn 和 Facebook 上,有很多人将当前的工作标记为全栈工开发程师。在 Medium 上关于这个问题的文章也收到了很多读者的好评。一些人认...

7130
来自专栏Java技术栈

Java 12 骚操作, String居然还能这样玩!

栈长之前在Java技术栈微信公众号分享过《Java 11 已发布,String 还能这样玩!》这篇文章,介绍了 Java 11 的 String 新玩法,让大家...

10130
来自专栏AI科技大本营的专栏

如何用Python编写一个Lisp解释器

这篇文章有两个目的:一是展示如何实现一个计算机语言的解释器,二是演示如何使用 Python 3 构造 Lisp 的一种方言 Schema,作者把自己的这个语言解...

13840
来自专栏Java帮帮-微信公众号-技术文章全总结

浏览器大战与 JavaScript 的诞生

在 Android 设备或 iPhone 流行之前,浏览器大战的战场是桌面计算机平台。众多公司投入了数十亿美元投身这场战役,而他们的出发点就是基于这样的一个假设...

4710

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励