问题
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:
- If
Test
implements an interface, and - If the exception is thrown from the interface method, and
- If the interface takes a type parameter, and
- If the class declaration's type parameter contains
extends Test<T>
(line 11 is not included if it is declared asclass Test<T>
), and - If the method is called on the
TestInterface
type rather than theTest
type.
Noting that:
- It is definitely my exception being thrown (message and stack trace).
- No other exceptions are thrown if I do not throw mine.
- I have reproduced this with the Oracle JDK 1.7, and 1.8 on Windows, and 1.8 on Ideone. However, 1.7 includes a stack trace element on line 1 instead of 11 (which is doubly weird).
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
.
回答1:
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.
来源:https://stackoverflow.com/questions/42464475/mysterious-line-in-stack-trace