Home > Mobile >  Type Inference resolution procedure involving throws clause in Java
Type Inference resolution procedure involving throws clause in Java

Time:02-18

Consider the following article in JLS §18.1.3 - Bounds

Here when we try to identify the set of bounds on the inference variables - we have one of the following situations:

...

  • throws α: The inference variable α appears in a throws clause.

...

A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.

I think this statement is incorrect:

  • this is because ideally the throws clause is mentioned to take care of checked exceptions which can happen during the course of execution of the code.
  • Then why still the JLS preventing α to be a Checked Exception?
  • Ideally the inference variable α must be bounded to be an exception of Checked type rather than being an Unchecked variant.

Is my understanding correct here or am I missing something?

CodePudding user response:

I think your interpretation/understanding of this statement is slightly misguided:

A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.

That line is referring to the resolution, which, as I understand it, is not about where throws α is, but about where α is inferred, conceivably the invocation of the method.

Consider this class:

static class MyClass {

    public static void main(String[] args) {
        MyClass.<RuntimeException>something(0); // same as MyClass.something(1);

        try {
            MyClass.<IOException>something(2);
        } catch (IOException ex) {
            // checked exception
        }
    }

    /**
     * Will throw IOException if argument is 2, a RuntimeException otherwise
     */
    static <T extends Exception> void something(int a) throws T {
        if (a == 2) {
            throw (T) new IOException(); //of course it's a bad cast
        } else {
            throw (T) new Exception();
        }
    }
}

After analyzing the two something method, focus on the invocation in the main method:

The call MyClass.<IOException>something(0) expects an IOException. The caller knows it (assume fully documented contract rather than tightly coupled code), and handles the exception.
This already tells you that the variable can be a checked exception, contrary to what you think.

On the contrary, the call MyClass.<RuntimeException>something(0) expects a runtime exception on similar grounds.

How α (T in the above example) is inferred allows the compiler to skip forcing the caller to catch/handle the exception (if it's to look at the bound, which it'd otherwise have to)

Now about the "optimization": The type variable being bounded as extends Exception can reasonably be expected to resolve to a checked exception. But, if the caller knows that it shall be a runtime exception, it can "inform" the compiler that it's going to be a runtime exception. This is what I did by specifying RuntimeException in the type witness (RuntimeException is also the resolved type when no type argument is given explicitly).

We can spend days to interpret "optimization", but at least I as a caller did not have to try/catch the invocation, and I still didn't upset the compiler (first invocation).

CodePudding user response:

Consider this example:

public class Main {

    public static void main(String[] args) {
        foo(() -> System.out.println("Foo"));
    }

    public static <T extends Exception> void foo(ThrowingRunnable<T> runnable) throws T {
        runnable.run();
    }
}

interface ThrowingRunnable<T extends Exception> {
    void run() throws T;
}

During the inference of the type parameter T when calling foo, there will be a "throws" bound on the type variable T, and T is inferred to be RuntimeException. If not for this bound, T would have been inferred as Exception because of the T extends Exception bound. This would have meant that I needed to do:

try {
    foo(() -> System.out.println("Foo"));
catch (Exception e) {
    // This won't be reached!
}

I had to handle the exception, even though all I'm doing in the lambda is printing things! That doesn't seem very nice, does it?

The purpose of this bound is so that if there's no reason for the method to throw a checked exception, it doesn't throw a checked exception, so that you don't have to write so many try...catches all over the place. foo would only throw a checked exception if you do things like:

foo(() -> new FileOutputStream("foo"));

If the effect of the bound were instead to force T to be a checked exception, it wouldn't be very useful at all.

  • Related