Java的静态绑定,动态绑定和多态
初识
动态绑定能够实现多态,而动态绑定离不开虚方法的调用(虚调用) ,下面让我们了解一下,类虚方法动态绑定从编码到执行阶段的大致情况
纵观整个类虚方法的动态绑定在每个阶段发生的情况:
- 编码阶段:
很显然要有类虚方法调用
类变量r.类虚方法methodName(实参列表)
- 字节码生成阶段:
(1) 编译器要根据变量r找到类R,因为涉及到虚方法的继承, 要在R或父类中,根据调用方法名称与声明方法名称的匹配、实参列表与形参列表的匹配,找到匹配的类方法声明
调用方法声明:methodName(实参列表)
类方法声明:methodName(形参列表)
(2)生成动态绑定调用该类匹配方法的指令
这个是为解释阶段,查虚方法表做准备的
类虚方法动态绑定调用 invokevirtual R.methodName(r,实参列表);
- JVM设置虚方法表阶段:
扫描代码,在方法区:
(1)生成类对象
(2)在常量池中,生成类名称的常量指针
(3)设置虚方法表
- 解释器解释执行阶段
(1)在变量的类对象的VTABLE表中,获得匹配方法的槽号。
(2)由变量的引用,找到实例对象。
(3)由实例对象的class类型的类型对象指针,找到实例的类。
(4)在实例类的VTABLE表中,根据槽号、定位虚方法。
(5)执行方法的实现代码。
- 操作系统执行阶段
执行代码的结果
(1)局部变量、形参放入栈中
(2)实例对象放入堆中
(3)访问实例对象
(4)加工实例对象中的数据
了解大致情况后,下面进行相关的解析
重要概念
Java中存在的方法和方法的调用
在Java中存在三种方法:
1、 实例方法(含有隐式形参this),实例方法又分为:
1) 实方法(不在虚方法表中):
a、 构造函数(不能继承)
b、 私有的实例方法
c、 final virtual方法。
2) 虚方法(在虚方法表中):Java中其它所有实例方法默认为虚方法。
a、 virtual虚方法
b、 override覆盖方法
c、 final override最终覆盖方法
d、 override桥接方法 + virtual新虚方法
方法表 = 偏移量为0的父类的虚方法表 + 本类的虚方法 +偏移量不为0的接口类的虚方法表
2、 接口方法(含有隐式形参this)
均为纯虚方法
3、 静态方法(没有隐式形参this)
继承只能继承虚方法表中的方法
注意每一个类虚表中前五个槽0~4,都被继承自Object的五个虚方法占用
结合前面的内容,可以发现字节码生成阶段存在"调用",下面进行详细的介绍
方法层面的调用方法
在Java字节码中,主要有以下方法调用:
1、 invokevirtual 虚调用
调用虚方法(非私有实例方法);
这个是多态的关键
2、 invokeinterface 接口方法调用
调用接口方法,在运行时再确定一个实现此接口的对象;
接口的多态很复杂,后面会专门写篇博文介绍接口的多态
3、 invokespecial 实调用 (非虚调用)
(早期的指令是invokenonvirtual,但从JDK 1.0.2开始重命名为invokespecial)
private方法子类看不见,也不会被虚化
final方法子类不能覆盖,不存在虚化问题
结合实方法很容易理解,它的范围是:
调用实例构造方法,私有方法和父类方法,以及使用super关键字调用父类的实例方法或构造器;
注意与虚方法的多态区分开,实调用很容易出现问题
后面会用代码进行相关的演示
4、 invokestatic 静态方法调用
调用静态方法
static的方法,以声明的类型为准,与实例类型无关
以上四个很关键
5、 fast_invokevfinal 虚方法实调用(解释器使用)
6、 invokedynamic JDK 7引入的,主要是为了支持动态语言的方法调用。
在运行时动态解析出调用点限定符所引用的方法之后,调用该方法(jdk1.8lamada表达式);
因为是个人复盘笔记,所以加上另外的两种方法.
下面介绍更深入的东西:
编译器层面的实调用和虚调用
编译器:编译时的实调用和虚调用
1) 根据变量的类型,查找到类型
2) 在类型中或在类型的超类中查找到方法
3) 根据方法的性质或是否是super关键字,决定是实调用还是虚调用。
实调用: invokespecial、invokestatic,解释时必静态绑定
虚调用: invokevirtual、invokeinterface
invokeinterface解释时必动态绑定
invokevirtual解释时:
1) final virtual方法,静态绑定,fast_invokevfinal
2) final override
如果是定义final override方法的类变量或该类的派生类变量调用该方法,则静态绑定,fast_invokevfinal
3) final override
如果是定义有final override方法的类的基类变量调用该方法,则动态绑定, invokevirtual
4) virtual方法、override方法,动态绑定,invokevirtual
Java编译器生成实调用的指令:
实参压栈
Invokespecial/invokestatic实调用指令 返回类型 类名.方法名(形参列表)
Java编译器生成虚调用的指令:
实参压栈
Invokevirtual/invokeinterface虚调用指令 返回类型 类名.方法名(形参列表)
//这一段可跳过
对方法的调用进一步详解:
Java中的方法调用:
1、 方法调用:
1) 静态方法调用—静态调用
字节码:invokestatic
2) private实方法、构造方法调用、final virtual—实调用
字节码:invokespecial
3) super —实调用
字节码:invokespecial
4) virtual 、override、new virtual方法—虚调用
字节码:invokevirtual
5) final override方法—虚调用
字节码:invokevirtual
到底是invokevirtual还是fast_invokevfinal,由解释器根据类的内存图来决定。
在运行时如果是需要静态绑定,解释器将会把字节码invokevirtual改为fast_invokevfinal。
关于静态绑定和动态绑定
方法层面的静态绑定和动态绑定
静态绑定和动态绑定的过程:
1)方法声明——〉方法实现——〉2)方法调用——〉3)方法的实调用和虚调用——〉4)方法的静态绑定和动态绑定
我们知道方法声明 + 方法实现就构成了方法定义,具体表现如下:
方法定义:返回类型 方法名称(形参列表) {方法体}
方法定义 = 方法声明 + 方法体
后面我们会进行方法的调用, 形式如下:
方法调用:源程序中 x.method(实参)
具体的概念:
静态绑定:在程序执行之前就已经被绑定、也就是说再编译阶段就已经知道这个方法是属于哪个类的方法。
-
private修饰的方法,不能被子类调用
-
被final修饰的方法
-
被static修饰的方法
动态绑定:在运行过程中根据调用者的动态类型来识别目标方法的情况。
动态绑定中,我们会记录方法对应的实际引用的地址,也可以理解为索引值,这里我们把它叫做方法表。
方法表使用了数组的数据结构,每个数组元素指向了当前类以及其祖先类中非私有的实例方法。
这个数据结构,便是JVM实现动态绑定的关键所在
三大方法与调用的对应关系
虚方法-动态绑定 实方法-静态绑定 静态方法–静态绑定
通过这些指令的描述,我们发现虚方法和 实方法与静态方法 直接决定了静态绑定还是动态绑定,也就决定了是直接用父类的方法还是动态地用子类重写的方法。
解释器层面的静态绑定和动态绑定
解释器:解释时的静态绑定和动态绑定
1) 静态绑定:由变量的类型来确定调用的成员(域成员、方法成员)。
(由变量的类型确定方法代码的入口地址。)
a. 确定类型对象的入口地址
根据类名称确定类型对象的入口地址。
b. 确定方法代码的入口地址
在类对象中,根据方法的返回值和方法签名进行匹配,获得该方法,确定方法代码的入口地址。
c. 直接调用该类中的该方法的代码。
2) 动态绑定(多态):由变量所引用的实例的类型来确定调用的方法成员。
(由变量的类型和变量所引用的实例对象的类型确定方法代码的入口地址。
a. 确定类型对象的入口地址
根据类名称确定类型对象的入口地址。
b. 确定虚方法的槽号 (偏移地址)
在类对象的虚方法表中,根据方法的返回值和方法签名进行匹配,获得该虚方法在虚方法表VTable中的槽号。
c. 首先验证引用类型变量是否为null,如果为空则产生异常。
d. 根据变量的引用,获得实例对象(的入口地址)。
e. 由实例对象中的Class引用,获得实例的类对象(的入口地址)。
f. 在类对象的虚方法中,根据槽号,获得该虚方法。
g. 调用该虚方法的代码。
下面介绍一下绑定的情形:
静态绑定的情形:
1、域
x.field 域访问一定静态绑定;
2、方法
x.method(实参列表) 方法调用,以下6种方法调用静态绑定;
1)private method: 私有的实方法
2)constructor method: 实例构造方法,不能继承
三种调用方式:new method(实参列表)、this(实参列表)、super(实参列表)
3)static method: 静态方法
4)final virtual method: 最终虚方法
5)super.method: 通过super关键字调用超类的方法
6)final override method: 如果是定义final override方法的类变量,
或该类的派生类变量调用该方法。
动态绑定的情形:
x.method(实参列表) 方法调用,以下5种方法调用动态绑定
1)virtual method: 虚方法, new slot
2)override method: 覆盖方法, reuse slot
3)final override method: 最终覆盖方法, reuse slot
如果是定义有final override方法的类的基类变量调用该方法。
4)overide virtual method: 桥接方法 override + 新虚方法 new slot
5)interface method: 接口方法, new slot
方法之间的关系:
Java语言调用方法:方法名(实参列表)
所以,Java语言中不允许定义方法签名相同,而返回值不同的方法。
字节码语言调用方法:返回类型 方法名(实参列表)
所以,字节码语言中允许定义方法签名相同,而返回值不同的方法。
1) overload 方法重载
方法名相同(功能相同),形参签名不同(参数的个数,类型,顺序不同),即方法重载。
形参签名:指形参的个数,类型,顺序
方法签名:方法名称 + 参数签名 //与形参名称无关
Java语言调用方法:方法名(实参列表)
所以,Java语言中不允许定义方法签名相同,而返回值不同的方法重载。
字节码语言调用方法:返回类型 方法名(实参列表)
所以,字节码语言中允许定义方法签名相同,而返回值不同的方法重载。
2) virtual生成一个虚方法,放入本类虚方法表中的一个新槽号(new slot)中。
3) override 覆盖父类中的虚方法,将生成的方法,放入本类虚方法表中的一个旧槽号(old slot)中。
4) final virtual 生成一个实方法,实调用
final override 最终覆盖,不允许在派生类中再次覆盖该虚方法。
5) new 隐藏
a、 Java支持字段的隐藏,是静态绑定
b、 new virtual 支持虚方法的隐藏。
字节码语言中允许定义方法签名相同,而返回值不同的方法重载。
**所以在Java类的虚方法中,
方法:**
//1、编译:源程序—〉编译器编译—〉字节码文件
//2、生成执行环境
//2、扫描:字节码文件—〉JVM扫描—〉生成方法区的类对象,修改字节码
//3、执行:方法实现代码—〉解释器解释—〉栈区的局部变量和形参、堆区的实例对象
走近多态
概念
多态是指一个程序中相同的名字表示不同的含义的情况。
多态有两种情形:
1.编译时多态:
1) 重载(overload)多个同名的不同方法
如:p.sayHello(); p.sayHello(“wang”);
2.运行时多态:
1) 覆盖(override)子类对父类方法进行覆盖
2)动态绑定(dynamic binding)—-也成为虚方法调用(virtual method invoking),真正的方法在运行时才确定。
3)在调用方法时,程序会正确地调用子类对象的方法。
从中也可以看出多态的必要条件:
1. 引用类型的继承
2. 虚方法,覆盖
3. 虚方法动态绑定
多态优点(类虚方法、接口方法):
1) 通用性:
多态屏蔽了子类对象的差异,使得调用者可以写出通用的代码,而无需针对每个子类来写出不同的代码。使得代码清晰、简单。
2) 可扩展性
当增加新的子类时,调用者的代码无需变动就能适用新的子类。
通过这些优点可以看出来多态的强大,也正是这些强大的特性,不看到多态的"底层"部分,便很难认识它.
黑盒表面学习的永远是表象
我在中国慕课mooc 北京大学<Java程序设计>中看到了一个概念:
上溯造型(upcasting):
是把派生类型当做基本类型处理
写这篇博文之前经常遇到这种的情况
比如说:(代码仅仅是展示)
Person p = new Student();
void fun(Person p){...}
fun(new Person)
fun()内不仅可以传入person 还可以传一个new Student,也就是说它是子类或者同一类型都可以传出来
但是问题是 传进去的参数既然是实际类型和写的类型不一样,它在运行的时候,怎么调用,这个涉及到虚方法的调用也引发了我的思考
注意用虚方法调用,可以实现运行时的多态.
子类重载了父类方法时,运行时系统根据调用该方法的实例的类型(也就是刚才说的Student类型,而不是类型Person,这里存在动态绑定)来决定选择哪个方法调用,所有的非final方法都会自动地进行动态绑定!
补充 动态类型可以通过instanceof方法来进行判断
实战理解
virtual_override
对应上文方法之间的关系 3) override 覆盖父类中的虚方法,将生成的方法,放入本类虚方法表中的一个旧槽号(old slot)中。
多层次的继承,这里展示4级
//运行时的多态性
class A {
//产生新的虚(virtual)方法MethodVirtual(),new slot //JVM创建相应虚表,该方法槽号为5
void MethodVirtual() {
System.out.println("aV");
}
//产生新的虚(virtual)方法MethodVirtual1,new slot //JVM创建相应虚表,该方法槽号为6
void MethodVirtual1(){
System.out.println("aV1");
}
}
class B extends A {
// 覆盖父类的MethodVirtual()方法,reuse slot //JVM创建相应虚表,该方法槽号为5
void MethodVirtual() {
System.out.println("bV");
}
// 覆盖父类的MethodVirtual1()方法,reuse slot //JVM创建相应虚表,该方法槽号为6
void MethodVirtual1() {
System.out.println("bV1");
}
}
class C extends B {
//注意这里进行了注释,没有覆盖父类方法,只是继承
/*
void MethodVirtual() {
System.out.println("cV");
}
void MethodVirtual1() {
System.out.println("cV1");
}
*/
}
class D extends C {
// 覆盖MethodVirtual()方法 //JVM创建相应虚表,该方法槽号为5
void MethodVirtual() {
System.out.println("dV");
}
// 覆盖MethodVirtual()1方法 //JVM创建相应虚表,该方法槽号槽号为6
void MethodVirtual1() {
System.out.println("dV1");
}
}
public class Main {
public static void main(String[] args) {
A a;
B b;
C c;
D d;
//************************************
//多态的演示
a = new A(); //创建A的实例对象a
b = new B(); //创建B的实例对象b
c = new C(); //创建C的实例对象c
d = new D(); //创建D的实例对象d
A ab = b; //A类型的变量ab引用 B类型的实例对象b
A ac = c; //A类型的变量ac引用 C类型的实例对象c
A ad = d; //A类型的变量ad引用 D类型的实例对象d
B bc = c; //B类型的变量bc引用 C类型的实例对象c
B bd = d; //B类型的变量bd引用 D型的实例对象d
C cd = d; //C类型的变量cd引用 D型的实例对象d
System.out.println("--------------------方法多态---------------------------");
System.out.println("--------------------a.MethodVirtual()---------------------------");
a.MethodVirtual();
//解释器通过a的类型A找到MethodVirtual()在虚表中的槽号5,接着通过a的引用找到对应虚表槽号为5的方法,执行相应的程序
ab.MethodVirtual();
//解释器通过ab的类型A找到MethodVirtual()在虚表中的槽号5,接着通过ab的引用(引用的是B类型的实例对象)找到对应虚表槽号为5的方法,执行相应的程序
ac.MethodVirtual();
//同ab 但是注意C类继承了B类的虚方法
ad.MethodVirtual();
//同ab
System.out.println("--------------------a.MethodVirtual1()---------------------------");
a.MethodVirtual1();
//解释器通过a的类型A找到MethodVirtual1()在虚表中的槽号6,接着通过a的引用找到对应虚表槽号为6的方法,执行相应的程序
ab.MethodVirtual1();
//解释器通过ab的类型A找到MethodVirtual1()在虚表中的槽号6,接着通过ab的引用(引用的是B类型的实例对象)找到对应虚表槽号为6的方法,执行相应的程序
ac.MethodVirtual1();
//同ab 但是注意C类继承了B类的虚方法
ad.MethodVirtual1();
//同ac
//下面的代码与上面的执行方式相似,没有什么好说的
System.out.println("--------------------b.MethodVirtual()---------------------------");
b.MethodVirtual();
bc.MethodVirtual();
bd.MethodVirtual();
System.out.println("--------------------b.MethodVirtual1()---------------------------");
b.MethodVirtual1();
bc.MethodVirtual1();
bd.MethodVirtual1();
System.out.println("--------------------c.MethodVirtual()---------------------------");
c.MethodVirtual();
cd.MethodVirtual();
System.out.println("--------------------c.MethodVirtual1()---------------------------");
c.MethodVirtual1();
cd.MethodVirtual1();
System.out.println("--------------------d.MethodVirtual()---------------------------");
d.MethodVirtual();
System.out.println("--------------------d.MethodVirtual1()---------------------------");
//d = null;
d.MethodVirtual1();
}
}
测试结果:
用电脑端作图太麻烦了,有兴趣的参考之前的内存逻辑图画出图示
多个子类继承同一父类
//父类
class Base {
//产生新的虚(virtual)方法print(),new slot //JVM创建相应虚表,该方法槽号槽号为5
public void print() {
System.out.println("父类...");
}
}
//子类
class Son1 extends Base {
// 覆盖(Override)父类的print()方法,reuse slot //JVM创建相应虚表,该方法槽号槽号为5
public void print() {
System.out.println("子类1...");
}
}
class Son2 extends Base {
// 覆盖父类的print()方法,reuse slot // JVM创建相应虚表,该方法槽号槽号为5
public void print() {
System.out.println("子类2...");
}
}
public class Main {
public static void main(String[] args) {
//多态
//obj指向自己
Base obj=new Base(); //创建Base类的实例对象obj
obj.print();
//解释器通过obj的类型Base找到print()在虚表中的槽号5,接着通过obj的引用(引用的对象也是Base)找到对应虚表槽号为5的方法,执行相应的程序
//obj指向子类Son对象
obj = new Son1();
obj.print();
//解释器通过obj的类型Base找到print()在虚表中的槽号5,接着通过obj的引用(引用的是Son1类型的实例对象)找到对应虚表槽号为5的方法,执行相应的程序
//obj指向子类Son2对象
obj= new Son2();
obj.print();
//与上同理
}
}
结果:
多层次的继承,在每一层级的继承中,都有一方法调用另一方法
原理相似 之后的老生常谈的注释会越来越少
//运行时的多态性
class A {
//private void f(){ //产生新的虚(virtual)f(),new slot //JVM创建相应虚表,该方法槽号槽号为5
public void f(){
System.out.println("A::f()");
}
//产生新的虚(virtual)ff(),new slot //JVM创建相应虚表,该方法槽号槽号为6
public void ff(){
f();
}
}
class B extends A {
// 覆盖(Override)父类的f()方法,reuse slot //JVM创建相应虚表,该方法槽号槽号为5
public void f(){
System.out.println("B::f()");
}
//继承父类槽号为6的方法ff()
}
class C extends B {
// 覆盖(Override)父类的f()方法,reuse slot //JVM创建相应虚表,该方法槽号槽号为5
public void f(){
System.out.println("C::f()");
}
//继承父类槽号为6的方法ff()
}
public class Main {
public static void main(String args[])
{
System.out.println("helloWorld");
A a = new A();
B b = new B();
C c = new C();
A ab = b;
A ac = c;
B bc = c;
System.out.println("--------------------a.ff()---------------------------");
a.ff();
//解释器通过a的类型A找到ff()方法在虚表中的槽号6,接着通过a的引用(引用的对象也是A)找到对应虚表槽号为6的方法,执行相应的程序
ab.ff();
//解释器通过ab的类型A找到ff()方法在虚表中的槽号6,接着通过ab的引用(引用的对象是B的实例对象)找到对应虚表槽号为6的方法,执行相应的程序
ac.ff();
//同理
System.out.println("--------------------a.f()---------------------------");
a.f();
ab.f();
ac.f();
System.out.println("--------------------b.ff()---------------------------");
b.ff();
bc.ff();
System.out.println("--------------------b.f()---------------------------");
b.f();
bc.f();
System.out.println("--------------------c.ff()---------------------------");
c.ff();
System.out.println("--------------------c.f()---------------------------");
c.f();
}
}
结果:
//运行时的多态性
class A {
private void f(){ //注意访问修饰符是private,该方法为实方法,不能被继承,JVM创建方法表的时候,归为实方法表,槽号为0
//public void f(){
System.out.println("A::f()");
}
public void ff(){ //虚方法,槽号为5
f();
}
}
class B extends A {
//首先继承父类的ff()方法,槽号为5
public void f(){ //虚方法,槽号为6
System.out.println("B::f()");
}
}
class C extends B {
//首先继承父类的ff()方法,槽号为5
public void f(){ //重载,虚方法,槽号为6
System.out.println("C::f()");
}
}
public class Main {
public static void main(String args[])
{
System.out.println("helloWorld");
A a = new A();
B b = new B();
C c = new C();
A ab = b;
A ac = c;
B bc = c;
System.out.println("--------------------a.ff()---------------------------");
a.ff();
ab.ff();
ac.ff();
System.out.println("--------------------a.f()---------------------------");
//a.f(); //error 语法错误
//ab.f(); //error 语法错误
//ac.f(); //error 语法错误
//注意A类型的类 f()方法为私有的,后代不能继承,该方法在实方法表槽为0的位置,但是继承此类的实方法表槽为0的方法和这个没有关系
System.out.println("--------------------b.ff()---------------------------");
b.ff();
bc.ff();
System.out.println("--------------------b.f()---------------------------");
b.f();
bc.f();
System.out.println("--------------------c.ff()---------------------------");
c.ff();
System.out.println("--------------------c.f()---------------------------");
c.f();
}
}
结果:
final_override—invokevirtual
注意创建一个实例对象的时候,构造方法那一块有一个注意事项:首先要运行父类的构造方法,然后才运行自己的构造方法,后面也会专门写篇关于实例构造方法的博文,好好介绍一下
//语法上:
//1、final方法告知编译器,该方法在派生类中是不能覆盖的方法。
//final方法的两种原因
//1、final语义上,是不允许在派生类中改变该方法的语义。如果不允许在派生类中改变该方法的语义,就必须final该方法。
//2、提高运行效率:
// 如果派生类中均不覆盖该虚方法,那么就可以final该方法。
//
// 当该类或派生类类型的变量调用该方法时,将进行静态绑定,提高程序的运行效率。
// 在xxx.class文件中,javac生成invokevirtual虚调用
// 在运行时,解释器里可以把invokevirtual改写为一个行为跟invokespecial相似的
// 内部字节码指令,叫做fast_invokevfinal,不需要查vtable就可以调用到目标方法。
//
// 当该类的基类类型的变量调用该方法时,将进行动态绑定。
// 在xxx.class文件中,javac生成invokevirtual虚调用
// 在运行时,解释器按invokevirtual虚调用执行。
//祖父类
class Grandfather {
public int a; //实例字段 要继承的
public Grandfather() {
a = 10; //构造方法 赋初值
}
//虚方法method,virtual,new slot //槽号为5
public void method() {
System.out.println("GrandFather方法...");
}
}
//父类
class Father extends Grandfather {
public int b;
public Father() {
b = 20;
}
//虚方法method,final override,reuse slot //槽号为5 后代不能够覆盖
public final void method() {
System.out.println("final、Father方法...");
}
}
//子类
class Son extends Father {
int c;
public Son() {
c = 30;
}
//编译错误:Son中的method()无法覆盖Father中的method();因为被覆盖的父方法为final。
//public void method() {
// System.out.println("final、Son方法...");
//}
}
public class Main{
public static void main(String[] args) {
System.out.println("--------实例化一个祖先类对象...");
Grandfather grandfatherObj = new Grandfather();
// 动态绑定method方法
grandfatherObj.method();
System.out.println("--------实例化一个父类对象...");
Father fatherObj = new Father();
// 动态绑定method方法
grandfatherObj = fatherObj;
grandfatherObj.method();
//编译器生成invokevirtual字节码方法调用,解释器在运行时,判断出是final override方法,将改为fast_invokevirtual静态绑定调用。
// 静态绑定final method方法
fatherObj.method();
System.out.println("--------实例化一个子类对象...");
Son sonObj = new Son();
// 动态绑定method方法
grandfatherObj = sonObj;
grandfatherObj.method();
// 静态绑定final method方法
fatherObj = sonObj;
fatherObj.method();
// 静态绑定final method方法
sonObj.method();
}
}
结果:
override_virtual
//祖父类
class GrandFather{
public int a;
public GrandFather() {
a = 10;
}
//虚方法method,new slot
public GrandFather method() {
System.out.println("--------调用GrandFather method()方法");
return new GrandFather();
}
}
//父类
class Father extends GrandFather {
public int b;
public Father() {
b = 20;
}
//添加一个桥接方法,调用Father method(), override
//注意: 当派生类中的方法名和父类中的方法名一样,但返回值为父类方法返回值的派生类时,此时需用到动态绑定的桥接方法
//桥接方法很重要!!!!!!!! 不要忽略
//虚方法method,返回可协变的类Father, new slot
public Father method() {
System.out.println("--------调用Father method()方法");
return new Father();
}
}
//子类
class Son extends Father {
int c;
public Son() {
c = 30;
}
//添加一个桥接方法,调用Son method(), override
//虚方法method,返回可协变的类Son, new slot
public Son method() {
System.out.println("--------调用Son method()方法");
return new Son();
}
}
public class Main{
public static void main(String[] args) {
GrandFather grandFatherObj = new GrandFather();
Father fatherObj = new Father();
Son sonObj = new Son();
System.out.println("--------GrandFather类型的变量调用method()方法,返回GrandFather类型的引用");
grandFatherObj.method();
grandFatherObj = fatherObj;
grandFatherObj.method();
grandFatherObj = sonObj;
grandFatherObj = grandFatherObj.method();
System.out.println(grandFatherObj.a);
//System.out.println(grandFatherObj.b); //error
//System.out.println(grandFatherObj.c); //error
System.out.println(((Father)grandFatherObj).b);
System.out.println(((Son)grandFatherObj).c);
System.out.println("--------Father类型的变量调用method()方法,返回Father类型的引用");
fatherObj.method();
fatherObj = sonObj;
fatherObj = fatherObj.method();
System.out.println(fatherObj.a);
System.out.println(fatherObj.b);
//System.out.println(fatherObj.c); //error
System.out.println(((Son)fatherObj).c);
System.out.println("--------Son类型的变量调用method()方法,返回Son类型的引用");
sonObj = sonObj.method();
System.out.println(sonObj.a);
System.out.println(sonObj.b);
System.out.println(sonObj.c);
}
}
结果:
反编译一下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Main {
public Main() {
}
public static void main(String[] args) {
GrandFather grandFatherObj = new GrandFather();
Father fatherObj = new Father();
Son sonObj = new Son();
System.out.println("--------GrandFather类型的变量调用method()方法,返回GrandFather类型的引用");
grandFatherObj.method();
fatherObj.method();
grandFatherObj = sonObj.method();
System.out.println(grandFatherObj.a);
System.out.println(((Father)grandFatherObj).b);
System.out.println(((Son)grandFatherObj).c);
System.out.println("--------Father类型的变量调用method()方法,返回Father类型的引用");
fatherObj.method();
fatherObj = sonObj.method();
System.out.println(fatherObj.a);
System.out.println(fatherObj.b);
System.out.println(((Son)fatherObj).c);
System.out.println("--------Son类型的变量调用method()方法,返回Son类型的引用");
sonObj = sonObj.method();
System.out.println(sonObj.a);
System.out.println(sonObj.b);
System.out.println(sonObj.c);
}
}
静态绑定
static—invokestatic
//静态方法、静态字段,永远都是静态绑定
public class StaticDemo {
// 静态变量,用于统计创建对象的个数
public static int count = 0;
public StaticDemo() {
//通过类名可以直接访问静态字段成员
count++;
}
// 静态方法,用于输出count的个数
public static void printCount() {
System.out.println("创建的实例的个数为:" + count);
}
public static void main(String[] args) {
//使用for循环创建对象
for (int i = 0; i < 10; i++) {
StaticDemo counter = new StaticDemo();
}
//通过类名直接访问静态方法成员
StaticDemo.printCount();
}
}
结果:
静态绑定 结果很明朗,爽不爽???
field—new(隐藏)
class A{
int num= 6;
int getNum() { //虚方法getNum,virtual,new slot
return num; //静态绑定A.num
}
}
class B extends A{
int num = 9;
int getNum() { //虚方法getNum,override,reuse slot
return num; //静态绑定B.num
}
}
class Main {
public static void main(String[] args) {
A a = new A();
B b = new B();
A ab = b;
System.out.println(a.getNum()); //动态调用虚方法getNum
System.out.println(ab.getNum()); //动态调用虚方法getNum
System.out.println(b.getNum()); //动态调用虚方法getNum
System.out.println(a.num); //静态绑定A.num
System.out.println(ab.num); //静态绑定A.num
System.out.println(b.num); //静态绑定B.num
}
}
/*
域是不支持动态绑定,虚方法可能是动态绑定的;
基类引用变量ab,动态调用的是 B类的方法 getNum(),
但是我们在方法里面调用的参数this的类型是B类型,指向的是this.num,静态绑定B.num,也就是9,所以调用的会是B里面的num, 也就是9;
但是直接输出a.num的话, a的类型是A类型,编译时期就绑定了A.m ,所以是6
但是直接输出ab.num的话, ab的类型是A类型,编译时期就绑定了A.m ,所以是6
但是直接输出b.num的话, b的类型是B类型,编译时期就绑定了B.m ,所以是6
*/
结果:
constructor—invokespecial
//构造器方法是不能继承的实方法,静态绑定
//声明Person类
class Person {
/* 属性,成员变量 */
// 姓名
private String name;
// 年龄
private int age;
// 地址
private String address;
// 默认构造方法
public Person() {
}
// 构造方法
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
/* 方法 ,属性对应的获取和设置方法(get/set) */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
// 输出信息
public void display() {
System.out.println("姓名:" + name + ",年龄:" + age + ",地址:" + address);
}
}
public class PersonDemo {
public static void main(String[] args) {
//创建Person类的一个对象p
//Person p=new Person();
Person p=new Person("小明",35,"郑州");
//使用对象p,调用display()方法显示对象各成员变量的值
p.display();
}
}
//new Constructor
//1、计算空间、申请空间、分配内存。OutOfMemoryError异常
//2、创建一个实例对象的内置数据结构(build-in object data structure)
//3、初始化内置数据结构:MarkWord、Class类型的指针。
//4、默认初始化字段/域:数值清零、布尔类型为false、引用为null。均清为二进制0。
//5、构造函数初始化
结果:
private—invokespecial
class A {
//私有的实方法
private void f(){
System.out.println("A::f()");
}
//virtual,new slot
public void ff(){
f(); //实调用,ff()隐藏的形参this的类型为A
}
}
class B extends A {
//protected有两层含义:包访问权限或继承访问权限
//virtual 虚方法,在虚方法表中占用一个新槽new slot,有固定的槽号
//virtual,new slot
protected void f(){
System.out.println("B::f()");
}
//override,reuse slot
public void ff(){
f(); //虚调用,ff()隐藏的形参this的类型为B
}
}
class C extends B {
//override,reuse slot
public void f(){
System.out.println("C::f()");
}
}
public class Main{
public static void main(String args[])
{
A a = new A();
B b = new B();
C c = new C();
A ab = b;
A ac = c;
B bc = c;
System.out.println("--------------------a.ff()---------------------------");
a.ff();
ab.ff();
ac.ff();
System.out.println("--------------------a.f()---------------------------");
System.out.println("由于访问控制符为private,类外部不可访问,所以编译错误");
//a.f();
//ab.f();
//ac.f();
System.out.println("--------------------b.ff()---------------------------");
b.ff();
bc.ff();
System.out.println("--------------------b.f()---------------------------");
b.f();
bc.f();
System.out.println("--------------------c.ff()---------------------------");
c.ff();
System.out.println("--------------------c.f()---------------------------");
c.f();
}
}
结果:
final_virtual—fast_invokevfinal
//1、final方法告知编译器,该虚方法在派生类中是不能覆盖的虚方法
//2、final语义上,是不允许在派生类中改变该方法的语义。
//3、如果派生类中均不覆盖该虚方法,那么就可以final该方法,当该类或派生类类型的变量调用该方法时,将进行静态绑定,提高程序的运行效率。
//祖父类
class Grandfather {
public int a;
public Grandfather() {
System.out.println("调用祖父类构造方法...");
a = 10;
}
//final virtual
public final void method() {
System.out.println("GrandFather方法...");
}
}
//父类
class Father extends Grandfather {
public int b;
public Father() {
System.out.println("调用父类构造方法...");
b = 20;
}
//Error:被覆盖的方法为final
//Father中的method()无法覆盖Grandfather中的method()
//public void method() {
// System.out.println("final、Father方法...");
//
//}
}
public class Final_Virtual {
public static void main(String[] args) {
System.out.println("--------实例化一个祖父类对象...");
Grandfather grandfatherObj = new Grandfather();
// 静态绑定method方法
grandfatherObj.method();
System.out.println("--------实例化一个父类对象...");
Father fatherObj = new Father();
//编译器生成invokevirtual字节码方法调用,解释器在运行时,判断出是final virtual方法,将改为fast_invokevfinal静态绑定。
// 静态绑定final virtual method方法
grandfatherObj = fatherObj;
grandfatherObj.method();
// 静态绑定final virtual method方法
fatherObj.method();
// Object类中public final void getClass()
Object o = new Object();
//静态绑定final virtual method方法,
o.getClass();
}
}
结果:
final_override—fast_invokevfinal
//祖父类
class Grandfather {
public int a;
public Grandfather() {
System.out.println("调用祖父类构造方法...");
a = 10;
}
//虚方法method,virtual,new slot
public void method() {
System.out.println("GrandFather方法...");
}
}
//父类
class Father extends Grandfather {
public int b;
public Father() {
System.out.println("调用父类构造方法...");
b = 20;
}
//虚方法method,final override,reuse slot
public final void method() {
System.out.println("final、Father方法...");
}
}
//子类
class Son extends Father {
int c;
public Son() {
System.out.println("调用子类构造方法...");
c = 30;
}
//编译错误:Son中的method()无法覆盖Father中的method();被覆盖的父方法为final。
//public void method() {
// System.out.println("final、Son方法...");
//}
}
public class Main {
public static void main(String[] args) {
System.out.println("--------实例化一个祖父对象...");
Grandfather grandfatherObj = new Grandfather();
// 动态绑定method方法
grandfatherObj.method();
System.out.println("--------实例化一个父类对象...");
Father fatherObj = new Father();
// 动态绑定method方法
grandfatherObj = fatherObj;
grandfatherObj.method();
//编译器生成invokevirtual字节码方法调用,解释器在运行时,判断出是final override方法,将改为fast_invokevfinal静态绑定。
// 静态绑定final override method方法
fatherObj.method();
System.out.println("--------实例化一个子类对象...");
Son sonObj = new Son();
// 动态绑定method方法
grandfatherObj = sonObj;
grandfatherObj.method();
// 静态绑定final override method方法
fatherObj = sonObj;
fatherObj.method();
// 静态绑定final override method方法
sonObj.method();
}
}
结果:
super— invokespecial
//super调用父类的实例成员
//super调用的方法一定是静态绑定
class A {
public void f(){
System.out.println("A::f()");
}
public void ff(){
f(); //动态绑定
}
}
class B extends A {
public void f(){
super.f(); //静态绑定,否则死循环
System.out.println("B::f()");
}
}
public class Main {
public static void main(String args[])
{
System.out.println("helloWorld");
A a = new B();
a.ff();
}
}
结果: