一、重载
Method Overload Resolution,也称Dispath,国内一般翻译为“静态分派”,但是严格来说,一般很少用再静态环境中。
1、静态类方法中的演示
package jdbc.test.es;
/**
* 方法静态分派演示
* @author Administrator
*
*/
public class MyStaticTest {
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHell(Human guy) {
System.out.println("hello guy!");
}
public void sayHell(Man guy) {
System.out.println("hello man!");
}
public void sayHell(Woman guy) {
System.out.println("hello lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
MyStaticTest sr = new MyStaticTest();
sr.sayHell(man);
sr.sayHell(woman);
// MyStaticTest sr = new MyStaticTest();
// Human man = new Man();
// Human woman = new Woman();
// sr.sayHell((Man)man);
// sr.sayHell((Woman)woman);
}
}
main函数的数据结果:
hello guy!
hello guy!
但是把main函数中的内容换成注释的内容则结果如下:
hello man!
hello lady!
2、非静态类方法中的演示
package jdbc.test.es;
public abstract class Human1 {
public Human1() {
// TODO Auto-generated constructor stub
}
}
package jdbc.test.es;
public class Man1 extends Human1 {
public Man1() {
// TODO Auto-generated constructor stub
}
}
package jdbc.test.es;
public class Woman1 extends Human1 {
public Woman1() {
// TODO Auto-generated constructor stub
}
}
package jdbc.test.es;
import org.junit.Test;
public class MyTest1 {
public void sayHell(Human1 guy) {
System.out.println("hello guy!");
}
public void sayHell(Man1 guy) {
System.out.println("hello man!");
}
public void sayHell(Woman1 guy) {
System.out.println("hello lady!");
}
@Test
public void test1(){
Human1 man = new Man1();
Human1 woman = new Woman1();
MyTest1 sr = new MyTest1();
sr.sayHell(man);
sr.sayHell(woman);
}
}
执行结果如下:
hello guy!
hello guy!
同样把test1()的内容换成
Human1 man = new Man1();
Human1 woman = new Woman1();
MyTest1 sr = new MyTest1();
sr.sayHell((Man1)man);
sr.sayHell((Woman1)woman);
执行结果如下:
hello man!
hello lady!
3、解释原理
可能在面试的时候,包括面试官在内,不一定知道原理,只是照本宣科的问问重载如何来区别的,比如参数个数,参数类型,以及能不能用会返回值来区别等。这些都是比较浅层次的东西,如果你能把内部原理的实现说出来,无疑就会脱颖而出。
其实对java虚拟机有一些了解的人都知道,在类的加载中,有个比较有名的模型,叫做双亲委派模型,类加载器是通过复合的方式而非继承来灵活实现双亲委派的,首先类加载器会递归到最上层的启动类加载器(Bootstrap ClassLoader)开始,在逐层来实现类的初始化(如果在某层能实现类的初始化,就直接返回结果,不行就逐层往下,直到成功为止)。
再有些了解的人都知道,虚拟机加载的过程中,类/接口,以及类常量都是等都是在编译期间已经完成了,重载实际上不是由虚拟机来执行的。静态方法、私有方法、实例构造器、父类方法(统称非虚方法),他们在类加载的时候就会把符号引用解析为该方法的直接引用(也包括final修饰的方法,虽然final方法是invokevirtural修饰的),所以可以看到没有强制转换类型的就会默认为编译期间的Human类,所以输入man和women其实调用的都是human匹配的方法。这符合“编译期可知,运行期不可变”的要求。编译为字节码的实现是调用invokestatic,invokespecial指令来实现的(感兴趣的可以用javac之后javap来查看详细信息)。
一、重写
1、代码演示
package jdbc.test.es;
/**
* 方法静态分派演示
* @author Administrator
*
*/
public class MyStaticTest {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello() {
// TODO Auto-generated method stub
System.out.println("hi man");
}
}
static class Woman extends Human{
@Override
protected void sayHello() {
// TODO Auto-generated method stub
System.out.println("hi lady");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
}
}
执行结果如下:
hi man
hi lady
2、原理剖析
javac MyStaticTest.java
javap -verbose MyStaticTest
通过javac编译之后,用javap查看详细信息如下:
Classfile /F:/Dev/myWorkSpace/jdbc.test.es/src/test/java/jdbc/test/es/MyStaticTest.class
Last modified 2017-11-21; size 535 bytes
MD5 checksum ecee8b49f9300fcbbb9c27aa2428e8a2
Compiled from "MyStaticTest.java"
public class jdbc.test.es.MyStaticTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // jdbc/test/es/MyStaticTest$Man
#3 = Methodref #2.#22 // jdbc/test/es/MyStaticTest$Man."<init>":()V
#4 = Class #24 // jdbc/test/es/MyStaticTest$Woman
#5 = Methodref #4.#22 // jdbc/test/es/MyStaticTest$Woman."<init>":()V
#6 = Methodref #12.#25 // jdbc/test/es/MyStaticTest$Human.sayHello:()V
#7 = Class #26 // jdbc/test/es/MyStaticTest
#8 = Class #27 // java/lang/Object
#9 = Utf8 Woman
#10 = Utf8 InnerClasses
#11 = Utf8 Man
#12 = Class #28 // jdbc/test/es/MyStaticTest$Human
#13 = Utf8 Human
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 SourceFile
#21 = Utf8 MyStaticTest.java
#22 = NameAndType #14:#15 // "<init>":()V
#23 = Utf8 jdbc/test/es/MyStaticTest$Man
#24 = Utf8 jdbc/test/es/MyStaticTest$Woman
#25 = NameAndType #29:#15 // sayHello:()V
#26 = Utf8 jdbc/test/es/MyStaticTest
#27 = Utf8 java/lang/Object
#28 = Utf8 jdbc/test/es/MyStaticTest$Human
#29 = Utf8 sayHello
{
public jdbc.test.es.MyStaticTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
line 23: 4
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class jdbc/test/es/MyStaticTest$Man
3: dup
4: invokespecial #3 // Method jdbc/test/es/MyStaticTest$Man."<init>":()V
7: astore_1
8: new #4 // class jdbc/test/es/MyStaticTest$Woman
11: dup
12: invokespecial #5 // Method jdbc/test/es/MyStaticTest$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method jdbc/test/es/MyStaticTest$Human.sayHello:()V
20: aload_2
21: invokevirtual #6 // Method jdbc/test/es/MyStaticTest$Human.sayHello:()V
24: return
LineNumberTable:
line 34: 0
line 35: 8
line 36: 16
line 37: 20
line 39: 24
}
SourceFile: "MyStaticTest.java"
InnerClasses:
static #9= #4 of #7; //Woman=class jdbc/test/es/MyStaticTest$Woman of class jdbc/test/es/MyStaticTest
static #11= #2 of #7; //Man=class jdbc/test/es/MyStaticTest$Man of class jdbc/test/es/MyStaticTest
static abstract #13= #12 of #7; //Human=class jdbc/test/es/MyStaticTest$Human of class jdbc/test/es/MyStaticTest
可以看到在mian方法调用的的实现上用的是invokevirtual 指令,而这个指令的含义就是在运行期间确定接收者的实际类型,所以两次实际调用会把常量池中的类方法应用解析到了不同的直接应用上,这个过程就是java重写的本质。
这也就是java开发人员熟悉的重写。
当然,重载的编译字节码文件,我没有详细拿出来分析,感兴趣也可以javac之后用javap查看详细信息