MENU

Java杂记(四)代码层面与字节码层面的特征签名区别

June 17, 2018 • Code

前言:阅读《深入理解Java虚拟机》泛型相关章节时,了解到方法的特征签名在 Java 代码层面与字节码层面存在差异,故学习记录之。

特征签名

来自 The Java Language Specification

Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.

关于特征签名的更多内容可参考 8.4.2. Method Signature

简单来说,特征签名就是方法的 ID身份证,由方法声明的各个部分组成。

不同层面的签名

上面的特征签名符合一般人的认知,即签名包括:

  • 方法名称

  • 声明参数类型个数顺序

但实际上,这只是 Java 语言层面的规范,在字节码层面上,签名还应包括返回类型抛出异常参数,如下图。

关于 JVM 中的签名内容可参考 Signatures 一节,其中定义了字节码层面完整的特征签名内容,以其中的方法签名为例(如下图),可看到确实在字节码中签名包含返回值及异常参数签名。

换句话说,Java 编译器无法通过的特征签名可能允许在 class 文件中共存,并被虚拟机运行。因为 Java 语言级的特征签名范围比字节码中定义的要窄。

栗子分析

栗子一

public class Signature {

    public void s(List<String> list) {
        System.out.println("Signature.s1");
    }

    public void s(List<Integer> list) {
        System.out.println("Signature.s2");
    }
}

上面的栗子无法通过编译,因为 Java 中泛型是伪泛型,在编译时会被擦除。所以上面两个方法在编译器看来都是 s(List list),签名相同,无法通过编译。

栗子二

public class Signature {

    public String s(List<String> list) {
        System.out.println("Signature.s1");
        return "";
    }

    public int s(List<Integer> list) {
        System.out.println("Signature.s2");
        return 1;
    }
}
  • 栗子二在栗子一的基础上修改方法返回值,在 Java 1.7 及以上的 Javac 编译器依旧无法通过,因为 Java 语言层方法签名不包括返回值,所以两个方法的签名在编译器看来还是一样

  • 但在 Oracle Javac 1.6 上却是可以正常编译的,并且编译出来的 class 文件可以正常运行。这说明两个方法可以共存于一个 class 文件中,即字节码层面与语言层的签名不同。

手动更改 class 文件中方法的签名部分后运行也可证明上述观点。

小结

  • Java 代码规范中方法特征签名:方法名 + 参数类型 + 参数顺序

  • JVM 规范中 class 字节码的方法特征签名:方法名 + 参数类型 + 参数顺序 + 返回值类型 + 异常参数,需要 1.6 的 Javac 编译器

参考

Last Modified: July 5, 2018
Archives QR Code
QR Code for this page
Tipping QR Code