[笔记]继承、封装、多态

2017-10-22  本文已影响41人  蓝灰_q

面向对象编程有三大特性:封装、继承、多态。

封装

封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
使用封装的好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。

继承

继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承,继承也为实现多态做了铺垫。
在构造函数中,子类会默认调用父类的构造器,但是如果没有默认的父类构造器,子类必须要显示的指定父类的构造器(super),而且必须是在子类构造器中做的第一件事(第一行代码)
1、父类变,子类就必须变。
2、继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
3、继承是一种强耦合关系。

多态

多态是同一事物具有不同的反应,可以实现不同的对象对同一个消息进行相应,是在封装和继承的基础上实现的。
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
实现多态,要有继承、重写、父类指向子类对象三个条件。重载不需要继承,它在编译时实现多态,算是一种广义上的多态。
多态有很多优点,包括:
1.可替换性,对已存在的代码具有可替换性,对不同的类可以同样的工作
2.可扩充性,新的类不影响现有类的多态和继承等特性
3.接口性,就是超类为子类提供了共同接口
4.灵活性,在应用中可以有灵活多变的操作
5.简化性,对大量对象的编写和修改等场景中,可以简化代码

方法调用、解析、分派、静态分派、动态分派
java在加载class字节码时,要处理的引用包括直接引用和符号引用,其中符号引用并不直接指向某个目标,而是允许在运行期间才确定具体方法的引用。
所有方法调用中的目标方法在class文件中都是一个常量池中的符号引用。
这种情况下,我们执行方法之前,首先要进行方法调用,方法调用不是方法的执行,而是为了确定需要调用的是哪一个方法。
确定方法分为两个维度:
1.解析:在类加载的解析阶段,我们可以先将一部分符号引用转换为直接引用,这时要求方法在程序运行前就能确定调用版本,也就是只能处理编译时确定的方法版本,不能确定需要运行时动态加载的方法版本。符号这个条件的只有静态方法、私有方法、构造方法和父类方法,这些方法也叫非虚方法。
解析调用是个静态过程,在编译期就已确定,不会拖到运行期才去完成。
2.分派:分派就是为了确定应该执行哪个方法,分派和解析不是互斥的,
分派首先要分静态类型和实际类型,Human man=new Man();这句话里,Human就是静态类型,Man就是实际类型,编译器在编译的时候并不知道实际类型是什么(因为使用哪个重载版本,取决于运行时传入的具体参数),所以静态类型在编译期就可以知道,而实际类型需要在运行期才能知道。
2.1静态分派:依赖静态类型来定位方法版本的,就是静态分派,静态分派最典型的就是方法重载,这其实不是JVM做的,而是编译器做的。
静态分派时会根据最接近的函数参数类型,自动进行类型转换,比如func('a')在没有char参数时,会调用int参数的重载func(int i),因为字符串'a'也可以代表int值97,类型转换的顺序为char->int->long->float->double->Serializable->Charactor->Object->char...(char类型的变长参数)
2.2动态分派:依赖实际类型来定位方法版本的,就是动态分配,动态分配最典型的就是方法重写,这个是JVM做的。
在实际类型中,基类对象指向了一个子类对象,相应的方法就发生了重写,基类的方法会被子类的方法覆盖,所以动态分配首先要找到执行方法的那个对象,称为接收者Receiver,这个过程在JVM中,就是invokevirtual指令的多态查找过程,这是java方法重写的本质,分为4步:
首先,找到操作数栈顶第一个元素(用invokevirtual执行方法前,一定会先aload_1向操作数栈里压入接收者,也就是方法的执行者)所指向对象的实际类型C
然后,如果C中有对应的方法,且访问权限通过,就返回这个对象的直接引用,完成查找
否则,按照继承关系,依次对父类执行第二步的查找
最后,如果都找不到,返回java.lang.AbstractMethodError方法。
2.3宗量、单分派和多分派
方法的接收者和方法参数统称为宗量,如果只需要考虑一个维度,就是单分派,如果需要考虑多个维度,就是多分派。
Java是静态多分派,动态单分派的语言。

方法表
多态的底层是分派,分派的底层是通过方法表实现的。
动态分派需要层层搜索方法元数据,这从性能上吃不消,所以虚拟机都会提供一个“稳定优化”手段,即vtable虚方法表Virtual Method Table,对应接口的是itable,在虚方法表里,用索引代替元数据查找,可以优化提升性能。
虚方法表中,存有各个方法的实际入口地址,如果方法没有被子类重写,子类的虚方法表中就存放了父类中方法的地址。
父类和子类的虚方法表具有一样的索引序号,这样,类型转换时只需要变换虚方法表即可。
虚方法表是在类的连接阶段初始化的,完成变量初始化后,就初始化虚方法表。
虚方法表比接口方法表的性能好,因为虚方法表的偏移量都是一致地,而接口方法表不是,接口方法表必须搜索方法,所以接口方法的调用是比类方法的调用更慢。

向上转型
多态中,很容易发生向上转型;
如果A是父类,B是子类,A a=new B();这句代码,就会发生向上转型。
因为栈中的引用变量为A,堆中的实例变量为B,这时候会发生向上转型,B中与A不同的方法,会被遗忘,与A同名的方法,则会重写,所以a可以提供的是A有而B没有的方法+B和A同名方法中B的方法;
有一个典型的题目,可以帮助加深理解:
Java多态性理解

Java多态的实现机制
包括:
1.接口实现
2.继承父类重写
3.同一个类里重载
关于重载OverLoad和重写Override,前者是通过参数的多样化实现多个同名函数,后者是通过继承关系实现不同类的不同实现。重载和重写有这几个不同:
重载发生在一个类里,重写发生在继承关系的多个类里。
重载是编译时的多态性,重写是运行时的多态性。
重写时访问范围只能越来越宽松。
无法重写一个private函数。
重写时不能声明更多的异常,这不符合里式替换原则

引用

《深入理解Java虚拟机》
java提高篇(一)-----理解java的三大特性之封装
OOP三个基本特征:封装、继承、多态
java方法调用之动态调用多态(重写override)的实现原理——方法表(三)
【深入Java虚拟机】之五:多态性实现机制——静态分派与动态分派

上一篇下一篇

猜你喜欢

热点阅读