Java语言层面和JVM层面方法特征签名的区别 及 实例分析
在文章《Java前端编译:Java源代码编译成Class文件的过程》和《Java Class文件结构解析 及 实例分析验证》中多次提到Java语言层面方法特征签名和JVM层面方法特征签名的区别。
下面我们先来回顾一下Java语言层面方法特征签名和JVM层面方法特征签名的区别的是什么,再用测试程序实例来分析验证。
1、两个层面的方法特征签名的区别
方法特征签名:用于区分两个不同方法的语法符号;
(A)、Java语言层面的方法特征签名:
特征签名 = 方法名 + 参数类型 + 参数顺序;
更多请参考:http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2
(B)、JVM层面的方法特征签名:
特征签名 = 方法名 + 参数类型 + 参数顺序 + 返回值类型;
如果存在类型变量或参数化类型,还包括类型变量或参数化类型编译未擦除类型前的信息(FormalTypeParametersopt),和抛出的异常信息(ThrowsSignature),即方法名+签名;
Java语言重载(Overload)一个方法,需要Java语言层面的方法特征签名不同,即不包括方法返回值;而Class文件中有两个同名同参数(类型、顺序都相同),但返回值类型不一样,也是允许的,可以正常运行,因为JVM层面的方法特征签名包括返回值类型。
同样的,对字段来说,Java语言规定字段无法重载,名称必须不一样;但对Class文件来说,只要两个字段描述(类型)不一样,名称一样也是可以的。
2、实例分析验证
下面我们先用javac编译测试程序JavacTestOverload.java,测试程序如下:
public class JavacTestOverload { public String method1(String str) { String mtdName = Thread.currentThread().getStackTrace()[ 1].getMethodName(); //获取当前方法名称,具体使用数组的那个元素和JVM的实现有关,具体说明可以查看Thread.getStackTrace方法的javadoc System.out.println( "invoke " + mtdName + " return String"); return ""; } public int method2(String str) { String mtdName = Thread.currentThread().getStackTrace()[ 1].getMethodName(); System.out.println( "invoke " + mtdName + " return int"); return 1; } public static void main(String[] args) { JavacTestOverload javacTestOverload = new JavacTestOverload(); String str = javacTestOverload.method1( "Test"); int i = javacTestOverload.method2( "Test"); } }
注意,public String method1(String str)方法和public String method2(String str)方法,方法名称和返回值类型不同,而参数一样,因为方法名不同,是同时满足Java语言层面的方法特征签名和JVM层面的方法特征签名的要求;
如果方法名相同(都为"method1"),就不满足ava语言层面的方法特征签名的要求,javac编译就会出现错误,如下:
我们用上面的程序先通过编译,运行调用这两个方法,可以看到两个方法分别输出了自己的名称,如下:
接着,我们用javap反编译JavacTestOverload..class文件,并保存到JavacTestOverload..txt文件,方便对照分析:
javap -verbose JavacTestOverload> JavacTestOverload..txt
然后,通过分析JavacTestOverload..txt反编译信息,可以知道"method2"方法名称对应的常量为第27项,描述符为第28项("method1"方法的分别为第25、26项),如下:
而后,在JavacTestOverload..class找到"method2"方法名称对应的字节码,修改为"method1",修改前后如下("32"--》"31"):
这样两个方法的名称就相同了,可以再通过反编译修改后的JavacTestOverload..class来对比,可以看到修改后生成的反编译信息中没有了"method2"的字符信息,都变为了"method1",如下:
而运行修改后的JavacTestOverload..class,可以看到两个方法运行时输出的名称都为"method1",也证明了两个方法的名称相同,如下:
到这里,这样就证明了:Class文件中有两个同名同参数(类型、顺序都相同),但返回值类型不一样的方法也是允许的,可以正常运行,因为JVM层面的方法特征签名包括返回值类型 。
【参考资料】
1、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
2、《The Java Language Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jls/se8/html/index.html