Подтвердить что ты не робот

Расширение Throwable в Java

Java позволяет создать совершенно новый подтип Throwable, например:

public class FlyingPig extends Throwable { ... }

Теперь, очень редко, я могу сделать что-то вроде этого:

throw new FlyingPig("Oink!");

и, конечно же, в другом месте:

try { ... } catch (FlyingPig porky) { ... }

Мои вопросы:

  • Это плохая идея? И если да, то почему?
    • Что можно было сделать, чтобы предотвратить этот подтипирование, если это плохая идея?
    • Поскольку это невозможно предотвратить (насколько я знаю), какие катастрофы могут возникнуть?
  • Если это не такая плохая идея, почему бы и нет?
    • Как вы можете сделать что-то полезное из того, что вы можете extends Throwable?

Предлагаемый сценарий № 1

Сценарий, в котором я действительно испытывал желание сделать что-то вроде этого, имеет следующие свойства:

  • "Событие" - это то, что в конечном итоге произойдет. Ожидается . Это определенно не Error, и там ничего Exception не о том, когда это произойдет.
    • Поскольку он ожидается, ожидается его catch. Он ничего не пропустит. Он не "убежит" от любой попытки catch общего Exception и/или Error.
  • "Событие" происходит крайне редко.
  • Когда это происходит, обычно есть глубокая трассировка стека.

Так что, возможно, теперь ясно, что я пытаюсь сказать: FlyingPig является результатом исчерпывающего рекурсивного поиска.

Объект для поиска существует: это только вопрос нахождения его в большом море, которое является поисковым пространством. Процесс поиска будет длинным, поэтому относительно высокая стоимость обработки исключений незначительна. Фактически, традиционная альтернатива управления потоком, использующая флаг boolean isFound, может быть более дорогой, поскольку ее необходимо проверять непрерывно в течение всего процесса поиска, скорее всего, на каждом уровне рекурсии. Эта проверка не удастся 99,99% времени, но абсолютно необходимо распространять условие завершения. В какой-то мере, будучи эффективным, проверка неэффективна!

Просто throw -ing a FlyingPig, когда искомый объект найден, вам не нужно загромождать код с помощью управления флагом boolean isFound. В этом отношении не только очиститель кода, но он может работать быстрее из-за этого упущения.

Итак, чтобы подвести итог, выбор между этими двумя:

  • Традиционный подход к управлению потоком
    • Используйте boolean isFound, постоянно проверенный
    • 99,99% времени, чек - это "отходы", потому что он все равно будет false
    • Когда он в конечном итоге превратится в true, вы перестанете рекурсировать, и вы должны убедиться, что сможете нормально расслабиться до начального вызова.
    Подход
  • FlyingPig
    • Не беспокойтесь ни с какими boolean isFound.
    • Если найдено, просто throw new FlyingPig(); он ожидается, поэтому для него будет catch.
    • Отсутствует управление флагом boolean, нет пропущенной проверки, если вам нужно продолжать, без учета ручного размотки рекурсии и т.д.

Вопросы:

  • Является ли эта методика (ab) использованием исключения действительным? (Есть ли это имя?)
  • Если допустимо, следует FlyingPig extends Throwable или Exception просто отлично? (хотя в его обстоятельствах нет ничего исключительного?)
4b9b3361

Ответ 1

Я бы сказал, что это действительно плохая идея. Много кода реализовано в предположении, что если вы поймаете Error и Exception, вы поймете все возможные исключения. И большинство учебников и учебников расскажут вам то же самое. Создавая прямой подкласс Throwable, вы потенциально создаете всевозможные проблемы обслуживания и взаимодействия.

Я не могу придумать никаких оснований для расширения Throwable. Расширьте Exception или RuntimeException.

EDIT. В ответ на предложенный OP вариант сценария № 1.

Исключения - очень дорогой способ борьбы с "нормальным" контролем потока. В некоторых случаях мы говорим о тысячах дополнительных инструкций, выполняемых для создания, броска и исключения исключений. Если вы проигнорируете принятую мудрость и используете исключения для управления исключительным потоком, используйте подтип Exception. Пытаться притвориться чем-то является "событием", а не "исключением", объявляя, что подтип Throwable ничего не достигнет.

Однако, ошибка состоит в том, чтобы скомпоновать исключение с ошибкой, ошибкой, ошибкой. И нет ничего плохого в представлении "исключительного события, которое не является ошибкой, ошибкой, ошибкой или чем-либо" с использованием подкласса Exception. Ключ в том, что событие должно быть исключительным; то есть необычно, происходит очень редко,...

Таким образом, FlyingPig может быть не ошибкой, но это не значит, что не объявлять ее как подтип Exception.

Ответ 2

Является ли эта методика (ab) использованием исключения действительным? (Есть ли это имя?)

По-моему, это не очень хорошая идея:

Исключение должно быть не используется для управления потоком. Если бы мне пришлось называть этот метод, я бы назвал его запахом кода или анти-шаблоном.

См. также:

Если значение valid, if FlyingPig extends Throwable или Exception просто отлично? (хотя нет ничего исключительного в его обстоятельствах?)

Могут быть ситуации, когда вы хотите уловить Throwable, чтобы не только поймать Exception , но и Error, но это редко, люди обычно не catch Throwable. Но мне не удается найти ситуацию, когда вы хотели бы бросить подкласс Throwable.

И я также думаю, что расширение Throwable не делает вещи менее "исключающими", они делают их хуже, что полностью поражает намерение.

Итак, если вы действительно хотите что-то бросить, отбросьте подкласс Exception.

См. также:

Ответ 3

Вот сообщение в блоге от Джона Роуза, архитектора HotSpot:

http://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive

Речь идет о "злоупотреблении" исключениями для управления потоком. Немного другой вариант использования, но.. Короче говоря, он работает очень хорошо - если вы предварительно выделяете/клонируете свои исключения, чтобы предотвратить создание стеков стека.

Я думаю, что эта техника оправдана, если "скрыта" от клиентов. IE, ваш FlyingPig никогда не сможет покинуть вашу библиотеку (все общедоступные методы должны транзитивно гарантировать, чтобы не бросать ее). Один из способов гарантировать это - сделать это проверенным Исключением.

Я думаю, что единственное оправдание для расширения Throwable - это то, что вы хотите разрешить людям передавать обратные вызовы, которые имеют предложения catch (Exception e), и вы хотите, чтобы их результат был проигнорирован ими. Я могу просто купить это...

Ответ 4

Аннотация org.junit.Test включает класс None, который расширяет Throwable и используется как значение по умолчанию для параметра аннотации expected.

Ответ 5

Если вы можете оправдать то, что устанавливает FlyingPig отдельно от Ошибки и Исключения, так что это не подходит как подкласс, либо нет ничего принципиально неправильного в его создании.

Самая большая проблема, о которой я могу думать, - это прагматичный мир, иногда есть оправданные причины, чтобы поймать java.lang.Exception. Ваш новый тип броска будет летать прямо мимо блоков try-catch, которые имели все разумные ожидания подавления (или регистрации, обертывания и т.д.) Всех возможных нефатальных проблем.

На оборотной стороне, если вы выполняете техническое обслуживание старой системы, которая un оправдывает подавление java.lang.Exception, вы можете обманывать ее. (Предполагая, что искренняя апелляция на время, чтобы исправить ее должным образом, отрицается).

Ответ 6

По мере развития этого вопроса я вижу что я неправильно понял суть первоначальный вопрос, поэтому некоторые из другие ответы, вероятно, больше Соответствующий. Я оставлю этот ответ здесь, поскольку он может по-прежнему другие, у кого есть аналогичный вопрос, и найдите эту страницу во время поиска ответ на их вопрос.

Нет ничего плохого в распространении Throwable, чтобы иметь возможность создавать (и обрабатывать) пользовательские исключения. Однако вы должны помнить о следующих моментах:

  • Использование наиболее конкретного исключения - отличная идея. Это позволит вызывающему пользователю обрабатывать различные типы исключений по-разному. Иерархия классов исключения важна, чтобы иметь в виду, поэтому ваше пользовательское исключение должно распространять другой тип Throwable, максимально приближенный к вашему исключению.
  • Расширение Throwable может быть слишком высоким. Вместо этого попробуйте расширить исключение или исключение RuntimeException (или исключение более низкого уровня, которое ближе к причине того, что вы выбрасываете исключение). Помните о различии между RuntimeException и Исключением.
  • Вызов метода, который генерирует исключение (или подкласс Exception), должен быть заключен в блок try/catch, способный справиться с этим исключением. Это хорошо для случаев, когда вы ожидаете, что что-то пойдет не так, из-за обстоятельств, которые могут оказаться вне вашего контроля (например, сеть не работает).
  • Вызов метода, который генерирует исключение RuntimeException (или его подкласс), не нужно обертывать в блок try/catch, который может обрабатывать исключение. (Это, безусловно, может быть, но это не обязательно.) Это больше для исключений, которые действительно не следует ожидать.

Итак, предположим, что в вашей базе кода есть следующие классы исключений:

public class Pig extends Throwable { ... }
public class FlyingPig extends Pig { ... }
public class Swine extends Pig { ... }
public class CustomRuntimeException extends RuntimeException { ... }

И некоторые методы

public void foo() throws Pig { ... }
public void bar() throws FlyingPig, Swine { ... }
// suppose this next method could throw a CustomRuntimeException, but it
// doesn't need to be declared, since CustomRuntimeException is a subclass
// of RuntimeException
public void baz() { ... } 

Теперь у вас может быть код, который вызывает эти методы следующим образом:

try {
    foo();
} catch (Pig e) {
    ...
}

try {
    bar();
} catch (Pig e) {
    ...
}

baz();

Обратите внимание, что когда мы вызываем bar(), мы можем просто поймать Pig, так как оба FlyingPig и Swine расширяют Pig. Это полезно, если вы хотите сделать то же самое для обработки любого исключения. Однако вы могли бы обрабатывать их по-разному:

try {
    bar();
} catch (FlyingPig e) {
    ...
} catch (Swine e) {
    ...
}

Ответ 7

Играть! framework использует что-то подобное для обработки запросов. Обработка запросов проходит через много слоев (маршрутизация, промежуточное ПО, контроллеры, рендеринг шаблонов), а на конечном уровне визуализированный HTML завернут в броски и брошен, который самый верхний слой захватывает, разворачивает и отправляет клиенту. Таким образом, ни один из методов во многих задействованных слоях не должен явно возвращать объект ответа, а также не должен иметь объект ответа, переданный как аргумент для распространения и изменения.

Я немного отрывочен по деталям. Вы можете просмотреть код игры! рамки для деталей.