JVM从虚拟机层面了解重载和重写

       本文是读周志明先生著的《深入理解Java虚拟机》一书有感后写的一篇读书笔记。毕竟好记性不如烂笔头,如果想要更多了解相关内容建议去读此书。  言归正传,众所周知,面向对象的三个基本特征是继承,封装,多态,本文将介绍在Java虚拟机中如何实现的“重载”与“重写”

重载

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同,重载方法都必须是独一无二的,最常见的是类构造器重载,重载规则如下

  • 被重载的方法必须改变参数列表(参数个数或类型不一样)
  • 被重载的方法可以改变返回类型
  • 被重载的方法可以改变访问修饰符
  • 被重载的方法可以声明新的或更广的检查异常
  • 方法能够在同一个类中或者在一个子类中被重载
  • 无法以返回值类型作为重载函数的区分标准

看下面的一个实例 ,《深入理解Java虚拟机》书中提到几个概念

“Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),静态类型是在编译器可知的,

“Man”则称为变量的实际类型(Actual Type),因为编译程序的时候不知道对象的实际类型是什么,因此实际类型变化的结果在运行期可知,

public class StaticDispatch {

    static abstract class Human {
    }

    static class Man extends Human {
    }

    static class Woman extends Human {
    }

    public void sayHello(Human guy) {
        System.out.println("hello,guy!");
    }

    public void sayHello(Man guy) {
        System.out.println("hello,gentleman!");
    }

    public void sayHello(Woman guy) {
        System.out.println("hello,lady!");
    }

    public static void main(String[] args) {
        //概念Human静态类型或外观类型  , Man 实际类型
        //静态类型编译器可知, 实际类型是运行期才可知
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);
        sr.sayHello(woman);

        //实际类型变化
        Human man2 = new Man();
        Human woman2 = new Woman();
        sr.sayHello((Man) man2);
        sr.sayHello((Woman) woman2);
    }

}

运行结果

结果为白色字体,圈选后才能看到

hello,guy!
hello,guy!
hello,gentleman!
hello,lady!

 

扫描二维码关注公众号,回复: 13594090 查看本文章

上面的示例主要是为了测试使用sayHello重载,使用哪个版本重载完全取决于参数的数量和参数的数据类型,重载是通过参数的静态类型作为判断依据。编译阶段, 编译器就会根据参数的静态类型判断使用的重载版本。

所有依赖静态类型来决定方法版本的分派叫做静态分派,静态分派发生在编译期,很多情况下重载不是惟一的,编译器会按照一定规则找到更加适合的版本,

1.运行的时候因为参数是char类型 a ,那么执行结果是hello char:a

public class Overload {

    public static void sayHello(Object arg) {
        System.out.println("hello object;"+arg);
    }

    public static void sayHello(int arg) {
        System.out.println("hello int:"+arg);
    }

    public static void sayHello(char arg) {
        System.out.println("hello char:"+arg);
    }

    public static void main(String[] args) {
        sayHello('a');
    }
}

2. 注释掉最匹配的char方法,执行结果是hello int:97 ,因为'‘a’ 处除了表示一个字符串还可以代表‘a’的Unicode编码 97

public class Overload {

    public static void sayHello(Object arg) {
        System.out.println("hello object;"+arg);
    }

    public static void sayHello(int arg) {
        System.out.println("hello int:"+arg);
    }
    
    /*
    public static void sayHello(char arg) {
        System.out.println("hello char:"+arg);
    }
    */

    public static void main(String[] args) {
        sayHello('a');
    }
}

3. 注释掉char和int 的方法, 执行结果是hello object;a ,因为char装箱后转型为父类,查找是从下往上找找到了 Object

public class Overload {

    public static void sayHello(Object arg) {
        System.out.println("hello object;"+arg);
    }
    /*
    public static void sayHello(int arg) {
        System.out.println("hello int:"+arg);
    }


    public static void sayHello(char arg) {
        System.out.println("hello char:"+arg);
    }
    */

    public static void main(String[] args) {
        sayHello('a');
    }
}

重写

重写(Override)是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变,重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法

public class DynamicDispatch {

    static abstract class Human {
        protected abstract void sayHello();
    }

    static class Man extends Human {
        @Override
        protected void sayHello() {
            System.out.println("man say hello");
        }
    }

    static class Woman extends Human {
        @Override
        protected void sayHello() {
            System.out.println("woman say hello");
        }
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();

        man = new Woman();
        man.sayHello();

    }
}

执行结果

man say hello
woman say hello
woman say hello

虚拟机是如何知道调用那个方法的呢,看看类的字节码

C:\Windows\system32>d:

D:\>javac DynamicDispatch.java

D:\>javap -c DynamicDispatch
Compiled from "DynamicDispatch.java"
public class com.jvm.gc.DynamicDispatch extends java.lang.Object{
public com.jvm.gc.DynamicDispatch();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new     #2; //class com/jvm/gc/DynamicDispatch$Man
   3:   dup
   4:   invokespecial   #3; //Method com/jvm/gc/DynamicDispatch$Man."<init>":()V
   7:   astore_1
   8:   new     #4; //class com/jvm/gc/DynamicDispatch$Woman
   11:  dup
   12:  invokespecial   #5; //Method com/jvm/gc/DynamicDispatch$Woman."<init>":()V
   15:  astore_2
   16:  aload_1
   17:  invokevirtual   #6; //Method com/jvm/gc/DynamicDispatch$Human.sayHello:()V
   20:  aload_2
   21:  invokevirtual   #6; //Method com/jvm/gc/DynamicDispatch$Human.sayHello:()V
   24:  new     #4; //class com/jvm/gc/DynamicDispatch$Woman
   27:  dup
   28:  invokespecial   #5; //Method com/jvm/gc/DynamicDispatch$Woman."<init>":()V
   31:  astore_1
   32:  aload_1
   33:  invokevirtual   #6; //Method com/jvm/gc/DynamicDispatch$Human.sayHello:()V
   36:  return

}

调用方法17行,21行,33行都是使用invokevirtual 指令实现invokevirtual指令的运行时解析过程大致分为以下几个步骤:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回
java.lang.IllegalAccessError异常。

3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。

4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

总结

面试题:重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分? 

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分

 上一篇:JVM 从虚拟机类加载看java.lang.NoSuchMethodError

猜你喜欢

转载自blog.csdn.net/Beijing_L/article/details/120420751