Alex Baranosky
Alex Baranosky

Reputation: 50094

Catch a generic exception in Java?

We use JUnit 3 at work and there is no ExpectedException annotation. I wanted to add a utility to our code to wrap this:

 try {
     someCode();
     fail("some error message");
 } catch (SomeSpecificExceptionType ex) {
 }

So I tried this:

public static class ExpectedExceptionUtility {
  public static <T extends Exception> void checkForExpectedException(String message, ExpectedExceptionBlock<T> block) {
     try {
        block.exceptionThrowingCode();
        fail(message);
    } catch (T ex) {
    }
  }
}

However, Java cannot use generic exception types in a catch block, I think.

How can I do something like this, working around the Java limitation?

Is there a way to check that the ex variable is of type T?

Upvotes: 44

Views: 46241

Answers (8)

chaotic3quilibrium
chaotic3quilibrium

Reputation: 5934

If you are interested in the OOP+FP way of approaching this, there is a specific type, Either<L, R>, where a specialization can be created as Either<L extends Throwable, R> via a .tryCatch() method.

While it is too much to show the entire implementation of Either<L, R> in this answer (a fully documented implementation is located in this Gist), below is a paraphrasing snapshot of what it looks like to reify any descendant of Throwable into a proper capture filter.

To see the implementation details, specifically focus on the pair of tryCatch() methods. This StackOverflow Answer inspired the first of these two methods.

public final class Either<L, R> {
  public static <L, R> Either<L, R> left(L value) {
    return new Either<>(Optional.of(value), Optional.empty());
  }
  public static <L, R> Either<L, R> right(R value) {
    return new Either<>(Optional.empty(), Optional.of(value));
  }

  public static <L extends Throwable, R> Either<L, R> tryCatch(
      Supplier<R> successSupplier,
      Class<L> throwableType
  ) {
    try {
      return Either.right(Objects.requireNonNull(successSupplier.get()));
    } catch (Throwable throwable) {
      if (throwableType.isInstance(throwable)) {
        return Either.left((L) throwable);
      }

      throw throwable;
    }
  }

  public static <R> Either<RuntimeException, R> tryCatch(Supplier<R> successSupplier) {
    return tryCatch(successSupplier, RuntimeException.class);
  }

  private final Optional<L> left;
  private final Optional<R> right;

  private Either(Optional<L> left, Optional<R> right) {
    if (left.isEmpty() == right.isEmpty()) {
      throw new IllegalArgumentException("left.isEmpty() must not be equal to right.isEmpty()");
    }
    this.left = left;
    this.right = right;
  }

  @Override
  public boolean equals(Object object) { ... }
  @Override
  public int hashCode() { ... }
  public boolean isLeft() { ... }
  public boolean isRight() { ... }
  public L getLeft() { ... }
  public R getRight() { ... }
  public Optional<R> toOptional() { ... }
  public <T> Either<L, T> map(Function<? super R, ? extends T> rightFunction) { ... }
  public <T> Either<L, T> flatMap(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
  public <T> Either<T, R> mapLeft(Function<? super L, ? extends T> leftFunction) { ... }
  public <T> Either<L, T> mapRight(Function<? super R, ? extends T> rightFunction) { ... }
  public <T> Either<T, R> flatMapLeft(Function<? super L, ? extends Either<? extends T, R>> leftFunction) { ... }
  public <T> Either<L, T> flatMapRight(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
  public <T> T converge(Function<? super L, ? extends T> leftFunction, Function<? super R, ? extends T> rightFunction) { ... }
  public <T> T converge() { ... }
  public static <T> T converge(Either<? extends T, ? extends T> either) { ... }
  public void forEach(Consumer<? super L> leftAction, Consumer<? super R> rightAction) { ... }
}

A quick way to see the above code in action is to check out this validation suite of JUnit tests.

Upvotes: 0

aolo23
aolo23

Reputation: 17

@FunctionalInterface
 public interface ThrowingConsumer<T, E extends Exception> {
      void accept(T t) throws E;
 }
public static <T, E extends Exception> Consumer<T> errConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer,
                   Class<E> exceptionClass, 
                   Consumer<E> exceptionConsumer) {
            return i -> {
                try {
                    throwingConsumer.accept(i);
                } catch (Exception ex) {
                    try {
                        exceptionConsumer.accept(exceptionClass.cast(ex));
                    } catch (ClassCastException ccEx) {
                        throw new RuntimeException(ex);
                    }
                }
            };
        }
  1. Usage example

    Stream.of("a").forEach(errConsumerWrapper(i -> Integer.parseInt(i), NumberFormatException.class, Throwable::printStackTrace));

Upvotes: -1

OscarRyz
OscarRyz

Reputation: 199333

You can also use an IDE that supports live template ( like IntellJ IDEA for instance ) and assign a shortcut like ee -> [tab] that inserts the try/catch/ignore for your and let you type the correct one.

like this

like this

Upvotes: -1

UB Mobile Banking
UB Mobile Banking

Reputation: 37

Generics are not types. They are not templates. They are compile time type checks, in Java. Exception blocks catch on type. You can catch(Exception e) or even catch(Throwable e) and then cast as needed.

Upvotes: -1

oldo
oldo

Reputation: 2202

Catch clause with type parameter is not possible:
http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch

Upvotes: 3

Kevin Bourrillion
Kevin Bourrillion

Reputation: 40851

I understand the impulse to try to simplify your exception-test idiom, but seriously: don't. Every possible choice you'll come up with is a cure that's worse than the disease. Especially JUnit 4's @ExpectedException nonsense! It is a too-clever frameworky solution, requiring everyone to learn how it works, as opposed to a plain self-evident bit of regular Java code. Worse, it gives you no way to wrap only the part of your test that you expect to throw the exception, so if an earlier setup step throws that same exception, your test will pass even though your code is broken.

I could write a long diatribe about this here (I'm sorry for not having enough time to), as we've had a lengthy discussion of this issue among Java engineers here at Google, and the consensus was that none of these crazy solutions are worthwhile. Get used to try/catch, it's really not that bad.

Upvotes: 3

Mark Peters
Mark Peters

Reputation: 81164

You could pass the Class object in and check that programatically.

public static <T extends Exception> void checkForException(String message, 
        Class<T> exceptionType, ExpectedExceptionBlock<T> block) {
    try {
       block.exceptionThrowingCode();
   } catch (Exception ex) {
       if ( exceptionType.isInstance(ex) ) {
           return;
       } else {
          throw ex;  //optional?
       }
   }
   fail(message);
}

//...
checkForException("Expected an NPE", NullPointerException.class, //...

I'm not sure if you'd want the rethrow or not; rethrowing would equally fail/error the test but semantically I wouldn't, since it basically means "we didn't get the exception we expected" and so that represents a programming error, instead of a test environment error.

Upvotes: 38

ShadowRanger
ShadowRanger

Reputation: 155684

Well, you could just catch Exception and rethrow if it's not an expected Exception. Though good coding practice usually dictates that the success path of code should not be defined by an Exception, so you might want to rethink your design.

Upvotes: -1

Related Questions