首先从定义上说说重载和重写:
- 方法重载
当我们发现在处理同一个功能或者业务的时候,有时候需要不同的参数,我们可以使用方法重载;当我们在修改以前的代码、功能扩展的时候,
可以使用方法重载。重载特征:同一个类中,两个或者两个以上的方法:
a、方法名字相同
b、方法签名不同(签名:方法参数的类型、顺序、个数,三个有一个不同,就代表签名不同)
c、与方法返回值没有关系
package com.wb.dispatch; public class OverLoadTest { public OverLoadTest() { } public void sayHello() { System.out.println("我是无参的!"); } public void sayHello(String arg) { System.out.println("我是有一个参数的:" + arg); } public void sayHello(String arg0, int arg1) { System.out.println("我是有2个参数的:" + arg0 + "和" + arg1); } public void sayHello(int arg0, String arg1) { System.out.println("我是有2个参数的:" + arg0 + "和" + arg1); } }
- 方法重写
类a继承类b,当b类中的某一个方法c不能满足类a的需求的时候,需要在类a中对类b的方法c的方法体进行重新实现。
重写又叫覆写,是以继承为基础的:
1、子类重写父类(基类)的方法
2、子类的方法与父类的方法名字、方法签名都要一样
3、子类的方法与父类的方法返回值也要一样
4、子类的访问修饰符一样要大于等于父类的访问修饰符(public>protected>default>private)5、子类不能抛出新的异常或者比父类声明更宽泛的异常4、请增加@Override注解
public class Animal { void sayHello(String arg) { System.out.println(arg); } protected String sayHello(String arg0, String arg1) { return arg0 + arg1; } public void sayHello(int arg0){ System.out.println(arg0); } }
public class OverrideTest extends Animal { /** * 重写了父类方法,修饰符从default扩展到了public * @param arg */ @Override public void sayHello(String arg) { super.sayHello(arg); } /** * 重写了父类方法,修饰符从protected扩展到了public * @param arg0 * @param arg1 * @return */ @Override public String sayHello(String arg0, String arg1) { return super.sayHello(arg0, arg1); } /** * 重写父类方法 * @param arg0 */ @Override public void sayHello(int arg0) { super.sayHello(arg0); } }
在了解基本定义是使用后,我们冲jvm层面来聊聊重载和重写1.重载在方法重载时,当需要通过参数类确定调用方法版本的时候,需要看参数的静态类型,而不是实际类型
java代码首先是通过编译器编译成class的字节码,然后虚拟机加载运行字节码。编译器在编译方法的
重载时候,是通过参数的静态类型确定方法版本的,静态类型在编译期就确定。package com.wb.dispatch; /** * 在方法重载时,当需要通过参数类确定调用方法版本的时候,需要看参数的静态类型,而不是实际类型 * java代码首先是通过编译器编译成class的字节码,然后虚拟机加载运行字节码。编译器在编译方法的 * 重载时候,是通过参数的静态类型确定方法版本的,静态类型在编译期就确定。 * @author wb * */ public class AnimalOverLoad { static class Animal { } static class Dog extends Animal { } static class Cat extends Animal { } public void sayHello(Animal arg) { System.out.println("hello,animal"); } public String sayHello(Dog arg) { System.out.println("hello,dog"); return ""; } public Integer sayHello(Cat arg) { System.out.println("hello,cat"); return 0; } public static void main(String[] args) { Animal animal = new Animal();//静态类型:Animal,实际类型Animal Animal dogAnimal = new Dog();//静态类型:Animal,实际类型Dog Animal catAnimal = new Cat();//静态类型:Animal,实际类型Cat Dog dog = new Dog();//静态类型:Dog,实际类型Dog Cat cat = new Cat();//静态类型:Cat,实际类型Cat AnimalOverLoad overLoad = new AnimalOverLoad(); overLoad.sayHello(animal);// hello,animal overLoad.sayHello(dogAnimal);// hello,animal overLoad.sayHello(catAnimal);// hello,animal overLoad.sayHello(dog);// hello,dog overLoad.sayHello(cat);// hello,cat } }
首先通过javac命令编译AnimalOverLoad,然后使用javap命令查看编译后的字节码javac:E:\workspace\JavaVm\src\com\wb\dispatch>javac AnimalOverLoad.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=GBK E:\workspace\JavaVm\src\com\wb\dispatch>
javap:javap -verbose AnimalOverLoad.classpublic static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=7, args_size=1 0: new #9 // class com/wb/dispatch/AnimalOve rLoad$Animal 3: dup 4: invokespecial #10 // Method com/wb/dispatch/AnimalOv erLoad$Animal."<init>":()V 7: astore_1 8: new #11 // class com/wb/dispatch/AnimalOve rLoad$Dog 11: dup 12: invokespecial #12 // Method com/wb/dispatch/AnimalOv erLoad$Dog."<init>":()V 15: astore_2 16: new #13 // class com/wb/dispatch/AnimalOve rLoad$Cat 19: dup 20: invokespecial #14 // Method com/wb/dispatch/AnimalOv erLoad$Cat."<init>":()V 23: astore_3 24: new #11 // class com/wb/dispatch/AnimalOve rLoad$Dog 27: dup 28: invokespecial #12 // Method com/wb/dispatch/AnimalOv erLoad$Dog."<init>":()V 31: astore 4 33: new #13 // class com/wb/dispatch/AnimalOve rLoad$Cat 36: dup 37: invokespecial #14 // Method com/wb/dispatch/AnimalOv erLoad$Cat."<init>":()V 40: astore 5 42: new #15 // class com/wb/dispatch/AnimalOve rLoad 45: dup 46: invokespecial #16 // Method "<init>":()V 49: astore 6 51: aload 6 53: aload_1 54: invokevirtual #17 // Method sayHello:(Lcom/wb/dispat ch/AnimalOverLoad$Animal;)V 57: aload 6 59: aload_2 60: invokevirtual #17 // Method sayHello:(Lcom/wb/dispat ch/AnimalOverLoad$Animal;)V 63: aload 6 65: aload_3 66: invokevirtual #17 // Method sayHello:(Lcom/wb/dispat ch/AnimalOverLoad$Animal;)V 69: aload 6 71: aload 4 73: invokevirtual #18 // Method sayHello:(Lcom/wb/dispat ch/AnimalOverLoad$Dog;)Ljava/lang/String; 76: pop 77: aload 6 79: aload 5 81: invokevirtual #19 // Method sayHello:(Lcom/wb/dispat ch/AnimalOverLoad$Cat;)Ljava/lang/Integer; 84: pop 85: return LineNumberTable: line 36: 0 line 37: 8 line 38: 16 line 40: 24 line 41: 33 line 43: 42 line 44: 51 line 45: 57 line 46: 63 line 47: 69 line 48: 77 line 49: 85
LineNumberTable下有line属性,两个数字说明一下:前面一个是java代码的行号,后面一个字节码行号,这里把java代码行号贴了一下,方便对比:
记住关键点:
在方法重载时,当需要通过参数类确定调用方法版本的时候,需要看参数的静态类型,而不是实际类型 java代码首先是通过编译器编译成class的字节码,然后虚拟机加载运行字节码。编译器在编译方法的 重载时候,是通过参数的静态类型确定方法版本的,静态类型在编译期就确定。
2.重写
/** *方法的重写: *1、方法的调用者是根据实际类型动态确定的 *2、方法版本是根据操作数栈顶元素自下往上找,意思就是优先执行子类方法,子类没有,从父类找,如果都没有找到 * */ public class Animal { static class Cat { } public static class Father { public void sayHello(Cat arg) { System.out.println("father is cat"); } public void eat(Cat arg) { System.out.println("father eat cat"); } } public static class Son extends Father { public void sayHello(Cat arg) { System.out.println("son is cat"); } } public static void main(String[] args){ Father father=new Father();//动态类型father Father son=new Son();//动态类型son father.sayHello(new Cat());//father is cat,动态类型father,直接在Father类中找sayHello方法 son.sayHello(new Cat());//son is cat,动态类型是Son,在Son中找sayHello方法 father.eat(new Cat());//father eat cat,动态类型是Father,直接在Father类中找eat方法 son.eat(new Cat());//father eat cat,动态类型是Son,在Son中找eat方法,没有找到,再从Father中找, } }
javac编译成class文件:E:\workspace\JavaVm\src\com\wb\dispatch>javac Animal.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=GBK E:\workspace\JavaVm\src\com\wb\dispatch>
javap 查看class文件注意关键:方法的重写:
*1、方法的调用者是根据实际类型动态确定的
*2、方法版本是根据操作数栈顶元素自下往上找,意思就是优先执行子类方法,子类没有,从父类找,如果都没有找到