Java教程 目录

Java组合与继承

在本小节中,我们将学习java多重继承,比较组合和继承。

java中的多重继承是创建具有多个超类的单个类的功能。与其他一些流行的面向对象编程语言(如C++)不同,java不支持类中的多重继承。

Java不支持一个类中的多个继承,因为它可能产生一些问题(钻石问题),Java有更好的方法可以实现与多重继承相同的结果。

1. Java中的钻石问题

为了方便理解钻石问题,首先假设java中支持多个继承。在这种情况下,可以有一个类层次结构,如下图所示。

假设SuperClass是一个抽象类,声明了一些方法。而ClassAClassB是继承了SuperClass类的具体类。

文件:SuperClass.java

------ -------- ----- ---------- -

    ------ -------- ---- --------------
-

文件:ClassA.java

------ ----- ------ ------- -----------

    ---------
    ------ ---- --------------
        ------------------------------- -------------- -- ----
    -

    -------- -----
    ------ ---- ----------

    -
-

文件:ClassB.java

------ ----- ------ ------- -----------

    ---------
    ------ ---- --------------
        ------------------------------- -------------- -- ----
    -

    -------- -----
    ------ ---- ----------

    -
-

现在假设ClassC实现类似于下面的内容,它同时扩展了ClassAClassB,这里实例多重继承。

文件:ClassC.java

--  --------------
-- ----------
------ ----- ------ ------- ------- -------

    ------ ---- -------
        ---------
        --------------
    -

-

请注意,test()方法中调用超类doSomething()方法。这就产生了歧义,因为编译器不知道要执行哪个超类方法。由于钻石类图,在java中称为钻石问题。Java中的钻石问题是java不支持类中多重继承的主要原因。

2. Java接口多重继承

前面我们说过类中不支持多继承,但是使用接口可以实现。单个接口可以扩展多个接口,下面是一个简单的例子。

文件:InterfaceA.java

------ --------- ---------- -

    ------ ---- --------------
-

文件:InterfaceB.java

------ --------- ---------- -

    ------ ---- --------------
-

请注意,两个接口都声明了相同的方法,现在可以使用一个接口来扩展这两个接口,如下所示。

文件:InterfaceC.java

------ --------- ---------- ------- ----------- ---------- -

    ------ ------ -- -------- -- ---------- --- ---------- ----
    ------ ---- --------------

-

这非常好,因为接口只声明方法,实际的实现将由实现接口的具体类完成。因此,Java接口中的多重继承中不存在任何歧义。

这就是java类可以实现多个接口的原因,如下例所示。

文件:InterfacesImpl.java

------ ----- -------------- ---------- ----------- ----------- ---------- -

    ---------
    ------ ---- ------------- -
        ------------------------------- -------------- -- -------- --------
    -

    ------ ------ ---- ------------- ----- -
        ---------- ---- - --- -----------------
        ---------- ---- - --- -----------------
        ---------- ---- - --- -----------------

        ----------------------
        -------------------
        -------------------
        -------------------
    -

-

是否注意到每次覆盖任何超类方法或实现任何接口方法时,都使用@Override注释。@Override注释是三个内置java注释之一,我们应该在覆盖任何方法时始终使用@Override注释。

2. Java组合

那么如果想在ClassC中使用ClassA函数methodA()ClassB函数methodB()方法,该怎么办? 可使用组合方案解决。下面是ClassC的重构版本,它使用组合来使用两个类中方法,并使用其中一个对象的doSomething()方法。

文件:ClassC.java

------ ----- -------

    ------ ---- - --- ---------
    ------ ---- - --- ---------

    ------ ---- -------
        -------------------
    -

    ------ ---- ----------
        ---------------
    -

    ------ ---- ----------
        ---------------
    -
-

3. 组合与继承

Java编程的最佳实践之一是“赞成组合而不是继承”。下面将研究一些有利于这种方法的应用。

  1. 假设有一个超类和子类如下:
    文件:ClassC.java
------ ----- -------

    ------ ---- ----------
    -
-

文件:ClassD.java

------ ----- ------ ------- -------

    ------ --- -------
        ------ --
    -
-

上面的代码编译并且工作正常,但是如果ClassC实现如下改变了怎么办:

文件:ClassC.java

------ ----- -------

    ------ ---- ----------
    -

    ------ ---- -------
    -
-

请注意,test()方法已存在于子类中,但返回类型不同。现在ClassD将无法编译,如果使用任何IDE,它将建议您更改超类或子类中的返回类型。

现在想象一下,有多级类继承和超类的情况不受控制。别无选择,只能更改子类方法签名或其名称以删除编译错误。此外,将不得不在调用子类方法的所有地方进行更改,因此继承会使代码变得脆弱。

上述问题永远不会出现在组合中,这使得它比继承更有利。

  1. 继承的另一个问题是将所有超类方法暴露给客户端,如果超类没有正确设计并且存在安全漏洞,那么即使完全注意实现类,也会受到糟糕实现的影响。
    组合有助于提供对超类方法的受控访问,而继承不提供对超类方法的任何控制,这也是组合优于继承的主要优点之一。

  2. 组合的另一个好处是它提供了调用方法的灵活性。上面的ClassC实现并不是最优的,它提供了与将被调用的方法的编译时绑定,只需极少的更改,就可以使方法调用灵活并使其动态化。

文件:ClassC.java

------ ----- -------

    ---------- --- - -----

    ------ ----------------- ---
        -------- - --
    -
    ------ ---- -------
        ------------------
    -

    ------ ------ ---- ----------- --------
        ------ ---- - --- ---------- ----------
        ------ ---- - --- ---------- ----------

        ------------
        ------------
    -
-

执行上面示例代码,得到以下结果 -

----------- -------------- -- -
----------- -------------- -- -

方法调用的这种灵活性在继承中不可用,并且提升了最佳实践以支持组合而不是继承。

  1. 单元测试中很容易组合,因为我们知道在超类中使用的所有方法,可以模拟它进行测试,而在继承中,很大程度上依赖于超类而不知道所有超类的方法将要使用,所以需要测试超类的所有方法。

上一篇:Java继承
下一篇:Java嵌套类