How to wrap checked exceptions but keep the origin

2019-01-25 02:48发布

问题:

I have some code that might throw both checked and runtime exceptions.

I'd like to catch the checked exception and wrap it with a runtime exception. But if a RuntimeException is thrown, I don't have to wrap it as it's already a runtime exception.

The solution I have has a bit overhead and isn't "neat":

try {
  // some code that can throw both checked and runtime exception
} catch (RuntimeException e) {
  throw e;
} catch (Exception e) {
  throw new RuntimeException(e);
}

Any idea for a more elegant way?

回答1:

I use a "blind" rethrow to pass up checked exceptions. I have used this for passing through the Streams API where I can't use lambdas which throw checked exceptions. e.g We have ThrowingXxxxx functional interfaces so the checked exception can be passed through.

This allows me to catch the checked exception in a caller naturally without needing to know a callee had to pass it through an interface which didn't allow checked exceptions.

try {
  // some code that can throw both checked and runtime exception

} catch (Exception e) {
  throw rethrow(e);
}

In a calling method I can declare the checked exception again.

public void loadFile(String file) throws IOException {
   // call method with rethrow
}

/**
 * Cast a CheckedException as an unchecked one.
 *
 * @param throwable to cast
 * @param <T>       the type of the Throwable
 * @return this method will never return a Throwable instance, it will just throw it.
 * @throws T the throwable as an unchecked throwable
 */
@SuppressWarnings("unchecked")
public static <T extends Throwable> RuntimeException rethrow(Throwable throwable) throws T {
    throw (T) throwable; // rely on vacuous cast
}

There is a lot of different options for handling exceptions. We use a few of them.

https://vanilla-java.github.io/2016/06/21/Reviewing-Exception-Handling.html



回答2:

Guava's Throwables.propagate() does exactly this:

try {
    // some code that can throw both checked and runtime exception
} catch (Exception e) {
    throw Throwables.propagate(e);
}

UPDATE: This method is now deprecated. See this page for a detailed explanation.



回答3:

Not really.

If you do this a lot, you could tuck it away into a helper method.

static RuntimeException unchecked(Throwable t){
    if (t instanceof RuntimeException){
      return (RuntimeException) t;
    } else if (t instanceof Error) { // if you don't want to wrap those
      throw (Error) t;
    } else {
      return new RuntimeException(t);
    }
}

try{
 // ..
}
catch (Exception e){
   throw unchecked(e);
}


回答4:

I have a specially compiled .class file containing the following:

public class Thrower {
    public static void Throw(java.lang.Throwable t) {
        throw t;
    }
}

It just works. The java compiler would normally refuse to compile this, but the bytecode verifier doesn't care at all.

The class is used similar to Peter Lawrey's answer:

try {
  // some code that can throw both checked and runtime exception

} catch (Exception e) {
    Thrower.Throw(e);
}


回答5:

You can rewrite the same using instanceof operator

try {
    // some code that can throw both checked and runtime exception
} catch (Exception e) {
    if (e instanceof RuntimeException) {
        throw e;
    } else {
        throw new RuntimeException(e);
    }
}

However, your solution looks better.



回答6:

The problem is that Exception is too broad. You should know exactly what the possible checked exceptions are.

try {
    // code that throws checked and unchecked exceptions
} catch (IOException | SomeOtherException ex) {
    throw new RuntimeException(ex);
}

The reasons why this wouldn't work reveal deeper problems that should be addressed instead:

If a method declares that it throws Exception then it is being too broad. Knowing that "something can go wrong" with no further information is of no use to a caller. The method should be using specific exception classes in a meaningful hierarchy, or using unchecked exceptions if appropriate.

If a method throws too many different kinds of checked exception then it is too complicated. It should either be refactored into multiple simpler methods, or the exceptions should be arranged in a sensible inheritance hierarchy, depending on the situation.

Of course there can be exceptions to the rule. Declaring a method throws Exception can be perfectly reasonable if it's consumed by some kind of cross-cutting framework (such as JUnit or AspectJ or Spring) rather than comprising an API for others to use.



回答7:

I generally use the same type of code structure, but condense it down to one line in one of the few times a ternary operator actually makes code better:

try {
  // code that can throw
}
catch (Exception e) {
  throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
}

This does not require additional methods or catch blocks which is why I like it.