jvm关于方法调用的问题
仅作为作者学习笔记
前言
仅作为作者学习笔记
一、重载和重写剖析
- 重载:如果我们想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同。这些方法之间的关系,我们称之为重载。
1)重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java 编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法
2)除了同一个类中的方法,重载也可以作用于这个类所继承而来的方法。也就是说,如果子类定义了与父类中非私有方法同名的方法,而且这两个方法的参数类型不同,那么在子类中,这两个方法同样构成了重载。
注意:那么,如果子类定义了与父类中非私有方法同名的方法,而且这两个方法的参数类型相同,那么这两个方法之间又是什么关系呢?
1)如果这两个方法都是静态的,那么子类中的方法隐藏了父类中的方法。
2)如果这两个方法都不是静态的,且都不是私有的,那么子类的方法重写了父类中的方法。
- 重写:
1)参数列表与被重写方法的参数列表必须完全相同。(但是在jvm层面不是这样的,jvm严格要求返回类型和参数列表完全相同,用了桥接技术才和java语言的规定是一样的。)
2)返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
3)访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
4)父类的成员方法只能被它的子类重写。
5)声明为 final 的方法不能被重写。
6)声明为 static 的方法不能被重写,但是能够被再次声明。
7)子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
8)子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
9)重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
10)构造方法不能被重写。
11)如果不能继承一个类,则不能重写该类的方法。
总之:
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
二、虚拟机如何识别方法
- Java 虚拟机识别方法的关键在于类名、方法名以及方法描述符(也叫方法签名)(method descriptor)。前面两个就不做过多的解释了。至于方法描述符,它是由方法的参数类型以及返回类型所构成。在同一个类中,如果同时出现多个名字相同且描述符也相同的方法,那么 Java 虚拟机会在类的验证阶段报错。
- Java 虚拟机与 Java 语言不同,java虚拟机并不限制名字与参数类型相同,但返回类型不同的方法出现在同一个类中,可见,JVM是根据名字和描述符来判断的,只要返回值不一样,其它完全一样,在JVM中是允许的,但Java语言不允许。
三、静态绑定和动态绑定
- 由于对重载方法的区分在编译阶段已经完成,我们可以认为 Java 虚拟机不存在重载这一概念。因此,在某些文章中,重载也被称为静态绑定(static binding),或者编译时多态(compile-time polymorphism);而重写则被称为动态绑定(dynamic binding)。这个说法在 Java 虚拟机语境下并非完全正确。
- 这是因为某个类中的重载方法可能被它的子类所重写,因此 Java 编译器会将所有对非私有实例方法的调用编译为需要动态绑定的类型。
- 理论上来说重载方法会被静态绑定,因为在编译期间已经能够确定。重写方法会使用动态绑定。但是父类重载的方法又可能被子类重写,所以这种父类的重载方法就没办法使用静态绑定,基于这种情况,编译器在处理非静态非私有非final方法时,都是直接使用动态绑定的(final方法因为不会被继承,所以使用静态绑定) 总结就是,不可被子类继承的方法(静态方法,私有方法,final方法)都会被编译成静态绑定。 有可能被子类继承重写造成需要运行时判断对象实例类型后才能决定调用哪个方法的,都会被编译成动态绑定。
- 具体来说,Java 字节码中与调用相关的指令共有五种。
invokestatic:用于调用静态方法。
invokespecial:用于调用私有实例方法、构造器,以及使用 super 关键字调用父类的实例方法或构造器,和所实现接口的默认方法。
invokevirtual:用于调用非私有实例方法。
invokeinterface:用于调用接口方法。
invokedynamic:用于调用动态方法。
四、调用指令的符号引用
- 在编译过程中,我们并不知道目标方法的具体内存地址。因此,Java 编译器会暂时用符号引用来表示该目标方法。这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。
- 经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引。
本文深入探讨Java中的方法调用,包括重载和重写的区别,JVM如何通过类名、方法名和描述符识别方法,以及静态绑定和动态绑定的概念。同时,详细解析了Java字节码中的调用指令,如invokestatic、invokespecial、invokevirtual等,并介绍了符号引用与实际引用的转换过程。
86万+

被折叠的 条评论
为什么被折叠?



