Ответ 1
Тот факт, что каждый throwable является экземпляром java.lang.Throwable
, подразумевается в разных местах байт-кода Java/JVM. Даже если обработчики для любого были предназначены для представления чего-то, возможно, вне иерархии типов Throwable
, эта идея не удалась, поскольку сегодняшние файлы классов должны иметь StackMapTable
для методов, содержащих обработчики исключений, и что StackMapTable
будет ссылаться на любой, экземпляр java.lang.Throwable
1.
Даже с помощью верификатора старого типа, обработчик, который повторно бросает броски, неявно содержит утверждение о том, что любой throwable является экземпляром java.lang.Throwable
, так как единственный объект athrow
разрешен.
http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow
Объектref должен иметь тип
reference
и должен ссылаться на объект, являющийся экземпляром классаThrowable
или подклассаThrowable
.
Короткий ответ: нет, невозможно создать ситуацию, в которой может быть брошено или поймано нечто, отличное от экземпляра java.lang.Throwable
(или подкласса).
Я попытался создать минимальный пример инструкции try-with-resource для анализа вывода javac
. Результат ясно показывает, что структура является артефактом того, как javac
работает внутри, но не может быть намеренным.
Пример выглядит следующим образом:
public static void tryWithAuto() throws Exception {
try (AutoCloseable c=dummy()) {
bar();
}
}
private static AutoCloseable dummy() {
return null;
}
private static void bar() {
}
(Я скомпилирован с jdk1.8.0_20
)
Я помещаю таблицу обработчика исключений в начало результирующего байтового кода, поэтому его легче ссылаться на местоположение при просмотре последовательности команд:
Exception table:
from to target type
17 23 26 Class java/lang/Throwable
6 9 44 Class java/lang/Throwable
6 9 49 any
58 64 67 Class java/lang/Throwable
44 50 49 any
Теперь к инструкциям:
Начало простое, используются две локальные переменные, одна для хранения AutoCloseable
(индекс 0), другая для возможного throwable (индекс 1, инициализированный с помощью null
). dummy()
и bar()
, то AutoCloseable
проверяется на null
, чтобы увидеть, должно ли оно быть закрыто.
0: invokestatic #2 // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: aconst_null
5: astore_1
6: invokestatic #3 // Method bar:()V
9: aload_0
10: ifnull 86
Мы получаем здесь, если AutoCloseable
не null
, и первая странная вещь происходит, бросается, что определенно null
проверяется на null
13: aload_1
14: ifnull 35
Следующий код закроет AutoCloseable
, защищенный первым обработчиком исключений из таблицы выше, которая вызовет addSuppressed
. Поскольку в этой точке переменная # 1 равна null
, это мертвый код:
17: aload_0
18: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
23: goto 86
26: astore_2
27: aload_1
28: aload_2
29: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
32: goto 86
Обратите внимание, что последняя команда мертвого кода - goto 86
, ветвь - на return
, поэтому, если код выше не был мертвым кодом, мы могли бы начать задаваться вопросом, зачем вам призывать addSuppressed
на Throwable
который игнорируется сразу же.
Теперь следует код, который выполняется, если переменная # 1 - null
(всегда читается). Он просто вызывает close
и переходит к команде return
, не вылавливая никакого исключения, поэтому исключение, созданное close()
, распространяется на вызывающего:
35: aload_0
36: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
41: goto 86
Теперь мы вводим второй обработчик исключений, охватывающий тело оператора try
, объявленного catch Throwable
, читаем все исключения. Он сохраняет Throwable
в переменной # 1, как и ожидалось, но также сохраняет ее в устаревшей переменной # 2. Затем он перебрасывает Throwable
.
44: astore_2
45: aload_2
46: astore_1
47: aload_2
48: athrow
Следующий код является целью двух обработчиков исключений. Во-первых, это цель избыточного любого обработчика исключений, который охватывает тот же диапазон, что и обработчик Throwable
, поэтому, как вы подозревали, этот обработчик ничего не делает. Кроме того, он является объектом четвертого обработчика исключений, улавливая что-либо и покрывая обработчик исключений выше, поэтому мы поймаем повторное исключение инструкции № 48 правой одной инструкции позже. Чтобы сделать вещи еще более забавными, обработчик исключений охватывает больше, чем обработчик выше; заканчивающийся на # 50, исключительный, он даже охватывает первую инструкцию сам по себе:
49: astore_3
Итак, первое, что нужно сделать, - ввести третью переменную, чтобы удерживать ту же брошюру. Теперь AutoCloseable
проверяется на null
.
50: aload_0
51: ifnull 84
Теперь значение переменной # 1 проверяется на null
. Это может быть null
только в том случае, если существует гипотетический, который не может быть Throwable
. Но обратите внимание, что весь код будет отклонен верификатором в этом случае, так как StackMapTable
объявляет все переменные и записи стека операндов, содержащие любое значение, которое может быть присвоено совместимости с java.lang.Throwable
54: aload_1
55: ifnull 78
58: aload_0
59: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
64: goto 84
Наконец, у нас есть обработчик исключений, который обрабатывает исключение, созданное закрытием, когда существует ожидающее исключение, которое будет вызывать addSuppressed
и перебрасывает основное исключение. Он вводит другие локальные переменные, которые показывают, что javac
никогда не использует swap
даже там, где это необходимо.
67: astore 4
69: aload_1
70: aload 4
72: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
75: goto 84
Таким образом, следующие две команды вызывается только в том случае, если catch может означать что-то другое, кроме java.lang.Throwable
, что не так. Путь кода соединяется С# 84 с обычным случаем.
78: aload_0
79: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
84: aload_3
85: athrow
86: return
Таким образом, нижняя строка заключается в том, что дополнительный обработчик исключений для любого отвечает за мертвый код только из четырех команд: # 54, # 55, # 78 и # 79, в то время как по другим причинам существует еще более мертвый код (# 17 - # 32), плюс странный код "throw-and-catch" (# 44 - # 48), который также может быть артефактом идеи обрабатывать иначе, чем Throwable
. Кроме того, один обработчик исключений имеет неправильный диапазон, охватывающий сам, который может быть связан с "записью Strange exception table, созданной javac от Sun" как в комментариях.
В качестве побочной заметки Eclipse создает более простой код, занимающий только 60 байт, а не 87 для последовательности команд, имеющий только два ожидающих обработчика исключений и три локальные переменные вместо пяти. И в этом более компактном коде он обрабатывает возможный случай, что исключение, созданное телом, может быть таким же, как и один бросок close
, и в этом случае addSuppressed
не следует вызывать. Созданный код javac
не заботится об этом.
0: aconst_null
1: astore_0
2: aconst_null
3: astore_1
4: invokestatic #18 // Method dummy:()Ljava/lang/AutoCloseable;
7: astore_2
8: invokestatic #22 // Method bar:()V
11: aload_2
12: ifnull 59
15: aload_2
16: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
21: goto 59
24: astore_0
25: aload_2
26: ifnull 35
29: aload_2
30: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
35: aload_0
36: athrow
37: astore_1
38: aload_0
39: ifnonnull 47
42: aload_1
43: astore_0
44: goto 57
47: aload_0
48: aload_1
49: if_acmpeq 57
52: aload_0
53: aload_1
54: invokevirtual #30 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
57: aload_0
58: athrow
59: return
Exception table:
from to target type
8 11 24 any
4 37 37 any