免责声明:很难确定,因为我从未阅读过明确的 Oracle 声明,但我认为这就是原因:
当您查看 Java 字节码时,您可能会就其他指令提出相同的问题。为什么验证者会在将两个int
s 压入堆栈并立即将它们视为一个时阻止您long
?(试试看,它会阻止你。)你可以争辩说,通过允许这样做,你可以用更小的指令集表达相同的逻辑。(为了进一步说明这个论点,一个字节不能表达太多的指令,因此 Java 字节码集应尽可能减少。)
当然,理论上您不需要字节码指令来将int
s 和s 推入堆栈,而且long
您不需要两条指令来表达方法调用,这一点是正确的。方法由其方法描述符(名称和原始参数类型)唯一标识,您不能在同一类中定义具有相同描述的静态和非静态方法。为了验证字节码,Java 编译器必须检查目标方法是否仍然存在。INVOKESPECIAL
INVOKESTATIC
static
备注:这与 v6ak 的答案相矛盾。但是,非静态方法的方法描述符不会更改为包含对this.getClass()
. INVOKESMART
因此,Java 运行时总是可以从假设指令的方法描述符中推断出适当的方法绑定。请参阅 JVMS §4.3.3。
理论就这么多。但是,两种调用类型表达的意图是完全不同的。请记住,Java 字节码也应该被javac以外的其他工具用来创建 JVM 应用程序。使用字节码,这些工具生成的东西比 Java 源代码更类似于机器代码。但它仍然是相当高的水平。例如,仍然会验证字节码,并且在编译为机器码时会自动优化字节码。但是,字节码是一种抽象,它故意包含一些冗余以使含义的字节码更明确。就像 Java 语言为相似的事物使用不同的名称以使语言更具可读性一样,字节码指令集也包含一些冗余。另一个好处是,验证和字节码解释/编译可以加快速度,因为方法的调用类型并不总是需要推断,而是在字节码中明确说明。这是可取的,因为验证、解释和编译是在运行时完成的。
作为最后的轶事,我应该提到在 Java 5 之前<clinit>
没有标记类的静态初始化static
程序。在这种情况下,静态调用也可以通过方法的名称来推断,但这会导致更多的运行时开销。