封装:
封装就是把对象的行为和属性装在一个类里。
抽象:
抽象是将存在于事物之间共同拥有的元素提取的过程,抽象成类或者接口。抽象分为:
1)数据抽象:用于表示事物的属性,比如人的姓名、年龄等。抽象成属性或者成员变量。
2)过程抽象:用于表示事物的行为,比如人会吃饭等。抽象成方法。
多态:
在面向对象程序设计语言中,多态是继封装和抽象的第三大特性。首先,须清楚多态是一种不能单独来看的特性,它只能作为类中"全景"关系中的一部分,与其他特性协同合作。多态的主要功能是将做什么和怎么做分离开来,从另一角度将接口和数据分离开来。多态不仅能改善代码的组织结构和可读性,还能创建可扩展的程序。
概括来说,多态的作用是消除耦合。没有多态,等号左边和右边需要相互对应,即耦合。有了多态,左边是父类(或者接口),右边是子类(或实现类),我只管调用接口里面的方法就是了,至于你实现类怎么去实现,那是你的事,你要修改一下实现,你只管去把实现类换掉,我这边一行代码都不用变,这就解耦了。
再论向上转型:
把导出类对象的引用视为其基类的引用,这种做法称为向上转型。因为在继承树的画法中,基类是处于上方的。
问题导出:
如果我们只写一个简单的方法,它仅接收基类引用作为参数,而不是写多个以导出类引用为参数的类似方法,并且这个方法对所有的导出类参数都可以正确运行,显然这种做法更好。
问题解决:
当只存在基类引用作为参数的方法时,如何让编译器准确判断调用对象的类型,从而在方法内准确调用恰当的方法(因为方法可能重载了,若无法正确判断是哪个对象,就无法正确调用)。解决的办法是后期绑定,也称动态绑定或运时绑定,当前状态我们只需了解动态绑定依赖某种机制实现的,而且Java中除了final(注意priva为隐式final)和static方法,其他所有的方法都是后期绑定。
当我们了解到动态绑定来实现多态,即消除耦合时,我们就可以编写只与基类打交道的代码了,并且适用于其导出类。
问题延伸:
使用final的另一重要原因可能是为了关闭动态绑定,这样,或许编译器就可以为final方法调用更加有效的方法,但是大多数情况下对系统性能并没有多大改善。所以在对final的使用中,应以实际设计来决定,不应为了性能盲目选择final。
几点注意:
对于基类中的私有方法,是不能被重载的,即使导出类中有同名的方法,也是视为新方法。
域是由编译器直接解析,无多态;static和final方法不可重载,无多态。
构造器是特殊的方法,不具有多态性,因为隐式static。
构造器的调用顺序:
首先,在导出类的构造器中,总是会先调用基类的构造器,若没有明确指定,则自动调用默认构造器,若基类中重写了构造器的话,编译将报错。
具体调用顺序为:
1)在其他任何事情发生之前,将分配给对象的存储空间初始化成二进制的零。
2)调用基类构造器,一直到最顶层,再往下。
3)按声明顺序调用成员的初始化方法。
4)调用导出类构造器主体。
对于1)的理解,参见代码:
class Glyph { void draw() { System.out.println("Glyph.draw()"); } Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { System.out.println("RoundGlyph.draw(), radius = " + radius); } void superDraw(){ super.draw(); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /*output: Glyph() before draw() RoundGlyph.draw(), radius = 0//在还没有创建RoundGlyph之前,radius还没有被初始化成1,而是一开始编译器知道有这个成员,先给它赋值为0。 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 */
关于清理:
对象的清理应该和初始化顺序相反,因为可能存在子对象依赖于其他对象的情况。
基类的清理,应该先对其导出类进行清理,然后才是自身。
总之,逆序。
协变返回类型:
导出类覆盖基类方法时,返回的类型可以是基类方法返回类型的子类。
class Grain { public String toString(){ return "Grain"; } } class Wheat extends Grain{ public String toString(){ return "Wheat"; } } class Mill{ Grain process(){ return new Grain(); } } class WheatMill extends Mill{ @Override Wheat process(){ //重载方法的返回类型是Wheat,而基类中的返回类型是Grain。注意只有Wheat是Grain的导出类时,才允许这种写法,称为协变返回类型。 return new Wheat(); } }
向下转型:
我们知道,向上转型是安全的,因为基类不会具有大于导出类的接口。但是向下转型,却不一定,所以必须通过运行时类型识别(RTTI)来确保。
向下转型是通过强制转换实现的,即将基类对象引用强行指向为导出类对象。如果所转类型是正确的,则转型成功;否则,编译器将会返回一个ClassCastException异常。
package com.music; class Useful { public void f(){} public void g(){} } class MoreUseful extends Useful { public void f(){} public void g(){} public void u(){ System.out.println("1"); } } public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() //向上转型 }; x[0].f(); x[1].g(); ((MoreUseful)x[1]).u();//向下转型,因为x[1]指向MoreUseful对象,所以可以向下转型。换句话说,先是向上,再向下才允许。 ((MoreUseful)x[0]).u();//虽然可以编译成功,但是会报ClassCastException异常。 } }
总结:
虽然似乎所有的东西都可以被继承,但是如果在任何创建新类时,都首先考虑使用继承技术,反而会加重我们的设计负担,这一点同样适用于设计模式的使用上。更好的方式仍然是优选组合,尤其是不能十分确定使用哪一种方式时。
来源:https://www.cnblogs.com/promiseslc/p/8734993.html