有三个操作码可用于调用Java方法。很明显,invokeStatic仅用于静态方法的调用。
据我所知,invokespecial用于调用构造函数和私有方法。那么,在运行时需要区分私有方法和公有方法吗?它可以使用相同的操作码,比如说 invokevirtual 吗?
JVM是否处理私有和公共方法定义?据我所知,public和private关键字只在开发阶段需要进行封装。
有三个操作码可用于调用Java方法。很明显,invokeStatic仅用于静态方法的调用。
据我所知,invokespecial用于调用构造函数和私有方法。那么,在运行时需要区分私有方法和公有方法吗?它可以使用相同的操作码,比如说 invokevirtual 吗?
JVM是否处理私有和公共方法定义?据我所知,public和private关键字只在开发阶段需要进行封装。
来自本站的内容:
如果仔细阅读Java VM规范,答案可以很容易地找到:
invokespecial和invokevirtual指令的区别在于,invokevirtual基于对象的类调用方法。invokespecial指令用于调用实例初始化方法以及当前类的私有方法和超类的方法。
换句话说,invokespecial用于调用方法而不关心动态绑定,以调用特定类的版本的方法。
有几种情况下,在“不考虑动态绑定”的情况下分派方法调用非常重要:
首先是从一个构造函数链到一个超类构造函数的情况。
其次,当从一个方法调用到超类的实现时,例如super.foo()
。如果该类或其任何子类覆盖了该方法,则invokevirtual会跳转到错误的位置。
第三,当一个类想要选择使用哪个方法的default
版本时,就像上面代码块中的情况。
interface I { void f(); }
interface J extends I { default void f() { ... } }
interface K extends I { default void f() { ... } }
class C implements J, K {
@Override public void f() { K.super.f(); }
}
class C
存在钻石继承问题,因此需要选择要调用哪个默认方法。使用 invokespecial 可以将其分派到 K
的版本,但如果 K.super.f
通过 invokevirtual 分派,调用最终会回到 C.f
。class Superclass {
private void interestingMethod() {
System.out.println("Superclass's interesting method.");
}
void exampleMethod() {
interestingMethod();
}
}
class Subclass extends Superclass {
void interestingMethod() {
System.out.println("Subclass's interesting method.");
}
public static void main(String args[]) {
Subclass me = new Subclass();
me.exampleMethod();
}
}
如果按照上面定义的方式在子类(Subclass)中调用main()方法,它必须打印出"超类(Superclass)的有趣方法"。如果使用invokevirtual,则会打印出"子类(Subclass)的有趣方法"。为什么?因为虚拟机将根据对象的实际类别(即Subclass)选择要调用的interestingMethod()方法。因此,它将使用Subclass的interestingMethod()方法。另一方面,使用invokespecial时,虚拟机将根据引用类型选择方法,因此将调用Superclass版本的interestingMethod()方法。
interestingMethod
方法签名之间微妙的差异足以使读者对发生了什么感到困惑。 - wcharginexampleMethod()
调用interestingMethod()
时,应该使用invokevirtual
。但实际上,我通过修改Superclass.class
文件中的字节码,将invokespecial
替换为invokevirtual
进行了实验,运行Java Subclass
后输出仍然是“Superclass's interesting method.”
此外,我发现JDK 11直接使用字节码指令invokevirtual
编译Superclass!而JDK 8仍然使用invokespecial
进行编译。 - Alfred Xiaoinvokespecial
或invokevirtual
无关紧要,因为被调用的方法是一个私有函数,JVM将会根据类检查并做出不分派的决定。 - Alfred Xiao假设您有一个A.java类
class A { public void printValue() { System.out.println("Inside A"); }
public static void callMethod(A a) { a.printValue(); } }
打开cmd提示符并进入包含java文件A.java的文件夹。
运行javac A.java。
现在生成了A.class文件,其中包含汇编指令,但您无法阅读它。
现在运行javap -c A
您可以看到方法调用的汇编生成--> a.printValue();
如果printValue()方法是私有的,则需要使用javap -c -private A。
您可以将printValue()设置为私有/静态/公共/私有静态两者都可以。
还要记住的一件事是首先编译器检查调用该方法的对象。然后找到其类类型,并在该类中查找该方法是否可用。
让我们也试试这个:
class B extends A
{
public void printValue()
{
super.printValue();// invokeStatic
System.out.println("Inside B");
}
}
public class Test
{
public static void main(String[] arr)
{
A a = new A();
B b = new B();
A.callMethod(a);// invokeVirtual
A.callMethod(b);// invokeVirtual
}
}
--> 将其保存为Test.java --> 运行javac Test.java --> 运行javap -c -private Test