Ответ 1
Ответ на этот вопрос сводится к тому, сколько у вас контроля над кодом, реализующим Listener. Вы правы, что невозможно создать stacktrace, не будучи в методе.
Общая методика заключается в создании Exception() в конструкторе, но не бросайте его. Это содержит информацию о стеке, которую вы можете использовать, как вы хотите. Это даст вам номер строки конструктора, но не класса. Обратите внимание, что этот метод не особенно эффективен, потому что создание stacktrace является дорогостоящим.
Вам понадобится:
- принудительно выполнить некоторый код в конструкторе (относительно легко, если ваш Listener является абстрактным классом, который вы контролируете)
- Принесите код как-нибудь (лечение здесь хуже, чем заболевание).
- Сделайте некоторые предположения о том, как названы классы.
- Прочитайте банку (сделайте то же самое, что и javac -p)
Для 1) вы просто поместите создание исключения в абстрактный класс, и конструктор будет вызван подклассом:
class Top {
Top() {
new Exception().printStackTrace(System.out);
}
}
class Bottom extends Top {
public static void main(String[] args) {
new Bottom();
}
}
это создает что-то вроде:
java.lang.Exception
at uk.co.farwell.stackoverflow.Top.<init>(Top.java:4)
at uk.co.farwell.stackoverflow.Bottom.<init>(Bottom.java: 11)
at uk.co.farwell.stackoverflow.Bottom.main(Bottom.java: 18)
В общем, есть некоторые правила именования, которые следуют: если у вас есть внешний класс под названием Actor и внутренний вызываемый Consumer, то скомпилированный класс будет называться Actor $Consumer. Анонимные внутренние классы называются в том порядке, в котором они появляются в файле, поэтому Актер $1 появится в файле до Актера $2. Я не думаю, что это на самом деле указано где угодно, так что это, вероятно, просто соглашение, и на него нельзя положиться, если вы делаете что-то сложное с несколькими jvms и т.д.
Возможно, как указывал jmg, вы можете определить несколько классов верхнего уровня в одном файле. Если у вас есть открытый класс Foo, это должно быть определено в Foo.java, но непубличный класс может быть включен в другой файл. Вышеупомянутый метод справится с этим.
Пояснение:
Если вы разобьете java (javap -c -verbose), вы увидите, что в отладочной информации есть номера строк, но они применяются только к методам. Используя следующий внутренний класс:
static class Consumer implements Runnable {
public void run() {
// stuff
}
}
а выход javap содержит:
uk.co.farwell.stackoverflow.Actors$Consumer();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 20: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Luk/co/farwell/stackoverflow/Actors$Consumer;
В строке LineNumberTable содержится список номеров строк, которые применяются к методу. Поэтому мой конструктор для Потребителя начинается с строки 20. Но это первая строка конструктора, а не первая строка класса. Это только одна и та же строка, потому что я использую конструктор по умолчанию. Если я добавлю конструктор, номера строк изменятся. компилятор не сохраняет строку, объявленную классом. Таким образом, вы не можете найти, где объявлен класс без разбора самой java. У вас просто нет информации.
Однако, если вы используете анонимный внутренний класс, например:
Runnable run = new Runnable() {
public void run() {
}
};
Тогда номер строки конструктора и класса будет соответствовать [*], поэтому это дает вам номер строки.
[*] За исключением случаев, когда "новый" и "Runnable()" находятся на разных строках.