6

In this code example, what's the static type of e in the catch block?

try {
    ....
} catch(IOException | NumberFormatException e) {
    //what's the static type of e in here? Is it Exception?
    System.out.println(e.getClass());
}

It seems to be effectively Exception, but when I hover over it in my IDE it says IOException | NumberFormatException. Is this a special type that only applies to multiple exceptions in catch blocks or does it generalize to other types?

neilO
  • 195
  • 2
  • 6
  • 1
    Yes, the compile-time type is special type `IOException | NumberFormatException` – khelwood Oct 28 '20 at 15:03
  • Yeah, just found it too (https://blog.jooq.org/tag/union-types/). Too bad its only for catch blocks, could have been useful elsewhere. – neilO Oct 28 '20 at 15:09
  • @Abra That would be the runtime type, not the static (compile-time) type. – khelwood Oct 28 '20 at 15:24
  • Nice question, but remember: almost *anything* has been asked here before. So please try using a search engine the next time. – GhostCat Oct 28 '20 at 15:32
  • I did try googling for 'static type exception multiple catch java' but the results were cluttered with tutorial articles on how to use multiple exception catch (without going into details about compile time types). I got a good answer though, so all's well that ends well. – neilO Oct 28 '20 at 16:42

1 Answers1

5

TL;DR: The static type is the nearest common super class.


The Java Language Specification, section 14.20. The try statement, says:

An exception parameter may denote its type as either a single class type or a union of two or more class types (called alternatives). The alternatives of a union are syntactically separated by |.

A catch clause whose exception parameter is denoted as a single class type is called a uni-catch clause.

A catch clause whose exception parameter is denoted as a union of types is called a multi-catch clause.

The declared type of an exception parameter that denotes its type with a single class type is that class type.

The declared type of an exception parameter that denotes its type as a union with alternatives D1 | D2 | ... | Dn is lub(D1, D2, ..., Dn).

So, it's basically called a "union type", and doesn't exist elsewhere in the language.


UPDATE

The effective type is the union of the nearest common super types (class and/or interfaces), i.e. you can call any method that is common for all the alternatives.

The code below illustrates the following points:

  • Since both exceptions extend SuperException, you can call the SuperException method.
  • Since both exceptions implement interface Foo, you can call the Foo method.
  • The effective type is not SuperException, because then you couldn't call Foo methods, and it is not Foo, because then you couldn't call SuperException methods.
  • The effective type is truly a union of the nearest common super types, which really means all common super types.
try {
    // some code throwing the exceptions
} catch (SubException1 | SubException2 e) {
    e.methodInSuper();  // you can call the SuperException method
    e.foo();            // you can call the Foo method
}
interface Foo {
    void foo();
}
class SuperException extends Exception {
    public void methodInSuper() {
        // code here
    }
}
class SubException1 extends SuperException implements Foo {
    @Override
    public void foo() {
        // code here
    }
}
class SubException2 extends SuperException implements Foo {
    @Override
    public void foo() {
        // code here
    }
}

UPDATE 2

To answer the exact question "what's the static type of the exception?", we need to look at the bytecode.

The bytecode of the catch clause of the above code is:

        34: astore_1
        35: aload_1
        36: invokevirtual #33                 // Method SuperException.methodInSuper:()V
        39: aload_1
        40: checkcast     #38                 // class Foo
        43: invokeinterface #40,  1           // InterfaceMethod Foo.foo:()V
        48: return
      Exception table:
         from    to  target type
             0    34    34   Class SubException1
             0    34    34   Class SubException2

As you can see, the single catch clause registers 2 exceptions to be caught, directing both to the same code block. The call to SuperException.methodInSuper() is done straight up. The call to Foo.foo() is done after casting to Foo. The compiled code can be consider equivalent to the follow, except it only catches the 2 subexceptions:

} catch (SuperException e) { // only catch SubException1 and SubException2
    e.methodInSuper();
    ((Foo) e).foo();
}

Conclusion: The static type is the nearest common super class. Any additional common interfaces that is not defined by that super class are silently be handled by the compiler using casts.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks for the answer. Weird decision for Java to not have union types everywhere else though. – neilO Oct 28 '20 at 16:44