Иногда проверенные исключения могут быть проблемой. Например, недавно я попытался реализовать некоторую общую логику для повторения неудачных сетевых операций, и это привело к некоему шаблону команд, по которому, как обычно, метод execute () генерирует исключение java.lang.Exception. Это усложнило код вызывающей стороны, который должен перехватывать и обрабатывать java.lang.Exception вместо более конкретных исключений …
Я знал, что проверенные исключения применяются компилятором , в то время как в виртуальной машине нет ничего, препятствующего созданию проверенного исключения методом, не объявляющим его, поэтому я начал проверять в Интернете, как это реализовать.
Я нашел два поста в блоге Андерса Нораса ( # 1 # 2 ) о том, как выполнить эту магию.
Метод № 1: класс sun.misc.Unsafe
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class UnsafeSample {
public void methodWithNoDeclaredExceptions( ) {
Unsafe unsafe = getUnsafe();
unsafe.throwException( new Exception( "this should be checked" ) );
}
private Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main( String[] args ) {
new UnsafeSample().methodWithNoDeclaredExceptions();
}
}
Это использует внутренние классы реализации библиотек Sun JRE. Это не может работать, если вы используете не Sun VM. И на самом деле это не так, если вы используете GCJ (компилятор GNU для Java).
Описанный выше метод getUnsafe () выполняет некоторые хитрости для доступа к закрытому полю в классе Unsafe, поскольку Unsafe.getUnsafe () может вызываться только классами, загруженными загрузчиком ClassLoader.
См. Также статью « Избегание проверенных исключений » Дона Шварца.
Способ № 2: Thread.stop (Исключение)
public class ThreadStopExample {
@SuppressWarnings("deprecation")
public void methodWithNoDeclaredExceptions( ) {
Thread.currentThread().stop(new Exception( "this should be checked" ));
}
public static void main( String[] args ) {
new ThreadStopExample().methodWithNoDeclaredExceptions();
}
}
Это использует устаревший метод, но работает. Нет проблем с переносимостью, пока ребята из спецификации Java не решат удалить метод.
Это может иметь некоторые побочные эффекты для текущего потока, так как мы вызываем stop (). Я не уверен.
Способ № 3: использование Class.newInstance ()
Посмотрите на подпись java.lang.Class.newInstance () и сравните ее с Constructor.newInstance ()
public final class Class ... {
public T newInstance()
throws InstantiationException, IllegalAccessException
}
public final class Constructor ... {
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
}
Ты видишь это? нет InvocationTargetException!
Если вы вызываете SomeObject.class.newInstance () и конструктор выдает исключение, исключение не включается в InvocationTargetException (это проверенное исключение).
Таким образом, вы можете написать служебный класс, подобный этому, для создания проверенных исключений без необходимости объявлять их в сигнатуре метода.
public class Exceptions {
private static Throwable throwable;
private Exceptions() throws Throwable {
throw throwable;
}
public static synchronized void spit(Throwable throwable) {
Exceptions.throwable = throwable;
try {
Exceptions.class.newInstance();
} catch(InstantiationException e) {
} catch(IllegalAccessException e) {
} finally {
Exceptions.throwable = null;
}
}
}
public class TestExceptionSpit {
public static void main(String[] args) {
Exceptions.spit(new Exception( "this should be checked" ));
}
}
Внутренне Class.newInstance () использует класс sun.misc.Unsafe, но в этом случае этот метод полностью переносим, поскольку вы не используете какой-либо устаревший или внутренний метод. На самом деле это работает и с GCJ JVM.
Я попытался удалить материал синхронизации и статическое поле, используя внутренний класс, но кажется, что компилятор делает какой-то странный трюк, переводя пустой конструктор во что-то еще, предотвращая использование Class.newInstace () для этого внутреннего класса.
Поведение Class.newInstance () также задокументировано:
«Обратите внимание, что этот метод распространяет любое исключение, выданное нулевым
конструктором, включая проверенное исключение. Использование этого метода
эффективно обходит проверку исключения во время компиляции, которая в
противном случае была бы выполнена компилятором».
Так что ваш код полностью безопасен и соответствует правилам ?
Способ № 4: ВС.Корба.Мост
import java.rmi.RemoteException;
public class Bridge {
public void methodWithNoDeclaredExceptions( ) {
sun.corba.Bridge.get().throwException(new RemoteException("bang!"));
}
public static void main( String[] args ) {
new Bridge().methodWithNoDeclaredExceptions();
}
}
Это более или менее то же самое, что и использование Unsafe.class. Разница заключается в том, что в этом случае вам не нужно выполнять рефлексию для доступа к закрытому полю «theUnsafe», потому что класс Bridge делает это за вас. Все еще использую внутренний класс JRE с теми же проблемами переносимости.
Метод № 5: Дженерики
В следующем примере используется тот факт, что компилятор не проверяет обобщенные типы …
import java.rmi.RemoteException;
class Thrower {
public static void spit(final Throwable exception) {
class EvilThrower<T extends Throwable> {
@SuppressWarnings("unchecked")
private void sneakyThrow(Throwable exception) throws T {
throw (T) exception;
}
}
new EvilThrower().sneakyThrow(exception);
}
}
public class ThrowerSample {
public static void main( String[] args ) {
Thrower.spit(new RemoteException("go unchecked!"));
}
}
Кредиты «Харальду», который разместил комментарий в блоге Йоханнеса Бродволла .
Лично я считаю, что последнее является лучшим решением: оно использует особенность компилятора против самого себя.
Выводы
Я думаю, что проверить исключение в Java лучше, чем не иметь его. Я уже высказывался, почему я за здесь проверенные исключения . Это дизайнерское решение, вы можете сделать так, чтобы исключения были проверены или не отмечены, если вы хотите, чтобы ваш клиент обрабатывал их или нет; Вы не можете сделать это в .NET, где проверенные исключения просто не существуют.
Иногда у вас есть (или вы должны написать) методы, бросающие java.lang.Exception, и вы попадаете в ловушку. Поэтому вам может быть интересно узнать, что существует грязный побег, и вы можете решить, использовать его или нет … мы видели, что Sun генерирует необъявленные проверенные исключения в Class.newInstace (), спросите себя: хорошо ли это для JRE код, это может быть хорошо и для вас?
Обычно вы можете заключить проверенное исключение в RuntimeExceptions, но это не упрощает клиентский код, поскольку вызывающий в случае необходимости должен перехватить RuntimeException, развернуть причину и устранить ее. Возможно, может помочь новое ключевое слово Java, чтобы бросить проверенное исключение, не требуя, чтобы вызывающий обработал их: я рекомендую прочитать сообщение Рики Кларксона о проверенных исключениях.
Наконец, я пришел к решению не использовать эти трюки в моем объекте, выполняя логику повторных попыток, и оставил в коде вызывающей стороны беспорядочную логику захвата. В случае необходимости я оценю использование динамического прокси-сервера, выполняющего логику повторных попыток и поддерживающего его поведение прозрачным для клиента.
Для тех, кто хочет непроверенных исключений в Java … ну, есть способ иметь это: пример с Generics — чистый способ получить это. Используйте его, если хотите, на свой страх и риск.
Лично я бы предпочел использовать библиотеки с проверенными исключениями …
Другие связанные статьи
Friday Free Stuff от Chris Nokleberg, использует манипуляции с байт-кодом.
Не пытайтесь делать это дома , Боб Ли, раскрывает некоторые методы, также описанные выше.
От: http://en.newinstance.it/2008/11/17/throwing-undeclared-checked-exceptions/