Java的编译不包含传统编译过程的连接阶段,连接是在class加载到JVM中才进行的。Java代码在编译成class文件后,里面所有的方法调用都替换成了符号引用,这些符号引用需要在解析阶段解析成实际的内存地址,也就是直接引用。
JVM提供了5条调用方法的字节码指令:
- invokestatic: 调用静态方法
- invokespecial: 调用构造器方法、私有方法
- invokevirtual : 调用所有的虚方法
- invokeinterface: 调用接口方法
- invokedynamic :动态方法调用
重载
看一个简单的代码示例:
public class OverloadTest {
void test(int a) {
}
void test(String b) {
}
public static void main(String[] args) {
OverloadTest o = new OverloadTest();
o.test(1);
o.test("s");
}
}
反编译:
$ javap -c OverloadTest
警告: 二进制文件OverloadTest包含test.method.OverloadTest
Compiled from "OverloadTest.java"
public class test.method.OverloadTest {
public test.method.OverloadTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
void test(int);
Code:
0: return
void test(java.lang.String);
Code:
0: return
public static void main(java.lang.String[]);
Code:
0: new #1 // class test/method/OverloadTest
3: dup
4: invokespecial #23 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: invokevirtual #24 // Method test:(I)V
13: aload_1
14: ldc #26 // String s
16: invokevirtual #28 // Method test:(Ljava/lang/String;)V
19: return
}
可以发现,在编译完成时,重载方法的两次调用分别是:
invokevirtual #24 // Method test:(I)V
invokevirtual #28 // Method test:(Ljava/lang/String;)V
也就是方法版本(签名)已经确定 .
再一个涉及继承的重载示例:
public class OverloadTest2 {
static class Super {
}
static class Sub extends Super {
}
static void test(Super s) {
System.out.println("super");
}
static void test(Sub s) {
System.out.println("sub");
}
public static void main(String[] args) {
Super s = new Super();
test(s);
s = new Sub();
test(s);
Sub sub = new Sub();
test(sub);
}
}
反编译:
$ javap -c OverloadTest2
警告: 二进制文件OverloadTest2包含test.method.OverloadTest2
Compiled from "OverloadTest2.java"
public class test.method.OverloadTest2 {
public test.method.OverloadTest2();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
static void test(test.method.OverloadTest2$Super);
Code:
0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22 // String super
5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
static void test(test.method.OverloadTest2$Sub);
Code:
0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #33 // String sub
5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: new #38 // class test/method/OverloadTest2$Super
3: dup
4: invokespecial #40 // Method test/method/OverloadTest2$Super."<init>":()V
7: astore_1
8: aload_1
9: invokestatic #41 // Method test:(Ltest/method/OverloadTest2$Super;)V
12: new #43 // class test/method/OverloadTest2$Sub
15: dup
16: invokespecial #45 // Method test/method/OverloadTest2$Sub."<init>":()V
19: astore_1
20: aload_1
21: invokestatic #41 // Method test:(Ltest/method/OverloadTest2$Super;)V
24: new #43 // class test/method/OverloadTest2$Sub
27: dup
28: invokespecial #45 // Method test/method/OverloadTest2$Sub."<init>":()V
31: astore_2
32: aload_2
33: invokestatic #46 // Method test:(Ltest/method/OverloadTest2$Sub;)V
36: return
}
对应关系:
Super s = new Super();
test(s); //9: invokestatic #41 // Method test:(Ltest/method/OverloadTest2$Super;)V
s = new Sub();
test(s); //21: invokestatic #41 // Method test:(Ltest/method/OverloadTest2$Super;)V
Sub sub = new Sub();
test(sub); //33: invokestatic #46 // Method test:(Ltest/method/OverloadTest2$Sub;)V
和上面的一样,在编译期确定了方法的版本。可以看出,Java在实现重载时,是使用静态选择的,为什么这么说呢,因为在编译期,要调用哪个重载方法已经确定下来了(看字节码就知道已经定下来了,为什么要在这个时候定下来,设计如此),另外在选择方法时,是依据对象的声明类型来绑定方法的,实际上为了在编译期确定方法,也只能通过对象的声明类型即静态类型,因为动态类型编译器也不知道啊。
重写
public class OverrideTest {
static class Super {
void test() {
System.out.println("super method");
}
}
static class Sub extends Super {
void test() {
System.out.println("sub method");
}
}
public static void main(String[] args) {
Super s = new Super();
s.test(); //super method
s = new Sub();
s.test(); // sub method
}
}
反编译:
$ javap -c OverrideTest
警告: 二进制文件OverrideTest包含test.method.OverrideTest
Compiled from "OverrideTest.java"
public class test.method.OverrideTest {
public test.method.OverrideTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #16 // class test/method/OverrideTest$Super
3: dup
4: invokespecial #18 // Method test/method/OverrideTest$Super."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #19 // Method test/method/OverrideTest$Super.test:()V
12: new #22 // class test/method/OverrideTest$Sub
15: dup
16: invokespecial #24 // Method test/method/OverrideTest$Sub."<init>":()V
19: astore_1
20: aload_1
21: invokevirtual #19 // Method test/method/OverrideTest$Super.test:()V
24: return
}
源码和字节码指令对应:
Super s = new Super();
s.test(); //9: invokevirtual #19 // Method test/method/OverrideTest$Super.test:()V
s = new Sub();//21: invokevirtual #19 // Method test/method/OverrideTest$Super.test:()V
s.test();
和重载一样,对应方法的签名在编译期也是确定的,当然是根据静态类型来确定的。那么最后怎么就进入了子类重写的方法呢?
这就要看一下invokevirtual指令的逻辑了:
If the resolved method is not signature polymorphic (§2.9), then the invokevirtual instruction proceeds as follows.
Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:
• If C contains a declaration for an instance method m that overrides (§5.4.5) the resolved method, then m is the method to be invoked, and the lookup procedure terminates.
• Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of
this lookup procedure.
• Otherwise, an AbstractMethodError is raised.
(未涉及同步和native方法的lookup逻辑)
也就是先在本类中找此签名的方法,没找到就去父类递归寻找,如果一直找不到,就抛异常。
所以方法签名在编译期确定,具体调用子类还是父类的方法,得看接受者的实际类型,得在运行期确定。
重写的注意点
- 子类方法的访问权限不得小于父类
- 子类抛出的异常不能比父类抽象
这两个限制是为了兼容里式替换原则(Liskov Substitution Principle LSP)。
里式替换原则
任何基类可以出现的地方,子类一定可以出现。
- 子类不能重写父类私有和静态方法 -> 可以在子类写,但是不是重写的效果,因为是私有的(实例私有和类私有,方法的确认过程上面已经说的很清楚了)
class Super {
static void a() {
}
final private void run() {
System.out.println("super");
}
public void b() {
}
public void c() throws IOException {
}
}
class Sub extends Super {
// 不是重写,是Class持有的
static void a() {
}
// 不是重写,是自己私有的
final private void run() {
System.out.println("sub");
}
// void b(){ 错误,不能比父类的访问权限低
//
// }
// 不能比父类方法抛出的异常范围更大
// public void c()throws Exception{
//
// }
}
网友评论