今天看到think in java的重载部分,大家都知道java的重载是一个方法的方法名称不变,参数类型,参数数量不同(如果这些相同,返回值类型不同,是不同通过javac编译的),但是java重载仅仅只有这些吗?
这里涉及到2个概念:
1、静态分派
2、动态分派
那么,什么是静态分派和动态分派呢?我们来看一个例子(暂时不涉及动态分派的概念),相信大家就都会明白了
package com.zx.exception;
public class Overload {
static class Human{}
static class Man extends Human{}
static class Women extends Human{}
public void hello(Human h){
System.out.println("human");
}
public void hello(Man man){
System.out.println("man");
}
public void hello(Women woman){
System.out.println("woman");
}
public static void main(String[] args) {
Overload l=new Overload();
Human man=new Man();
Human women=new Women();
l.hello(man);
l.hello(women);
System.out.println("=======================");
}
}
我们看到在overload类中有三个重载版本的hello方法,只是参数类型不同,在main方法中我们实例化了Human的两个子类Man以及Woman,执行的结果如下:
human
human
=======================
相信大家都能一眼看出结果,执行结果是两个human,对重载有一定认识的朋友都会知道为什么,这就是我要重点讲解的部分。
java重载是基于静态分派技术实现的,说的通俗一点就是java编译器在编译阶段就会确定方法调用的参数类型,
在main中 声明了这两个对象,Human man=new Man(); Human women=new Women();,对于编译器来说,我们认为它没有那么聪明的知道man是一个Man的实例,women是一个Woman的实例,实际上这些是在运行时才能确定的,所以当你调用l.hell(man) , l.hello(women)的时候,java编译器都会选择
public void hello(Human h){
System.out.println("human");
}
这个hello方法的重载版本,如果你还有疑惑的话,那我们用javap -c com.zx.exception.Overload 来看一下实际编译的字节码是什么:
D:\java\thinkinjava\bin>javap -c com.zx.exception.Overload
Compiled from "Overload.java"
public class com.zx.exception.Overload extends java.lang.Object{
public com.zx.exception.Overload();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public void hello(com.zx.exception.Overload$Human);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22; //String human
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void hello(com.zx.exception.Overload$Man);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #33; //String man
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void hello(com.zx.exception.Overload$Women);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #37; //String woman
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: new #1; //class com/zx/exception/Overload
3: dup
4: invokespecial #42; //Method "<init>":()V
7: astore_1
8: new #43; //class com/zx/exception/Overload$Man
11: dup
12: invokespecial #45; //Method com/zx/exception/Overload$Man."<init>":()V
15: astore_2
16: new #46; //class com/zx/exception/Overload$Women
19: dup
20: invokespecial #48; //Method com/zx/exception/Overload$Women."<init>":()V
23: astore_3
24: aload_1
25: aload_2
26: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
29: aload_1
30: aload_3
31: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
34: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #51; //String =======================
39: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
}
首先来看一下三个hello重载方法的字节码:
hello(Huma)的字节码为:
public void hello(com.zx.exception.Overload$Human);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22; //String human
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
4条指令,getstatic获取System.out的常量池引用,ldc将常量池中索引为0xF6的项"human"加载到当前栈的栈顶,
invokevirtual指令调用System.out的println方法,参数为当前栈顶的值,最后返回。
第二个重载方法
hello(Overload$Man)的字节码为:
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #33; //String man
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
方法执行同上
第三个重载方法
hello(Overload$Man)的字节码为:
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #33; //String man
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
方法执行同上
看完了这三个方法后,回到main中去:
public static void main(java.lang.String[]);
Code:
0: new #1; //class com/zx/exception/Overload
3: dup
4: invokespecial #42; //Method "<init>":()V
7: astore_1
8: new #43; //class com/zx/exception/Overload$Man
11: dup
12: invokespecial #45; //Method com/zx/exception/Overload$Man."<init>":()V
15: astore_2
16: new #46; //class com/zx/exception/Overload$Women
19: dup
20: invokespecial #48; //Method com/zx/exception/Overload$Women."<init>":()V
23: astore_3
24: aload_1
25: aload_2
26: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
29: aload_1
30: aload_3
31: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
34: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #51; //String =======================
39: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
执行的字节码很简单,就是new 创建三个对象的实例,dup复制这三个实例并推到当前栈顶,astore将实例变量的保存到当前栈的局部变量表中去,关键的地方看26行和31行,我们分别传入了一个Human的实例man和women,然而字节码却是这样的:
26: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
31: invokevirtual #49; //Method hello:(Lcom/zx/exception/Overload$Human;)V
这下真相大白于天下了,调用的是Overload类的hello(Human )版本的重载方法
总结:java的重载是基于静态分派实现,也就是说对于某一个类C中方法A的若干个重载版本(A1....AN),C实例的方法A被调用时,java编译器在编译阶段就已经确定了调用者会调用的某一个确定的A版本。这和java的重写Overrite恰恰相反,下一章我会详细讲解Overrite的原理。