资讯专栏INFORMATION COLUMN

《Java编程思想》笔记8.多态

chinafgj / 738人阅读

摘要:多态的作用是消除类型之间的耦合关系。编写构造器准则用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法。

点击进入我的博客

在面向对象的程序设计语言中,多态是继数据抽象(封装)和继承之后的第三种基本特征。
多态通过分离做什么怎么做,从另一角度将接口和实现分离开来。
多态的作用是消除类型之间的耦合关系。

8.1 再论向上转型

对象既可以作为它自己的本类使用,也可以作为它的基类使用。

8.1.1 忘记对象类型

我们只写一个简单的方法,它接受基类作为参数,而不是那些特殊的导出类。

public class Test {
    public static void main(String[] args) {
        func(new Unicycle());
        func(new Bicycle());
        func(new Tricycle());
    }

    public static void func(Cycle cycle) {
        cycle.ride();
    }
}

class Cycle {
    void ride() {}
}

class Unicycle extends Cycle {
    void ride() {
        System.out.println("Unicycle");
    }
}

class Bicycle extends Cycle {
    void ride() {
        System.out.println("Bicycle");
    }
}

class Tricycle extends Cycle {
    void ride() {
        System.out.println("Tricycle");
    }
}
8.2 转机

func(Cycle cycle)接受一个Cycle引用,那么编译器怎么才能知道这个Cycle引用指的是哪个具体对象呢?实际上,编译器并不知道。

8.2.1 方法调用绑定

绑定:讲一个方法调用同一个方法主体关联起来被称作绑定。

前期绑定:程序执行前进行绑定(由编译器和连接程序实现)叫做前期绑定。

后期绑定(动态绑定、运行时绑定):在运行时根据对象的类型进行绑定。

Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。

在讲解final关键字的时候讲到final关键字曾经可以提高运行效率,原因就在于它可以关闭动态绑定,必须前期绑定。

8.2.2 产生正确的行为

在编译时,编译器不需要获得任何特殊信息就能进行正确的调用。

Cycle cycle = new Tricycle();
cycle.ride();
8.2.3 可扩展性

一个良好的OOP程序中,大多数或所有方法都会遵循基类的模型,而且只与基类接口通信。
这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型。
多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

8.2.4 缺陷:“覆盖私有方法”

父类的私有方法子类是无法重载的,即子类的方法是一个全新的方法

只有非private的方法才能被覆盖

下述程序调用的依然是父类的对应方法

约定:子类中的方法不能和父类中的private方法同名,能用起个名字解决的问题不要搞得那么复杂

public class Test {
    public static void main(String[] args) {
        Test test = new TestDemo();
        test.func();
        // Output: Test
    }

    private void func() {
        System.out.println("Test");
    }
}

class TestDemo extends Test {
    public void func() {
        System.out.println("TestDemo");
    }
}
8.2.5 缺陷:域和静态方法

只有普通方法的调用是多态的

当子类对象转型为父类对象时,任何域访问操作都由编译器解析,因此不是多态的

如果某个方法是静态的,那么他就不是多态的

8.3 构造器和多态

构造器不具有多态性,因为它们也是隐式声明为static

8.3.1 构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐想和那个链接,以便每个基类的构造器都能得到调用。

因为只有基类的构造器才有恰当的方法和权限来初始化自己的元素,所以必须令所有构造器都得到调用,这样才能正确的构造对象。

没有明确指定基类构造器,就是调用默认构造器

对象调用构造器顺序

调用基类构造器(从根构造器开始)

按声明顺序调用成员的初始化方法

调用导出类的构造器

8.3.2 继承与清理

通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常会留给GC进行处理。

如果确实遇到清理的问题,在清理方法中要先写子类的清理逻辑,然后调用父类的清理方法;即清理顺序应该和初始化顺序相反

8.3.3 构造器内部的多态方法的行为

如果在构造器的内部调用正在构造的对象的某个动态绑定方法,会发生什么情况?

初始化的实际过程:

在其他任何事情发生之前,将分配给对象的存储空间初始化成二进制的零。

如8.3.1中那样调用基类构造器。因为在基类构造器中调用了func(),其实是被覆盖的func()方法。

按照声明的顺序调用成员的初始化方法。

调用导出类的构造器主体。

public class Test {
    public static void main(String[] args) {
        new Child(100);
    }
}

class Child extends Parent {
    private int i;
    void func() {
        System.out.println("Child func, i = " + i);
    }

    public Child(int i) {
        this.i = i;
        System.out.println("Before Child constructor, i = " + i);
        func();
        System.out.println("After Child constructor, i = " + i);
    }
}

class Parent {
    void func() {
        System.out.println("Parent func");
    }

    public Parent() {
        System.out.println("Before Parent constructor");
        func();
        System.out.println("After Parent constructor");
    }
}
Output:
Before Parent constructor
Child func, i = 0
After Parent constructor
Before Child constructor, i = 100
Child func, i = 100
After Child constructor, i = 100
编写构造器准则:

用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法。

在构造器中唯一能够安全调用的是基类中的finalprivate方法,因为这些方法不会被覆盖。上述代码中把Parent中的func()变成private的会得到不一样的结果。

8.4 协变返回类型

子类覆盖(重写)父类的方法时,可以返回父类返回类型的子类。
这是JSE 5之后增加的功能,如下所示。Child中的func()返回的是父类返回类型List的子类ArrayList

class Child extends Parent {
    @Override
    ArrayList func() {
        return null;
    }
}

class Parent {
    List func() {
        return null;
    }
}
8.5 用继承进行设计

准则:用继承表达行为间的差异,用字段表达状态上的变化。

8.5.1 纯继承与扩展
纯继承

只有在基类已经建立的方法才可以在导出类中被覆盖,纯粹的“is-a”的关系。

扩展

extends关键词的意思可以看出,仿佛是希望我们在基类的基础上扩展功能,即增加基类中不存在的方法,这可以称为“”is-like-a“”的关系。

这样的缺点就是扩展部分不能被基类访问,主要是在向上转型的时候。

8.5.2 向下转型与运行时类型识别(RTTI)

向上转型是安全的,因为基类不会具有大于导出类的接口。

向下转型时会有运行时类型识别(Run-Time Type Identification)机制对类型进行检查,如果发现转型失败,会抛出一个运行时异常(ClassCastException)。

RTTI的内容不仅包括转型处理,还可以查看对象类型。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72199.html

相关文章

  • javascript设计模式与开发实践(一)- 多态

    摘要:当我们对一些函数发出调用的消息时,这些函数会返回不同的执行结果,这是多态性的一种体现,也是很多设计模式在中可以用高阶函数来代替实现的原因。 PS:上一篇文章发表之后,很多朋友关注了本人的思否和掘金的博客,虽然关注的朋友还有有限,但足够让我把自己在技术上的问题积累分享给大家,也希望大家能够喜欢,同时能动一动手指,给一颗心(赞),博主会持续更新下去 多态 本文是《javascript设计模...

    wthee 评论0 收藏0
  • Java编程思想笔记14.类型信息

    摘要:接口与类型信息关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去接口并非是对解耦的一种无懈可击的保障。 点击进入我的博客 运行时类型信息使得你可以在运行时发现和使用类型信息,主要有两种方式: 传统的RTTI,它假定我们在编译时已经知道了所有的类型; 反射机制,它允许我们在运行时发现和使用类的...

    Hwg 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    李昌杰 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    Lyux 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    AaronYuan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<