While investigating a stack trace discrepancy when composing another answer, I came across a behavior I do not understand. Consider the following test program (this is as far down as I could narrow it):
interface TestInterface <U> {
void test (U u);
}
static class Test <T extends Test<T>> implements TestInterface<T> { // line 11
@Override public void test (T t) {
throw new RuntimeException("My exception"); // line 13
}
}
static class TestA extends Test<TestA> { }
static class TestB extends Test<TestB> { }
public static void main (String[] args) throws Exception {
try {
Test a = new TestA();
Test b = new TestB();
a.test(b);
} catch (Exception x) {
x.printStackTrace(System.out);
}
try {
TestInterface a = new TestA();
Test b = new TestB();
a.test(b);
} catch (Exception x) {
x.printStackTrace(System.out);
}
try {
TestInterface a = new TestA();
TestInterface b = new TestB();
a.test(b);
} catch (Exception x) {
x.printStackTrace(System.out);
}
}
Lines 11 and 13 are labelled in the above snippet and it can be run on ideone. The output of that program is:
java.lang.RuntimeException: My exception
at Ideone$Test.test(Main.java:13)
at Ideone.main(Main.java:25)
java.lang.RuntimeException: My exception
at Ideone$Test.test(Main.java:13)
at Ideone$Test.test(Main.java:11)
at Ideone.main(Main.java:33)
java.lang.RuntimeException: My exception
at Ideone$Test.test(Main.java:13)
at Ideone$Test.test(Main.java:11)
at Ideone.main(Main.java:41)
My question is: Why is line 11 in the stack trace for the second and third test cases? The difference between the three test cases there are the declared types of a
and b
.
Line 11 (the class declaration line) is only present under the following conditions:
Test
implements an interface, andextends Test<T>
(line 11 is not included if it is declared as class Test<T>
), andTestInterface
type rather than the Test
type.Noting that:
What is happening here? How is that line ending up in the stack trace and why does it not appear if both objects are declared as Test
?
Here is the original program that prompted this, where line 55 of java.lang.Enum
is present if a
is declared as Comparable
but not present when it is declared as Enum
. Line 55 is the declaration of Enum
in the JDK source, line 180 is an explicitly thrown ClassCastException
.
You're looking at the effects of a bridge method!
The test
method declared in TestInterface
has erasure test(Object)
, but the test
method declared in Test
has erasure test(Test)
. A method lookup for the test(Object)
method won't find the test(Test)
method, so Java actually puts separate test(Object)
and test(Test)
methods in Test
's bytecode.
Your first trial uses the test(Test)
method, which behaves as you expected. Your other trials use the test(Object)
method, which is a synthetic bridge method that just calls the test(Test)
method. This bridge method doesn't really have a line number, so it shows up in the stack trace with the fairly arbitrary line number of 11.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With