I use the following class which has this specificity in the equals() method that the return keyword and its expressions are split into several lines (return keyword is on its own line).
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);
}
}
I use javac -g
command (from JDK 17) and javap -l
to obtain the line number table.
Below a reduced extract of javap
for equals()
to illustrate what is puzzling:
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
It says there is more than just return
on line 36, there's return
(75: ireturn) and this
(31: aload_0) whereas in the source, the return keyword is on its own line.
If I look at what produces ecj
for equals()
, I can't see this problem:
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
CodePudding user response:
Yes, it's a javac bug, although you won't notice it in this case. The line number mappings are only used for exception stack traces, and so I don't see any issues unless there's a NPE thrown on the mStyle == other.mStyle
line. If I modify the equals
method to be broken:
@Override
public boolean equals(Object obj) {
TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
return
Objects.equals(mLocale, mLocale) &&
mStyle == other.mStyle &&
Objects.equals(mTimeZone, other.mTimeZone);
}
And then I call equals
with null
as the second argument, a NullPointerException
is thrown when trying to access other.mStyle
but the line number is wrong. It reports the line above which is comparing (incorrectly) the mLocale
field.