特定场景下javac生成的行号是否不如ecj准确?
问题现象
下面是一个Java类,其equals()方法的特点是将return关键字单独占一行,后续的布尔表达式分行书写:
package jd.core.test; import java.util.Locale; import java.util.Objects; import java.util.TimeZone; public class TimeZoneDisplayKey { private final TimeZone mTimeZone; private final int mStyle; private final Locale mLocale; public TimeZoneDisplayKey(TimeZone mTimeZone, int mStyle, Locale mLocale) { this.mTimeZone = mTimeZone; this.mStyle = mStyle; this.mLocale = mLocale; } @Override public int hashCode() { return Objects.hash(mLocale, mStyle, mTimeZone); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj; return Objects.equals(mLocale, other.mLocale) && mStyle == other.mStyle && Objects.equals(mTimeZone, other.mTimeZone); } }
使用JDK 17的javac -g命令编译后,通过javap -l提取equals()方法的行号表,出现了不符合预期的映射:
31: aload_0 32: getfield #17 // Field mLocale:Ljava/util/Locale; 35: aload_2 36: getfield #17 // Field mLocale:Ljava/util/Locale; 39: invokestatic #37 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 42: ifeq 74 45: aload_0 46: getfield #13 // Field mStyle:I 49: aload_2 50: getfield #13 // Field mStyle:I 53: if_icmpne 74 56: aload_0 57: getfield #7 // Field mTimeZone:Ljava/util/TimeZone; 60: aload_2 61: getfield #7 // Field mTimeZone:Ljava/util/TimeZone; 64: invokestatic #37 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 67: ifeq 74 70: iconst_1 71: goto 75 74: iconst_0 75: ireturn LineNumberTable: line 26: 0 line 27: 5 line 29: 7 line 30: 11 line 32: 13 line 33: 24 line 35: 26 line 36: 31 line 37: 39 line 39: 64 line 36: 75
行号表显示,第36行(即单独写return的那一行)同时对应了字节码的31: aload_0(读取this)和75: ireturn(返回操作),但源码中return关键字和后续表达式是分开展开的。
而使用ecj编译生成的行号表则没有这个问题,每个字节码指令都对应到了正确的源码行:
31: aload_0 32: getfield #21 // Field mLocale:Ljava/util/Locale; 35: aload_2 36: getfield #21 // Field mLocale:Ljava/util/Locale; 39: invokestatic #47 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 42: ifeq 72 45: aload_0 46: getfield #19 // Field mStyle:I 49: aload_2 50: getfield #19 // Field mStyle:I 53: if_icmpne 72 56: aload_0 57: getfield #17 // Field mTimeZone:Ljava/util/TimeZone; 60: aload_2 61: getfield #17 // Field mTimeZone:Ljava/util/TimeZone; 64: invokestatic #47 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 67: ifeq 72 70: iconst_1 71: ireturn 72: iconst_0 73: ireturn LineNumberTable: line 26: 0 line 27: 5 line 29: 7 line 30: 11 line 32: 13 line 33: 24 line 35: 26 line 37: 31 line 38: 45 line 39: 56 line 36: 70
原因解析
这是javac和ecj在调试行号表生成策略上的差异:
javac在处理跨多行的return表达式时,会将整个表达式对应的所有字节码指令的起始和结束位置,都关联到return关键字所在的源码行。因此表达式中读取字段、调用方法的字节码,都会被映射到return所在行,最终出现同一行对应多个不相关字节码的情况。ecj则采用了更精细的行号映射策略,会将每个子表达式对应的字节码,关联到该子表达式实际所在的源码行,确保行号和代码逻辑的对应关系更准确。
总结
这种现象不属于编译错误,不会影响程序的运行逻辑,但会干扰调试过程(比如断点命中位置不符合预期)。如果需要更准确的行号映射,可以:
- 改用
ecj编译代码 - 调整代码风格,将
return关键字和表达式写在同一行
内容的提问来源于stack exchange,提问作者Nicolas Baumann
相关产品推荐
相关产品推荐

