Java SneakyThrow of exceptions, type erasure
Asked Answered
B

4

25

Can someone explain this code?

public class SneakyThrow {


  public static void sneakyThrow(Throwable ex) {
    SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
  }

  private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
    throw (T) ex;
  }



  public static void main(String[] args) {
    SneakyThrow.sneakyThrow(new Exception());
  }


}

It may seems strange, but this doesn't produce a cast exception, and permits to throw a checked exception without having to declare it in the signature, or to wrap it in an unchecked exception.

Notice that neither sneakyThrow(...) or the main are declaring any checked exception, but the output is:

Exception in thread "main" java.lang.Exception
    at com.xxx.SneakyThrow.main(SneakyThrow.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

This hack is used in Lombok, with the annotation @SneakyThrow, which permits to throw checked exceptions without declaring them.


I know it has something to do with type erasure, but i'm not sure to understand every part of the hack.


Edit: I know that we can insert an Integer in a List<String> and that checked/unchecked exceptions distinction is a compile time feature.

When casting from a non-generic type like List to a generic type like List<XXX> the compiler produces a warning. But it's less common to cast to a generic type directly like (T) ex in the above code.

If you want, the part that seems strange for me is that I understand that inside the JVM a List<Dog> and List<Cat> looks the same, but the above code seems to mean that finally we can also assign a value of type Cat to a variable of type Dog or something like that.

Bullwhip answered 26/12, 2012 at 9:40 Comment(2)
See also #18198676Robbinrobbins
...and #31271259Robbinrobbins
B
19

If you compile it with -Xlint you'll get a warning:

c:\Users\Jon\Test>javac -Xlint SneakyThrow.java
SneakyThrow.java:9: warning: [unchecked] unchecked cast
    throw (T) ex;
              ^
  required: T
  found:    Throwable
  where T is a type-variable:
    T extends Throwable declared in method <T>sneakyThrowInner(Throwable)
1 warning

That's basically saying "This cast isn't really checked at execution time" (due to type erasure) - so the compiler reluctantly assumes you're doing the right thing, knowing that it won't actually be checked.

Now it's only the compiler which cares about checked and unchecked exceptions - it's not part of the JVM at all. So once you've got past the compiler, you're home free.

I'd strongly advise you to avoid doing this though.

In many cases there's a "real" check when you're using generics because something uses the desired type - but that's not always the case. For example:

List<String> strings = new ArrayList<String>();
List raw = strings;
raw.add(new Object()); // Haha! I've put a non-String in a List<String>!
Object x = strings.get(0); // This doesn't need a cast, so no exception...
Batista answered 26/12, 2012 at 9:45 Comment(13)
See my edit. Btw if I remember you come from the C# word so why do you advice not using stuff like Lombok's @SneakyThrow which helps to not deal with checked exceptions and do like in C#Bullwhip
@SebastienLorber: Because when I'm writing Java, I try to write idiomatic Java. I don't see how SCJP certification is relevant in the slightest, by the way. Your edit talks about assigning values - where is a value ever assigned within your code?Batista
Sorry if it feels arrogant, just wanted to say that these stuff are subjects of the certification. I'll try to find a relevant code with an assignation to show you what I meanBullwhip
@SebastienLorber: In my experience, being certified doesn't mean that someone really understands the material at a deep level. In the course of interviewing lots of Java engineers, I can't say that I've seen any correlation between certification and understanding. Hence my view that it's not really relevant in your question.Batista
@JonSkeet Can you explain compiler logic? Why does it thinks that it RuntimeException?Dehiscent
@gstackoverflow: What do you mean by "Why does it thinks that it RuntimeException"? Why does the compiler think what is a RuntimeException? Please be more specific about which part you're finding hard to understand.Batista
sneakyThrowInner can throw any exception(lets think that there are no <RuntimeException>). Why compiler think that it is RuntimeException?Dehiscent
@gstackoverflow: It declares that it can throw the throwable type you specify as a type argument. Now sneakyThrow calls it using RuntimeException as a type argument, therefore the compiler acts as if it were declared to throw RuntimeException.Batista
@gstackoverflow: So you're asking about code in a completely different question? That's not really helpful...Batista
It is related question. I can ask you about code from sibling answer https://mcmap.net/q/356613/-java-sneakythrow-of-exceptions-type-erasureDehiscent
@gstackoverflow: Yes, it's a related question - but that means you should have added a comment on that question. Why add it here, when there's no type inference involved? (And why only mention the other question half an hour after first asking about it?) I still don't know whether you're confused by the code in this question or the other one.Batista
@JonSkeet why classcast exception not thrown ? After all this is where the JVm will be unable to cast (T) ex ?Polypetalous
@BreakingBenjamin: Because generics aren't part of the JVM other than in terms of annotations. At execution time, that code doesn't know what T is, so it can't cast.Batista
G
9

he above code seems to mean that finally we can also assign a value of type Cat to a variable of type Dog or something like that.

You have to think in terms of how the classes are structured. T extends Throwable and you are passing Exception to it. This is like assigning Dog to Animal not Dog to Cat.

The compiler has rules about which Throwable are checked and which are not, based on inheritance. These are applied at compile time and it is possible to confuse the compiler into allowing you to throw a check exception. At runtime this has no impact.


Checked exceptions are a compile time feature (like generics)

BTW Throwable is a checked exception as well. If you sub class it, it will be checked unless it is a sub-class of Error or RuntimeException.

Two other way to throw checked exceptions without the compiler being aware that you are doing this.

Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);

The only difference is that both use native code.

Gothurd answered 26/12, 2012 at 10:7 Comment(0)
P
7

As of Java 8, the sneakyThrowInner helper method is no longer required. sneakyThrow can be written as:

@SuppressWarnings("unchecked")
static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
    throw (T)t;
}

See the "A peculiar feature of exception type inference in Java 8" post.

The T of sneakyThrow is inferred to be RuntimeException. This can be followed from the langauge spec on type inference (http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)

Note: The sneakyThrow function is declared to return RuntimeException, so that it can be used as follows:

int example() { 
   if (a_problem_occurred) {
      throw sneakyThrow(new IOException("An I/O exception occurred"));
      // Without the throw here, we'd need a return statement!
   } else {
      return 42;
   }
}

By throwing the RuntimeException returned by sneakyThrow, the Java compiler knows this execution path terminates. (Of course, sneakyThrow itself doesn't return.)

Philis answered 12/4, 2016 at 21:30 Comment(1)
this is the answerVirulent
P
0

Let us look at the code below :

public class SneakyThrow {


  public static void sneakyThrow(Throwable ex) {
    SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
  }

  private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
    throw (T) ex;
  }



  public static void main(String[] args) {
    SneakyThrow.sneakyThrow(new Exception());
  }


}

Let us see why is it behaving like this.

First of all any cast to a type parameter ignored by compiler. JVM will directly cast it. So, throw (T) ex; will not be checked for type safety by compiler. But, by the time code reaches the JVM , type erasure takes place. Hence code will be like : [Note : Actual code will be byte code . Below is just to explain what type erasure does.]

public class SneakyThrow {


    public static void sneakyThrow(Throwable ex) {
        SneakyThrow.sneakyThrowInner(ex); // Note : Throwable is checked exception but we don't mention anything here for it
    }

    private static  Throwable sneakyThrowInner(Throwable ex) throws Throwable {
        throw (Throwable) ex;
    }


    public static void main(String[] args) {
        SneakyThrow.sneakyThrow(new Exception());

    }


}

First thing to note is every thing will run smooth and no ClassCastException will be thrown as JVM is easily able to type cast ex to Throwable.

Second thing to note here is that Compiler always forced us to catch or pass on the checked exception received. No such checks are made by JVM. Here, after we type casted, ideally , SneakyThrow.sneakyThrowInner(ex); should be forced to handle the Throwable exception but remember , the byte code of this version is reached to JVM. Hence, we somehow fooled the compiler.

Do like this :

public class SneakyThrow {


    public static void sneakyThrow(Throwable ex) {
        SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
    }

    private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
        throw (T) ex;
    }


    public static void main(String[] args) {
        try {
            SneakyThrow.sneakyThrow(new Exception());
        }catch (Throwable ex){
            System.out.println("Done Succesfully"); // Added to show everything runs fine
        }
    }


}

Output : Done Succesfully

Polypetalous answered 22/7, 2018 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.