Иногда проверенные исключения могут быть проблемой. Например, недавно я попытался реализовать некоторую общую логику для повторения неудачных сетевых операций, и это привело к некоему шаблону команд, по которому, как обычно, метод 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/