Неуловимый ChuckNorrisException
Можно ли построить фрагмент кода в Java, который сделал бы гипотетический java.lang.ChuckNorrisException
неуловимый?
Мысли, которые приходили на ум, используют, например, перехватчики или аспектно-ориентированное программирование.
17 ответов
Я не пробовал это, поэтому я не знаю, будет ли JVM ограничивать что-то подобное, но, возможно, вы могли бы скомпилировать код, который выдает ChuckNorrisException
, но во время выполнения предоставьте определение класса ChuckNorrisException
который не распространяется Throwable.
ОБНОВИТЬ:
Не работает Это генерирует ошибку верификатора:
Exception in thread "main" java.lang.VerifyError: (class: TestThrow, method: ma\
in signature: ([Ljava/lang/String;)V) Can only throw Throwable objects
Could not find the main class: TestThrow. Program will exit.
ОБНОВЛЕНИЕ 2:
На самом деле, вы можете заставить это работать, если отключите проверку байтового кода! (-Xverify:none
)
ОБНОВЛЕНИЕ 3:
Для тех, кто следует из дома, вот полный сценарий:
Создайте следующие классы:
public class ChuckNorrisException
extends RuntimeException // <- Comment out this line on second compilation
{
public ChuckNorrisException() { }
}
public class TestVillain {
public static void main(String[] args) {
try {
throw new ChuckNorrisException();
}
catch(Throwable t) {
System.out.println("Gotcha!");
}
finally {
System.out.println("The end.");
}
}
}
Компилировать классы:
javac -cp . TestVillain.java ChuckNorrisException.java
Бежать:
java -cp . TestVillain
Gotcha!
The end.
Закомментируйте "extends RuntimeException" и перекомпилируйте ChuckNorrisException.java
только:
javac -cp . ChuckNorrisException.java
Бежать:
java -cp . TestVillain
Exception in thread "main" java.lang.VerifyError: (class: TestVillain, method: main signature: ([Ljava/lang/String;)V) Can only throw Throwable objects
Could not find the main class: TestVillain. Program will exit.
Запустить без проверки:
java -Xverify:none -cp . TestVillain
The end.
Exception in thread "main"
Обдумав это, я успешно создал неуловимое исключение. Я решил назвать это JulesWinnfield
однако, скорее, чем Чак, потому что это одно исключение, лежащее за грибовидным облаком. Кроме того, это может быть не совсем то, что вы имели в виду, но это, безусловно, не может быть пойман. Заметим:
public static class JulesWinnfield extends Exception
{
JulesWinnfield()
{
System.err.println("Say 'What' again! I dare you! I double dare you!");
System.exit(25-17); // And you shall know I am the LORD
}
}
public static void main(String[] args)
{
try
{
throw new JulesWinnfield();
}
catch(JulesWinnfield jw)
{
System.out.println("There's a word for that Jules - a bum");
}
}
И вуаля! Неисследованное исключение.
Выход:
бежать:
Скажи "Что" снова! Попробуй! Я дважды смею тебя!
Java Результат: 8
СТРОИТЬ УСПЕШНО (общее время: 0 секунд)
Когда у меня будет немного больше времени, я посмотрю, не смогу ли я придумать что-то еще.
Кроме того, проверьте это:
public static class JulesWinnfield extends Exception
{
JulesWinnfield() throws JulesWinnfield, VincentVega
{
throw new VincentVega();
}
}
public static class VincentVega extends Exception
{
VincentVega() throws JulesWinnfield, VincentVega
{
throw new JulesWinnfield();
}
}
public static void main(String[] args) throws VincentVega
{
try
{
throw new JulesWinnfield();
}
catch(JulesWinnfield jw)
{
}
catch(VincentVega vv)
{
}
}
Вызывает переполнение стека - опять же, исключения остаются необработанными.
С таким исключением, очевидно, было бы обязательно использовать System.exit(Integer.MIN_VALUE);
от конструктора, потому что это то, что произойдет, если вы бросили такое исключение;)
Любой код может поймать Throwable. Так что нет, какое бы исключение вы ни создавали, оно будет подклассом Throwable и может быть поймано.
public class ChuckNorrisException extends Exception {
public ChuckNorrisException() {
System.exit(1);
}
}
(Конечно, технически это исключение на самом деле никогда не выдается, а ChuckNorrisException
не может быть брошено - это бросает тебя первым.)
Любое исключение, которое вы выбрасываете, должно расширять Throwable, чтобы его всегда можно было поймать. Так что ответ - нет.
Если вы хотите усложнить обработку, вы можете переопределить методы getCause(), getMessage()
, getStackTrace()
, toString()
бросить другого java.lang.ChuckNorrisException
,
Мой ответ основан на идее @jtahlborn, но это полностью работающая Java- программа, которую можно упаковать в файл JAR и даже развернуть на вашем любимом сервере приложений как часть веб-приложения.
Прежде всего, давайте определимся ChuckNorrisException
класс, чтобы он не разбивал JVM с самого начала (Чак действительно любит разбивать JVM, кстати)
package chuck;
import java.io.PrintStream;
import java.io.PrintWriter;
public class ChuckNorrisException extends Exception {
public ChuckNorrisException() {
}
@Override
public Throwable getCause() {
return null;
}
@Override
public String getMessage() {
return toString();
}
@Override
public void printStackTrace(PrintWriter s) {
super.printStackTrace(s);
}
@Override
public void printStackTrace(PrintStream s) {
super.printStackTrace(s);
}
}
Сейчас идет Expendables
класс для его построения:
package chuck;
import javassist.*;
public class Expendables {
private static Class clz;
public static ChuckNorrisException getChuck() {
try {
if (clz == null) {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("chuck.ChuckNorrisException");
cc.setSuperclass(pool.get("java.lang.Object"));
clz = cc.toClass();
}
return (ChuckNorrisException)clz.newInstance();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
И, наконец, Main
класс, чтобы пнуть задницу:
package chuck;
public class Main {
public void roundhouseKick() throws Exception {
throw Expendables.getChuck();
}
public void foo() {
try {
roundhouseKick();
} catch (Throwable ex) {
System.out.println("Caught " + ex.toString());
}
}
public static void main(String[] args) {
try {
System.out.println("before");
new Main().foo();
System.out.println("after");
} finally {
System.out.println("finally");
}
}
}
Скомпилируйте и запустите его с помощью следующей команды:
java -Xverify:none -cp .:<path_to_javassist-3.9.0.GA.jar> chuck.Main
Вы получите следующий вывод:
before
finally
Не удивительно, ведь это удар с разворота:)
В конструкторе вы можете запустить поток, который несколько раз вызывает originalThread.stop (ChuckNorisException.this)
Поток мог бы перехватить исключение несколько раз, но продолжал бы выбрасывать его, пока он не умрет.
Все исключения в Java должны быть подклассами java.lang.Throwable
и, хотя это может не быть хорошей практикой, вы можете поймать каждый тип исключения следующим образом:
try {
//Stuff
} catch ( Throwable T ){
//Doesn't matter what it was, I caught it.
}
См. Документацию java.lang.Throwable для получения дополнительной информации.
Если вы пытаетесь избежать проверенных исключений (тех, которые должны быть явно обработаны), тогда вы захотите создать подкласс Error или RuntimeException.
На самом деле принятый ответ не так хорош, потому что Java нужно запускать без проверки, то есть код не будет работать в нормальных условиях.
AspectJ на помощь для реального решения!
Исключительный класс:
package de.scrum_master.app;
public class ChuckNorrisException extends RuntimeException {
public ChuckNorrisException(String message) {
super(message);
}
}
аспект:
package de.scrum_master.aspect;
import de.scrum_master.app.ChuckNorrisException;
public aspect ChuckNorrisAspect {
before(ChuckNorrisException chuck) : handler(*) && args(chuck) {
System.out.println("Somebody is trying to catch Chuck Norris - LOL!");
throw chuck;
}
}
Пример приложения:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
catchAllMethod();
}
private static void catchAllMethod() {
try {
exceptionThrowingMethod();
}
catch (Throwable t) {
System.out.println("Gotcha, " + t.getClass().getSimpleName() + "!");
}
}
private static void exceptionThrowingMethod() {
throw new ChuckNorrisException("Catch me if you can!");
}
}
Выход:
Somebody is trying to catch Chuck Norris - LOL!
Exception in thread "main" de.scrum_master.app.ChuckNorrisException: Catch me if you can!
at de.scrum_master.app.Application.exceptionThrowingMethod(Application.java:18)
at de.scrum_master.app.Application.catchAllMethod(Application.java:10)
at de.scrum_master.app.Application.main(Application.java:5)
Вариант на тему - удивительный факт, что вы можете бросить необъявленные проверенные исключения из кода Java. Поскольку оно не объявлено в сигнатуре методов, компилятор не позволит вам перехватить само исключение, хотя вы можете перехватить его как java.lang.Exception.
Вот вспомогательный класс, который позволяет вам бросать что-либо, объявленное или нет:
public class SneakyThrow {
public static RuntimeException sneak(Throwable t) {
throw SneakyThrow.<RuntimeException> throwGivenThrowable(t);
}
private static <T extends Throwable> RuntimeException throwGivenThrowable(Throwable t) throws T {
throw (T) t;
}
}
Сейчас throw SneakyThrow.sneak(new ChuckNorrisException());
выдает ChuckNorrisException, но компилятор жалуется
try {
throw SneakyThrow.sneak(new ChuckNorrisException());
} catch (ChuckNorrisException e) {
}
о перехвате исключения, которое не выдается, если ChuckNorrisException является проверенным исключением.
Единственный ChuckNorrisException
s в Java должен быть OutOfMemoryError
а также StackruError
,
Вы действительно можете "поймать" их в том смысле, что catch(OutOfMemoryError ex)
будет выполняться в случае возникновения исключения, но этот блок автоматически сбросит исключение для вызывающей стороны.
Я не думаю что public class ChuckNorrisError extends Error
делает трюк, но вы могли бы попробовать. Я не нашел документации о расширении Error
Is it possible to construct a snippet of code in java that would make a hypothetical java.lang.ChuckNorrisException uncatchable?
Да, и вот ответ: Создай свой java.lang.ChuckNorrisException
такой, что это не пример java.lang.Throwable
, Зачем? Невыбираемый объект по определению неуловим, потому что вы никогда не сможете поймать что-то, что никогда не может быть брошено.
Вы можете оставить Чака Норриса внутренним или частным и заключить его в капсулу или проглотить его...
try { doChuckAction(); } catch(ChuckNorrisException cne) { /*do something else*/ }
Две фундаментальные проблемы с обработкой исключений в Java состоят в том, что она использует тип исключения, чтобы указать, должно ли действие быть предпринято на его основе, и что все, что предпринимает действие, основанное на исключении (то есть "поймает" его), как предполагается, разрешает основное условие. Было бы полезно иметь средство, с помощью которого объект исключения мог бы решить, какие обработчики должны выполняться, и выполнили ли обработчики, которые выполнялись до сих пор, достаточно вещей, чтобы настоящий метод удовлетворял условиям его выхода. Хотя это может быть использовано для создания "неуловимых" исключений, есть два более значительных применения: (1) создавать исключения, которые будут считаться обработанными, только когда они пойманы кодом, который фактически знает, как с ними работать, и (2) разрешить для разумной обработки исключений, которые происходят в finally
блок (если FooException
во время finally
блок во время разматывания BarException
оба исключения должны распространяться вверх по стеку вызовов; оба должны быть уловимы, но раскручивание должно продолжаться до тех пор, пока оба не будут пойманы). К сожалению, я не думаю, что был бы какой-то способ заставить существующий код обработки исключений работать таким образом, не ломая вещи.
Легко можно смоделировать необработанное исключение в текущем потоке. Это вызовет обычное поведение необработанного исключения и, таким образом, выполнит работу семантически. Это, однако, не обязательно остановит выполнение текущего потока, так как никакое исключение фактически не выдается.
Throwable exception = /* ... */;
Thread currentThread = Thread.currentThread();
Thread.UncaughtExceptionHandler uncaughtExceptionHandler =
currentThread.getUncaughtExceptionHandler();
uncaughtExceptionHandler.uncaughtException(currentThread, exception);
// May be reachable, depending on the uncaught exception handler.
Это действительно полезно в (очень редких) ситуациях, например, когда Error
обработка требуется, но метод вызывается из фреймворка, перехватывающего (и отбрасывающего) любой Throwable
,
Вызовите System.exit(1) в finalize
и просто выбросить копию исключения из всех других методов, чтобы программа вышла.