Java的前期绑定和后期绑定[新]

时间:2023-02-25 23:37:36

前言:本文需要《Java类的实际定义和类资源名称冲突解决》文章的内容作为前导知识。

涉及到继承,可能使得待访问的类资源有多个定义,那么该何时和如何确定具体访问哪个定义呢?这个过程就是绑定过程。绑定分为前期绑定和后期绑定。在编译过程中,所有类资源(一般数据成员,静态数据成员,一般方法,静态方法)的前期绑定一定进行;在运行过程中,一般方法类资源的后期绑定一定进行,但是由“final或者private符号”修饰的一般方法类资源不进行后期绑定。后期绑定的作用结果能够覆盖前期绑定的作用结果。

一、前期绑定

1.1、概念

前期绑定的时机是在编译进行中,具体的策略是:
首先找到欲访问类资源的类实例对应的类的实际定义(或者直接是,欲访问类资源的类的实际定义),然后获取到相应的类资源名称冲突解决辅助数据结构,该层次链数据结构的最下面一层中的类资源定义就是前期绑定要绑定到的对象,选定后进行访问控制判断(当以类形式访问,那么选定后,还需判断待访问类资源是否是静态类资源)。
三点注意:
1)在类似如下的情形中,访问类资源”b”和”f()”的类实例a对应的类是A,而不是B。

class A {
int b;

int f() {
return 5;
}
}

public class B extends A {
int f() {
return 10;
}

public static void main(String[] args) {
A a = new B();

System.out.println(a.b);
System.out.println(a.f());
}
}

2)当以类形式访问,那么选定后,还需判断待访问类资源是否是静态类资源。

class A {
static int a = 10;
}

public class B extends A{
int a = 20;

public static void main(String[] args) {
B b = new B();

System.out.println(b.a);
//编译出错,绑定的a不是静态类资源
System.out.println(B.a);
}
}

根据类B的实际类定义,可以得到类资源名称冲突解决辅助数据结构如图1所示。

图1
Java的前期绑定和后期绑定[新]
针对”b.a”,前期绑定过程中绑定到”类B下的int a=20”;针对”B.a”,前期绑定过程中绑定到”类B下的int a=20”,此时判断得到是“非静态类资源”,编译出错。
3)如果是针对访问数据成员的情形,名称冲突解决辅助数据结构的同一层中不会有两个定义,那么前期绑定不会有歧义性。
如果是针对访问方法的情形,很容易分析得到:
i、同一层次不可能出现两个静态方法,也不可能出现既含有静态方法也含有一般方法,也即当含有静态方法时,会是含有且仅含有该静态方法,那么前期绑定不会有歧义性。
ii、在多个一般方法中“要么都是虚方法,要么是一个具体方法,N个虚方法”。如果是前者,那么随意选择一个虚方法绑定即可,因为覆盖其中一个虚方法的方法必定也同时覆盖其他虚方法,因此此时不管选取哪个虚方法作为绑定对象,最后经过后期绑定得到的结果是一样的。如果是后者,结合“在最下面一层”的限制,那么一定是具体方法覆盖了这些虚方法,否则编译会出错,直接绑定具体方法就好了,如果此时绑定某个虚方法,最后经过后期绑定,结果还会是一样的。[如果只有一个一般方法,或者是虚方法,或者是具体方法,那么也不会有歧义]
示例代码见下面三例子:

class A {
void f() {}
}

interface IA {
void f();
}

/**
* 同一层中,有“一个具体方法,一个虚方法”,如果具体方法不能覆盖该虚方法,编译会出错
*/

public abstract class AA extends A implements IA {
}
abstract class A {
abstract void f();
}

interface IA {
void f();
}

abstract class B extends A implements IA {
}

public class C {
void f(B b) {
// 可以与A下的f()绑定,也可以与IA下的f()绑定
b.f();
}
}
abstract class A {
public void f() {
}
}

interface IA {
void f();
}

abstract class B extends A implements IA {
}

public class C {
void f(B b) {
//与A下的f()绑定
b.f();
}
}

1.2、举例

1、例1
现有如下类继承体系结构:

class A {
int a = 10;
int b = 20;
}

class B extends A {
private int a = 20;
static int b = 30;
}

public class C {
public static void main(String[] args) {
B b = new B();
//欲绑定B下的"private int a",但是通不过访问权限控制
System.out.println(b.a);
// 打印30
System.out.println(b.b);
}
}

根据类B的实际定义,可得到类资源名称冲突解决辅助数据结构如图2和3所示。

图2
Java的前期绑定和后期绑定[新]

图3
Java的前期绑定和后期绑定[新]

针对”b.a”,根据图2,欲绑定到的定义为”private int a=20”,通不过访问权限控制,编译报错。
针对”b.b”,根据图3,欲绑定到的定义为”static int b=30”,绑定成功,最后程序会打印”30”。
2、例2
现有如下类继承体系结构:

abstract class A {
abstract int f();

int g() {
return 10;
}
}

interface IA {
int f();
}

interface IB {
int f();
}

interface IC {
int f();
}

abstract class B extends A implements IA, IB, IC {
int g() {
return 20;
}
}

public class C {
void f(B b) {
b.f();
b.g();
}
}

根据类B的实际定义,可得到类资源名称冲突解决辅助数据结构如图4和5所示。

图4
Java的前期绑定和后期绑定[新]

图5
Java的前期绑定和后期绑定[新]

针对”b.g()”,根据图4,欲绑定到的定义是”abstract class B”下的”int g()”定义。
针对”b.f()”,根据图5,欲绑定到的定义可以是图5所有定义中的任意一个。

二、后期绑定

2.1、概念

后期绑定的时机是在运行进行中,在运行过程中,对于一般方法来说,真正执行的代码定义有可能不是前期绑定过程中绑定到的代码定义(但是如上面所述,由“final或者private符号”修饰的一般方法类资源不进行后期绑定)。
具体策略是(假设,以A代指“欲访问一般方法前期绑定方法”):
1)首先找到类继承体系,直到A所在类为止
2)从下往上,找到类的实际定义,根据类的实际定义,得到类资源名称冲突解决辅助数据结构
3)如果在某个类对应的类资源名称冲突解决辅助数据结构的某一层中含有A的定义,那么最后后期绑定绑定到该层次链数据结构最后一层中的“具体一般方法”的定义(很容易分析得到,在每层中,“具体一般方法”最多只能有一个),最终执行的也是该“具体一般方法”定义对应的代码片段
一点注意:
1)在如下代码体系中,访问f()方法的实例a对应的是内围父实例,相应的类继承体系如图6所示。

class A {
void f() {
}
}

class B extends A {
void f() {
}
}

class C extends B {
void f() {}
}

public class D extends C {
void f() {
}

public static void main(String[] args) {
A a = new D();
a.f();
}
}

图6
Java的前期绑定和后期绑定[新]

2.2、举例

1、例1
现在有如下代码片段:

class A {
public void f() {
System.out.println("A");
}
}

interface IA {
void f();
}

interface IIA {
void f();
}

public class B extends A implements IA, IIA {
public static void main(String[] args) {
IA ia = new B();
ia.f();
IIA iia = new B();
iia.f();
}
}

1)首先找到类继承体系,分别如图7和图8所示。在前期绑定过程中,ia.f()方法绑定到”类IA下的void f()”,iia.f()方法绑定到”类IIA下的void f()”

图7
Java的前期绑定和后期绑定[新]

图8
Java的前期绑定和后期绑定[新]

2)从下往上,找到类B的实际定义,得到关于f()方法的类资源名称冲突解决辅助数据结构如图9所示

图9
Java的前期绑定和后期绑定[新]
3)在以上层次链数据结构中,分别可以找到”类IA下的void f()”和”类IIA下的void f()”定义,该层中的“具体一般方法”为“类A下的public void f()”定义,因而后期绑定最后绑定到”类A下的public void f()”
4)执行上述代码,最后得到如图10所示结果

图10
Java的前期绑定和后期绑定[新]

2、例2
现在有如下代码片段:

class A {
void f() {
System.out.println("A");
}
}

class B extends A {
void f() {
System.out.println("B");
}
}

class C extends B {
void f() {
System.out.println("C");
}
}

public class D extends C {
void f() {
System.out.println("D");
}

public static void main(String[] args) {
A a = new D();
a.f();
}
}

1)首先找到类继承体系,如图11所示。在前期绑定过程中,a.f()方法绑定到”类A下的void f()”

图11
Java的前期绑定和后期绑定[新]
2)从下往上,找到类D的实际定义,得到关于f()方法的类资源名称冲突解决辅助数据结构如图12所示

图12
Java的前期绑定和后期绑定[新]

3)在以上层次链数据结构中,可以找到”类A下的void f()”定义,因而后期绑定最后绑定到“类D下的void f()”定义
4)执行上述代码,最后得到如图13所示结果

图13
Java的前期绑定和后期绑定[新]

3、例3
现在有如下代码片段:

package com.dslztx.package1;

import com.dslztx.package2.C;

public class A {
void f() {
System.out.println("A");
}

public static void main(String[] args) {
A a = new C();
a.f();
}
}
package com.dslztx.package1;

public class B extends A {
void f() {
System.out.println("B");
}
}
package com.dslztx.package2;

import com.dslztx.package1.B;

public class C extends B {
public void f() {
System.out.println("C");
}
}

1)首先得到类继承体系,如图14所示。在前期绑定过程中,a.f()方法绑定到”类A下的void f()”

图14
Java的前期绑定和后期绑定[新]

2)从下往上,找到类C的实际定义,得到关于f()方法的类资源名称冲突解决辅助数据结构如图15所示

图15
Java的前期绑定和后期绑定[新]

3)在以上层次链数据结构中,未找到”类A下的void f()”定义,接着找到类B的实际定义,得到关于f()方法的类资源名称冲突解决辅助数据结构如图16所示

图16
Java的前期绑定和后期绑定[新]

4)在以上层次链数据结构中,找到”类A下的void f()”定义,因而后期绑定最后绑定到”类B下的void f()”定义
5)执行上述代码,最后得到如图17所示结果

图17
Java的前期绑定和后期绑定[新]

三、后期绑定扩展

后期绑定就是我们常说的多态行为。后期绑定在运行过程中进行,此时不会进行访问控制判断,访问控制判断在编译过程中进行,这也是以下现象产生的原因:某个方法本不能被访问,但是通过多态机制,使得该方法可被访问,这对于访问控制来说是个漏洞,具体要结合”protected”修饰符来达到上述目的。

package package1;

public class A {
protected void f() {
System.out.println("Hello");
}
}
package package2;

import package1.A;

public class B extends A {
protected void f() {
System.out.println("World");
}
}
package package1;

import package2.B;

public class C {
public static void print(A a) {
a.f();
}

public static void main(String[] args) {
B b = new B();
//不能直接访问该方法
// b.f();

//通过多态访问了本不能访问的方法
print(b);
}
}

最后的输出结果如图18所示。

图18
Java的前期绑定和后期绑定[新]