3

I have another challenging question from javaDeathMatch game; In the code below we are asked what kind of problem the code below has. please correct me if I am wrong; compilation error : None; At compile time the erasure of type parameter has not still occurred and the dynamic binding has not been taken place, so the parameter passed to the method which is of type SQLException is thought of as Exception in the method 'pleaseThrow' and it(i mean Exception not SQLException) is cast to Runtime Exception in the method with no error. The only error is that we don't have a proper catch clause to catch the exception.

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch (final SQLException ex){
            ex.printStackTrace();
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

if we replace the catch clause with this:

catch(final RuntimeException e){
    e.printStackTrace();
    System.err.println("caught");
}

the exception will be caught but the System.err.println("caught") will never be printed!!! What is the problem????

Radiodef
  • 37,180
  • 14
  • 90
  • 125
AlirezaAsadi
  • 793
  • 2
  • 6
  • 21

3 Answers3

1

This is due to type erasure. In java after compilation, every generic information is lost (there is something left, which is not relevant to this though). That means that during compilation, the generic variable T is equal to RuntimeException. So your pleaseThrow code looks like this:

private void pleaseThrow(final Exception t) throws RuntimeException{
    throw (RuntimeException)t;
}

After compilation though, every generic parameter is erased to the base type. In your case, to Exception. Which leaves you with a method signature like this:

private void pleaseThrow(final Exception t) throws Exception{
    throw (Exception)t;
}

Which finally is clear, why your catch block is never reached. You're trying to catch RuntimeExceptions but what you're actually throwing is a checked exception. Which then propagates upwards and is finally caught by the JVM.

Additional reading: Oracle Tutorial on type erasure

Lino
  • 19,604
  • 6
  • 47
  • 65
  • I don't think this is the correct answer. The reason the `catch RuntimeException` block isn't catching `SQLException` is simply because `SQLException` is not a sub-class of `RuntimeException`. – Frans Jul 18 '22 at 10:34
  • Just change the `pleaseThrow` line to read `new Exption().pleaseThrow(new IllegalArgumentException());` and the `catch` clause to read `catch(final IllegalArgumentException e)` and you'll see the `catch` clause is triggered, which wouldn't be the case if your type erasure explanation was correct. – Frans Jul 18 '22 at 10:39
  • @Frans I don't undestand, your first comment is essentially my last paragraph – Lino Jul 18 '22 at 13:41
  • You're talking about checked and unchecked exceptions but that's not really relevant here. The problem is simply that the exception being thrown doesn't match any of the catch blocks - and that this evaluation is done at runtime, not at compile time. That's all. The poster mistakenly says the exception is being caught, but it's not being caught, so it just bubbles up to the JVM which prints out the stack trace. – Frans Jul 22 '22 at 06:54
1

This code will fail to compile, because SQLException is a checked exception, and to catch a checked exception, it must be declared to be thrown by something inside of the try block. Here it is failing to compile on Ideone, for example, with the following message:

Main.java:7: error: exception SQLException is never thrown in body of corresponding try statement
        }catch (final SQLException ex){
         ^
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

If you change the catch block so it catches RuntimeException, then the code will compile, but the exception will not be caught, because SQLException is not a subclass of RuntimeException.

There's a discussion of how the pleaseThrow method works here. That idiom is usually called "sneaky throws", and it lets the caller throw a checked exception as if it was an unchecked exception. For the difference between checked and unchecked exceptions, see the official tutorial or this Q&A and StackOverflow.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
0

Let us take the first version of your code :

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch (final SQLException ex){ // This is compilation error
            ex.printStackTrace();
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

How is compiler able to detect the error ?

It is not because of throw (T)t;. Infact , if you type cast to a type parameter , compiler ignores it and leaves it to JVM.

Then how does compiler able to generate error ?

It is because of this :

private void pleaseThrow(final Exception t) throws T

Notice the throws T . When you say new Exption<RuntimeException>() , compiler does not create any object. But it infers T during compilation. It comes under it powers.

Now , let us understand the whole picture of why error gets generated.

Compiler from private void pleaseThrow(final Exception t) throws T{ knows that you will throw RuntimeException.

According to rule of type casting , compiler checks both types if one is parent of other. If yes , it passes the code to JVM . Only JVM will then further check if one object can actually be type casted to other.

Similarly, compiler checks for throws and catch block if any is compatible or not. If more than one is compatible, it leaves the decision to JVM. Compiler checks for many other things too but let us focus on the main track.

In your example, One type is SqlException and other is RuntimeException. None is parent of other. Hence, compiler shows error.

Let us see few examples to clear it :

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
        }catch (final ClassCastException ex){
            ex.printStackTrace();
            System.out.println("done");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

In this example, Catch clause won't be called , however code compiles fine. Since ClassCastException is a RuntimeException , Compiler compiles the code fine. RuntimeException is parent to ClassCastException. But when compiler hands over code to JVM, JVM knows that exception object is of type IllegalArgumentException and hence will settle for a catch clause of IllegalArgumentException or it's super type. Here we have nothing like that. Hence, Catch clause won't be called as there is no match.

Take another example :

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
        }catch (final RuntimeException ex){
            ex.printStackTrace();
            System.out.println("done");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

This runs fine and catch block is called. JVM knows object type will be IllegalArgumentException and RuntimeException is super class , hence , it matches due to super class able to refer child class object.

Now, let us come back to your code.

You have only written only one catch block consisting of SqlException which is a checked exception and hence cannot be thrown from pleaseThrow() as it throws RuntimeException according to compiler.

So, this error gets generated :

Error:(9, 10) java: exception java.sql.SQLException is never thrown in body of corresponding try statement

As you know it is illegal in Java to have a catch block catching a checked expression never thrown.


Now , let us come to version 2 of your code :

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

Now, things make sense as in catch block you have written RuntimeException. Compiler sees that method throws RuntimeException and in catch block too we have runtime exception.

Now, let us look more closely. Compiler easily compiles this code and send it to JVM. But before that it does the type erasure.

Let us look what would type erasure would have done to our code : [In actual code given to JVM is byte level code. The below example is to show what type erasure does to the code. Note that below code won't run properly in compiler if you try to compile as this code is after type erasure and few details have been lost which compiler needs. JVM however can run the equivalent byte code of this.]

public class Exption {
    public static void main(String[] args) {
        try {
            new Exption().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }
    }
    private void pleaseThrow(final Exception t) throws java.lang.Exception {
        throw (java.lang.Exception) t;
    }
}

To understand how this code was reduced to , you need to read about type erasure. But trust me till here for time being.

Now, this code is very interesting piece of code.

On this code JVM directly operates. If you see JVM knows it method throws an Object of type SqlException type casted to Exception. It tries to find a match in catch blocks but no match. RunTimeException is not a super class of SqlException. Hence, no catch block is called.

Let us modify the code to understand it more.

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }catch(Exception r){
            System.out.println("done");
        }

    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

This will output "done".

Number945
  • 4,631
  • 8
  • 45
  • 83