基本概念
多态父类 p = new 子类(); 向上转型
变量shape可以引用任何Shape子类类型的对象,这叫多态,即一种类型的变量,可以一用多种实际类型对象。
对于变量shape,它就有两个类型,Shape,称之为shape的静态类型;类型Circle、Line、ArrowLine,称之为shape的动态类型。
在ShapeManager的fraw方法中,shapes[i].draw()调用的是其对应动态类型的draw方法,这称之为方法的动态绑定。
继承和多态每个类有且只有一个父类,没有声明父类的,其父类为Object,子类继承了父类非privater的属性和方法,可以增加自己的属性和方法,以及重写父类的方法实现
new过程中,父类先进行初始化,可通过supper调用父类响应的构造方法,没有使用supper的情况下,调用父类的默认构造方法。
子类变量和方法与父类重名的情况下,可通过supper强制访问父类的变量和方法。
子类对象可以赋值给父类引用变量,这叫多态;实际执行调用的是子类实现的,这叫动态绑定。
继承的细节
构造方法子类可以通过super调用父类构造方法
如果子类没有使用super调用父类构造方法,则会自动调用父类构造方法。
如果父类不存在默认构造方法(存在有参构造,不存在默认的无参构造),那么子类必须在构造方法中通过super调用父类有参构造方法,否则编译错误。
下面代码,第一次输出为0,第二次输出为123,。
在new过程中,首先初始化父类,父类构造方法调用test()方法,test()方法被子类重写了,则调用的子类的test()方法,子类方法访问子类实例变量a,这个时候子类的实例变量的赋值语句和构造方法还没有执行,所以输出为0;
注:父类中调用可被子类重写的方法,是一种不好的实践,容易引起混淆,应该只调用private的方法。public class Base(){
public Base(){
test();
}
public void test(){
System.out.println(a);
}}public class Child extends Base(){
private int a = 234;
public Child(){
}
public void test(){
System.out.println(a);
}}1234567891011121314151617
静态绑定Base是Child的父类,分别访问其类中定义的静态方法和静态成员变量以及实例变量时(同名)【说明:静态变量和静态方法一般通过类名直接访问,但也可以通过类的对象访问。】
通过b(静态类型Base)访问时,访问的是Base的变量和方法。
通过c(静态类型Child)访问时,访问的是Child的变量和方法。
上面两种情况称为静态绑定
静态绑定,即访问绑定到变量的静态类型。静态绑定在程序编译阶段即可,而动态绑定则要等到程序运行时
实例变量、静态变量、静态方法、private方法,都是静态绑定的。
父子类型转换子类对象赋值给父类型的引用变量,这叫做向上转型。
父类型的变量赋值给子类型的变量,向下转型,【不一定转换成功】
一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或这个子类的子类。
判断一个父类的变量是否是某个子类的对象 :instanceof关键字。 A instanceof B
继承访问权限protectedprotected可以被子类和通包类调用,可被子类重写。
可见性重写重写方法时,一般不会修改方法的可见性,但重写时,子类方法不能降低夫父类方法的可见性。
父类public,子类public; 父类protected,子类protected或public
继承反应is-a的关系,子类对象也属于父类,子类支持父类对外的行为,子类对父类进行扩充。
防止继承finalfinal关键字加在类上,无法被继承。
加方法上,无法被重写
加在变量上,为常量
类继承的细节
子类继承父类细节。
实例化子类对象时(new Child()时),父类子类静态代码块、实例化代码块、构造方法、运行顺序。父类静态代码块(仅一次)
子类静态代码块(仅一次)
父类实例化代码块
父类构造方法
子类实例化代码块
子类构造方法public class Base {
public static int s;
private int a;
static {
System.out.println("基类静态代码块,s:" + s);
s = 1;
}
{
System.out.println("基类实例代码块,a:" + a);
a = 1;
}
public Base(){
System.out.println("基类构造方法,a:" + a);
a = 2;
}
protected void step(){
System.out.println("base s: " + s + ", a:" + a);
}
public void action(){
System.out.println("start");
step();
System.out.println("end");
}}public class Child {
public static int s;
private int a;
static {
System.out.println("子类静态代码块,s:" + s);
s = 10;
}
{
System.out.println("子类实例代码块, a:" + a);
a = 10;
}
public Child(){
System.out.println("子类构造方法,a" + a);
}
protected void step(){
System.out.println("child s:" + s +",a :" + a);
}}
public static void main(String[] args) {
System.out.println("---new Child()");
Child c = new Child();
System.out.println("\n---c.action()");
c.action();
Base b = c;
System.out.println("\n---b.action()");
b.action();
System.out.println("\n---b.s:" + b.s);
System.out.println("\n---c.s:" + c.s);123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
输出:基类静态代码块,s:0子类静态代码块,s:0---new Child()基类实例代码块,a:0基类构造方法,a1
子类实例代码块, a:0子类构造方法,a:10---c.action()start
child s:10,a :10end---b.action()start
child s:10,a :10end---b.s:1---c.s:10123456789101112131415161718192021
对象创建过程分配内存
对所有实例变量赋默认值
执行实例初始化代码分配的内存包括本类和所有父类的实例变量,但不包括任何静态变量。
实例初始化代码的执行从父类开始,再执行子类的。
任何类执行初始化代码之前,所有的实例变量都已设置完默认值。
每个对象除了保存类的实例变量外,还保存着实际类信息的引用
方法调用的过程
c.action();查看c的对象类型,找到Child类型,在Child类型中找action方法,发现没有,去父类中查找;
父类Base中找到方法action,开始执行action方法;
action先输出了start,然后发现需要调用step()方法,就从Child类型开始寻找step()方法;
在Child类型中找到了step()方法,执行Child中的step()方法,执行完后返回action方法;
继续执行action方法,输出end;
寻找执行的实例方法的时候,从对象的实际类型信息开始查找,找不到时去父类型信息中查找。
b.action();这句代码输出与c.action相同,称为动态绑定。
动态绑定实现机制时根据对象的实际类型查找要执行的方法,子类型中找不到的时候再查找父类。
如果继承层次比较深,调用效率会降低。因此使用一种称为虚放发表的方法优化调用的效率。
虚方法表,就是在类加载时为每个类创建一个表,记录该类的随想所有动态绑定的方法(包括父类的方法)及地址,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。
变量访问的过程对变量的访问是静态绑定的,无论是类变量还是实例变量。
b.a访问的是Base类定义的实例变量a
c.a访问的是Child类定义的实例变量a
继承优缺点
继承破坏封装
继承可能破坏封装,而封装可以说是程序设计的第一原则。继承没有反映出is-a关系。封装?封装就是隐藏实现细节,提供简化接口
继承可能破坏封装:子类和父类之间可能存在着实现细节的依赖。
封装的破坏:
父类不能随意增加公开方法,因为给父类增加就是给所有子类增加。
子类可能必须要重写该方法才能确保方法的正确性。
对父类而言,让子类继承和种些方法,可能是哪个是随意修改内部实现的自由。
对子类而言,通过继承实现是没有安全保障的,因为父类修改内部实现细节,它的功能就可能会被破坏。
应对继承的双面性:给方法加final修饰符,父类就保留了随意修改这个方法内部实现的自由
给类加final,父类就保留了随意修改这个类实现的自由
优先使用组合而非继承使用组合可以抵挡父类变化对子类的影响,从而保护子类,应该优先使用组合
正确使用继承