Статьи

Бросать необъявленные проверенные исключения

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