面向对象的继承和多态是解决需求多变系统所要求可扩展性的技术手段。
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例字段,或子类从父类继承方法,使得子类具有父类相同的行为。父类更通用,子类更具体。
Java在实现继承的手段上与其它面向对象语言存在着较大的差异。在Java中,所有的继承都是公有继承。
超类和子类是Java程序员最常用的两个术语。其它术语为基类、祖先类、父类;派生类、(孩)子类。
子类和父类之间,“is-a”是继承的一个明显特征,是一个用来判断是否应该设计为继承关系的简单规则,它表明子类的每个对象也是超类的对象。“is-a”规则的另一个表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换。
前缀“超”和“子”来源于计算机科学和数学理论中的集合语言的术语。
例如:雇员集合是经理集合的超集,也可以说,经理集合是雇员集合的子集。
this和super都是Java的关键字,具有特殊的语义。
1、this
1)this是Java的关键字,具有特殊的语义。
2)this是编译器自动加入实例方法的隐式引用形参,是一个对该类或该类派生类实例对象的引用形参。
3)形参不能与this同名。
4)局部变量不能与this同名。
5)this的类型是该类的类型。
6)this的值是在调用实例方法时刻被确定的。
7)this的值是只读的,不能更改;但是可以将this赋予另一个对象变量。
8)this的使用:this.field、 this.method()、this()
重要提示:在本类构造器中,使用this(..)调用本类其它构造器的语句必须是本类构造器的第一条语句。
2、super 通过super关键字来实现对父类成员的访问
1)super 是Java的关键字,具有特殊的语义。
2)super只是一个指示编译器调用超类成员的特有的关键字。因为super不是一个对象的真实引用,不能将super赋予另一个对象变量。
3)形参不能与super同名。
4)局部变量不能与super同名。
5)可以理解为super的类型是该类的父类类型。
6)可以理解为super的值是 super = (superclass) this。
7)可以理解为super的值是只读的,不能更改;
8)super的使用:super.field、 super.method()、super()。
重要提示:
1、super.method() 告知Java编译器,super调用的超类方法,进行静态绑定,不会出现多态。
2、 在子类构造器中,使用super(..)调用超类构造器的语句必须是子类构造器的第一条语句。
3、如果子类的构造器没有显示地调用超类的构造器super(..)、
本类的构造器this(..),则将自动调用超类默认的构造器(没有参数的构造器super()),如果超类没有默认的构造器,则Java编译器将报告语法错误。
1.静态字段,不可以继承
2.实例实例,可以继承
在Java中,所有的实例方法默认是虚方法(Java设计人员认为虚方法用的多),不需要将方法声明为虚拟方法(不需要virtual)。动态绑定是虚方法的默认处理方式。
1.静态方法(不具有隐式形参 this),不可以继承
1)静态方法
2)静态块(静态构造方法)
2.实例方法(具有隐式形参 this)
2.1实方法(不在虚方法表中的实例方法)
1)实例构造方法,不可以继承
2)私有的实例方法,可以继承
3)final (virtual) 实例方法,可以继承
最终的虚方法:
a)是虚方法,不可以覆盖override
b)但是在定义虚方法时就final,不可能发生多态
c)所以该方法成员的调用是实调用
d)因此,从调用角度,该方法成员作为实方法看待,不放入虚方法表中。
2.2虚方法(在虚方法表中的实例方法),可以继承
1)virtual,new slot
2)override,old slot
3)final override,old slot
4)new virtual(Java不允许)×
思维导图:
1、字段
1)继承超类的所有的实例字段
2)可以增加非同名的新的实例字段
3)可以增加隐藏超类同名的新的实例字段(不需要new)
4)实例字段只存在静态绑定(由编译器在编译时决定)
5)不继承超类的静态字段
2、方法
1)继承超类的所有的实例方法
2)可以增加方法名不同的新的实例方法
3)可以覆盖方法签名(指方法名 + 参数签名)相同的实例方法
如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个实例方法就会覆盖超类中的这个相同的方法(override)。要求子类的覆盖虚方法的返回类型,应该定义为超类的被覆盖虚方法的返回类型的同类或子类
4)不能增加隐藏超类方法签名相同的新的实例方法(不存在new)
5)当方法名相同,而参数签名不同(参数的类型、顺序、个数)时,方法可以重载(overload)(包括:实例方法、构造方法)
6)虚方法可能静态绑定,也可能动态绑定
7)不能继承超类的静态方法和实例构造方法
在Java程序设计语言中,对象变量是多态的,一个对象变量可以引用本类或子类的实例对象。
方法的调用:
1.静态调用:静态方法的调用
2.实调用:实方法的调用、super方法调用
3.虚调用:虚方法调用
4.接口调用:接口方法调用
静态绑定:由编译器在编译时决定,静态调用哪个类的方法和字段
1、Invokestatic 静态调用 静态方法
1)static方法:静态方法,静态绑定
2、invokespecial 实调用
实方法
1)private方法:私有实方法,静态绑定
2)构造方法:由new自动调用的构造实方法,静态绑定
3)final virtual 方法:静态绑定
虚方法
4)final ovreride方法:当同类C或派生类D的变量调用C类final方法的时,静态绑定;但是当基类A或B的变量调用C类final方法的时,动态绑定;
5)super.方法():静态绑定,
在Java中,只能通过super.f()调用,才能调用到父类的方法实现。
在Java中,通过类变量c.f()调用,是不能调用到父类B的方法实现。
3、实例字段、静态字段
1)(实例字段、静态字段)字段:只存在静态绑定
动态绑定:由解释器在运行时决定,动态调用哪个类的方法。增加程序的通用性。
在Java中,通过基类A的变量a引用派生类B的实例对象,由于Java的所有实例方法(除private、构造方法、final virtual外,Java的虚方法表中并不包含private、构造方法、final virtual)均默认为虚方法,均放入Java的虚方法表中,所以通过a.f()调用到的方法体,只能是B类中f()的方法实现,不可能调用到A类中f()的方法实现;如果需要调用A类中f()的方法实现,只能通过super.f()调用。
1)虚方法调用:invokevirtual
2)接口调用:invokeinterface
判断超类的成员是否被继承的依据,可以把超类中可以被继承的成员,移动到子类中,逻辑上生成一个独立的子类,如果在独立子类中可以使用,这说明该成员可以被继承。否则,说明该成员不可以继承。
不可以继承的成员:
1)超类中所有的静态成员
2)超类中的实例构造方法
所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的, Java 中类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
继承格式
class 父类 { }
class 子类 extends 父类 { }
继承父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁(可读性),提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码;基类的实例方法既可以作用于基类的实例,也可以作用于派生类的实例)。提高程序的扩展性(继承的本质是类的扩展,产生了新的类型)。
继承类型
需要注意的是 Java 不支持多继承,但支持多重继承。
数据结构的可维护性:
超类的字段是所有子类的共享字段,修改超类的共享字段,子类是不需要修改的。
数据结构的可扩展性:
通过增加子类的实例字段实际上是扩展了超类的数据结构,产生了新的数据结构。
1)可以增加非同名的新的实例字段
2)可以增加隐藏超类同名的新的实例字段(不需要new)
子类增加字段,扩展数据结构,生成新的数据结构
class Father {
//父类的两个字段
int a = 1;
int b = 2;
}
class Son extends Father {
//子类扩展的两个字段
int a = 3; //隐藏超类字段new
int c = 4; //增加新的字段
}
class Field {
public static void main(String[] args) {
Father f = new Father ();
Son s = new Son ();
Father fs = s;
//静态绑定实例字段
System.out.println("Father类型变量引用Father实例:" + "a = " + f.a + "; b = " + f.b);
System.out.println("Son类型变量引用Son实例:" + "a = " + s.a + "; b = " + s.b + "; c = " + s.c);
System.out.println("Father类型变量引用Son实例:" + "a = " + fs.a + "; b = " + fs.b);
}
}
运行结果:
代码的可维护性:
通过代码复用,超类的实例方法是所有子类的共享方法,修改超类的共享方法,子类是不需要修改的。超类的实例方法可以作用于子类的实例对象。
代码重用,我们通常称为代码复用,通常代码复用是通过类与对象来实现的, 这也是面向对象编程与面向过程编程最主要的区别之一。
代码的可扩展性:
通过增加子类的实例方法,可以增加新的实例方法,可以重载超类的实例方法,可以覆盖超类的实例方法,可以隐藏超类的私有实例方法。
1)增加(virtual,private,final virtual):可以增加方法名不同的新的实例方法。virtual新的虚方法、private新的私有的实例方法、final virtual逻辑上新的实例方法
2)重载(overload):可以重载方法名相同,而参数签名不同(参数的类型、顺序、个数)的实例方法。
3)覆盖(override):可以覆盖方法签名(指方法名 + 参数签名)相同,并且返回类型相同的实例方法。
4)覆盖(override)+ 增加(virtual):可以覆盖方法签名(指方法名 + 参数签名)相同,并且子类覆盖方法返回类型是超类方法返回类型的派生类(叫协变)的实例方法。
继承私有字段和私有方法. 子类是可以继承父类所有的东西的(除了静态成员和实例构造方法),只是对于private访问控制符对外不可见,所以访问控制符跟继承没有关系,只是影响字段或者方法对外是否可见 。
class Father {
//父类的1个私有属性
private int a = 1;
//父类的一个私有方法
private void priMethod() {
System.out.println("父类的私有方法 a = " + a);
}
public void fMethod() {
priMethod();
}
}
class Son extends Father {
//子类的1个私有属性
private int a = 2; //隐藏字段new
//子类的一个私有方法,子类的私有方法与父类的私有方法无关
private int priMethod() {
System.out.println("子类的私有方法 a = " + a);
return a;
}
public void sMethod() {
priMethod();
}
}
public class PrivateMethod {
public static void main(String[] args) {
Father f = new Father ();
Son s = new Son ();
Father fs = s;
f.fMethod();
s.sMethod();
fs.fMethod();
//fs.sMethod(); //编译错误
}
}
运行结果:
继承私有字段和私有方法
class Father {
//父类的1个私有属性
private int a = 1;
//父类的一个私有方法
private void priMethod() {
System.out.println("父类的私有方法 a = " + a);
}
public void fMethod() {
priMethod();
}
}
class Son extends Father {
//子类的1个私有属性
private int a = 2; //隐藏字段new
//子类的一个私有方法
private void priMethod() {
System.out.println("子类的私有方法 a = " + a);
}
public void sMethod() {
fMethod();
}
}
public class PrivateMethod {
public static void main(String[] args) {
Father f = new Father ();
Son s = new Son ();
Father fs = s;
f.fMethod();
s.sMethod();
fs.fMethod();
//fs.sMethod(); //编译错误
}
}
运行结果:
覆盖虚方法
//父类
class CommonMethod {
protected int x = 100;
//定义一个私有的实方法
private void priMethod() {
System.out.println("父类的私有方法");
}
//定义一个最终的实方法 final virtual
//该方法不能被子类覆盖
public final void setX(int ix) {
x=ix;
}
//定义一个普通返回值的虚方法 virtual new slot
public int getX() {
return x;
}
//定义一个返回引用类型的虚方法 virtual new slot
public CommonMethod getObject() {
return new CommonMethod();
}
//定义一个具有保护权限的虚方法 virtual new slot
protected void proShowMsg() {
System.out.println("this is protected ShowMsg() in Common class");
}
//定义一个具有公共访问权限的虚方法 virtual new slot
public void pubShowMsg() {
System.out.println("this is public showMsg() in Common class");
}
//定义一个静态方法
static protected CommonMethod stShowMsg() {
System.out.println("this is static showMsg() in Common class");
return new CommonMethod();
}
}
//子类
public class OverrideMember extends CommonMethod{
//---------------------------------------增加成员 add
protected int y = 200;
//增加一个私有的实方法
private void priMethod() {
System.out.println("子类的私有方法");
}
//增加一个最终的实方法 final virtual
public final void setY(int iy) {
y = iy;
}
//增加一个普通返回值的虚方法 virtual new slot
public int getY() {
return y;
}
//---------------------------------------重载成员 overload
//增加一个重载的虚方法 virtual new slot
public void pubShowMsg(String s) {
System.out.println("this is public showMsg() in Override class" + s);
}
//---------------------------------------覆盖虚方法
//方法名相同、参数签名相同、返回类型相同、访问控制符相同
//覆盖父类中的public同名虚方法 override
public void pubShowMsg() {
System.out.println("this is public showMsg in derive class");
}
//---------------------------------------覆盖虚方法 访问控制符越来越宽松,private——>public的方向
//方法名相同、参数签名相同、返回类型相同、访问控制符越来越宽松
//覆盖父类中的protect同名虚方法,,public > protect override
public void proShowMsg() {
System.out.println("this is public ShowMsg()");
}
//---------------------------------------覆盖虚方法错误 访问控制符越来越严格,privatepublic——>private的方向
//访问控制符越来越严格,protect < public,语法错误
/*protected void pubShowMsg() {
System.out.println("this is protect ShowMsg()");
}*/
//---------------------------------------覆盖虚方法 子类方法返回类型是父类方法的派生类
//方法名相同、参数签名相同、返回类型是父类方法的派生类、访问控制符相同 override
//覆盖父类中的public同名虚方法 ,OverrideMember是CommonMethod的派生类
public OverrideMember getObject() {
return new OverrideMember();
}
//---------------------------------------覆盖虚方法错误 子类方法返回类型不是父类方法的派生类
//子类方法返回类型不是父类方法的派生类,语法错误
/*public double getX() {
return (double)x;
}*/
//---------------------------------------覆盖虚方法 并指定为final最终方法
//方法名相同、参数签名相同、返回类型相同、访问控制符相同,指定为final最终方法
//覆盖父类中的public同名虚方法 ,并指定为final最终方法 override
public final int getX() {
return x;
}
//---------------------------------------覆盖成员错误 子类方法是不能覆盖父类的final最终方法的
//子类方法覆盖父类最终方法,语法错误
/*public void setX(int ix) {
x=ix;
}*/
//---------------------------------------静态方法
//方法名相同、参数签名相同、返回类型协变、访问控制符越来越宽松
//覆盖父类中的同名静态方法
static public OverrideMember stShowMsg() {
System.out.println("this is static showMsg() in derive class");
return new OverrideMember();
}
//可以定义静态方法的重载
static public OverrideMember stShowMsg(String s) {
System.out.println("this is static showMsg() in derive class" + " s");
return new OverrideMember();
}
//---------------------------------------静态方法与实例方法在同类或派生类中,方法签名不能相同
//派生类中的静态方法与父类的实例方法签名相同,语法错误
/*public static void pubShowMsg() {
System.out.println("this is public ShowMsg()");
}*/
//派生类中的实例方法与父类的静态方法签名相同,语法错误
/*public void stShowMsg() {
System.out.println("this is static ShowMsg()");
}*/
public static void main(String args[]) {
OverrideMember oa =new OverrideMember();
oa.pubShowMsg();
oa.proShowMsg();
}
}
可以参考Java内存逻辑图
以及思维导图知识网络:
下面引博文https://blog.csdn.net/coder_what/article/details/88852544 一个例子和解释
package chapter05;
public class Duotai01 {
public static void main(String [] args){
GrandF a = new GrandF(); //a为grandf的实例
a.funA();
a = new Father(); //a为father的实例
a.funA();
}
}
class GrandF {
public void funA(){
System.out.println("A's fun he");
}
}
class Father extends GrandF{
@Override
public void funA() {
System.out.println("B's fun he");
}
}
输出结果为:
A’s fun he
B’s fun he
根据栗子大致说下这个程序的执行过程:首先先在堆里面建立这三个类,加载类中的实例方法。
在这个栗子中,Grandf明显是父类,它先继承object中的5个类,槽号分别为0-4,都放在虚表(存放虚方法的表)中,然后再加载自己的虚方法,即funA,也放在虚表之中,槽号为5。之后加载Father类,它把父类grandf的所有虚方法全部继承过来,槽号不变,但是需要覆盖槽号5的funA方法
当加载完实例之后,会在栈中给变量a分配地址,然后指向堆中的实例对象,此时实例对象指向grandf类。当开始编译器开始编译的时候,会顺着a找到grandf类,然后找到类中的方法,记录这个方法的槽号。之后交给虚拟机,虚拟机会在通过a找到实例,根据实例找到相对应的类,在通过槽号找到方法因为第一次调用a还是指向了grandf的实例,故执行grandf的方法。
当第二次把a指向father实例时,这时father实例指向father类,JVM会通过a找到father的实例,在根据槽号找到father中固定位置的方法
上面讨论了虚方法的虚调用是怎么工作的,那么实方法是如何工作的呢?
虚调用实在编译器和JVM的共同作用下才能实现,而实方法则是直接通过编译器就可以完成。
实方法是没有槽号的,当变量找到类之后,不用记录槽号,直接就会调用类中的实方法。因为没有了槽号,所以也就不存在寻找子类中的方法。实方法不需要通过实例直接就会被找到,所以在一定程度上比虚调用更快,但是很显然,虚调用实现了多态而实方法不可以。
带有final的实调用工作过程:
实调用也属于虚方法,故实调用也有槽号,但是因为带有final,所以虚调用不能被子类覆盖。当子类进行实调用的时候,就会和实方法的过程一样进行,所以加上final后也会更快
内容结构较为复杂,文字能力有限,后面继续优化语言