2

e is of type Exception but prints Exception1 in below code:

class Exception1 extends IOException {void info(){}}
class Exception2 extends Exception {}

class TestMultiCatch {
    public static void main(String args[]) {
        try {
            int a = 10;
            if (a <= 10)
                throw new Exception1();
            else
                throw new Exception2(); 
        } catch (Exception1 | Exception2 e) {
            e.info();  //line 1 error "The method info() is undefined for type Exception"
            System.out.println(e);   //prints Exception1 (after commenting line 1)
        }
    }
}

From what I studied "e" should be of type Exception which is common base class of Exception1 and Exception2. Which it is, as evident from message in line 1.

But then why:

System.out.println(e); //prints Exception1 and not Exception
System.out.println(e instanceof IOException); //prints true and not false
System.out.println(e instanceof Exception1); //prints true and not false
System.out.println(e instanceof Exception2); //false

? Thanks.

mustafa1993
  • 541
  • 1
  • 5
  • 17
  • 1
    e is for both Exception1 and Exception2 type. It's only at run time that the difference is made, but you are trying to compile it while calling .info() on Exception2 type as well – Stultuske Nov 14 '17 at 09:47

3 Answers3

4

When you use a multi-catch clause (the Exception1 | Exception2 e form of catch), the compile-time type of e is the greatest type that the two types have in common, since of course the code has to handle either type of exception.From the spec:

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 as a union with alternatives D1 | D2 | ... | Dn is lub(D1, D2, ..., Dn).

...where lub is Least Upper Bound as defined here.

If you want to use anything that's specific to Exception1 or Exception2, use separate catch blocks:

} catch (Exception1 e) {
    // Something using features of Exception1
} catch (Exception2 e) {
    // Something using features of Exception2
}

If info exists on both Exception1 and Exception2, refactor them so that info exists on a common ancestor class of them:

class TheAncestorException extends Exception {
    public void info() { // Or possibly make it abstract
        // ...
    }
}
class Exception1 extends TheAncestorException {
    // Optionally override `info` here
}
class Exception2 extends TheAncestorException {
    // Optionally override `info` here
}

...so the compiler can give e the type TheAncestorException and make info accessible.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks. So _the greatest type that the two types have in common_ will be _Exception_ right? I got the info() part, but why would e print Exception1 if it is Exception? sorry if am missing something silly. – mustafa1993 Nov 14 '17 at 10:03
  • 1
    @mustafa1993: Not silly, this is something a lot of people have trouble understanding. The exception object **is** an `Exception1`, but the type of `e` (the exception parameter) is just `Exception`. It's just like when you use `List l = new ArrayList<>();` The list object is an `ArrayList`, but the type of the `l` variable is `List`, not `ArrayList`. It's the type of the parameter/variable that determines what methods you can call on it. So even though the object `e` refers to has an `info` method, you can't write `e.info()` because the type of `e` doesn't have it. – T.J. Crowder Nov 14 '17 at 10:08
  • Got it. It's super class reference pointing to sub class object. I refined it down to this(please correct me if I am wrong): At compile time e **is** Exception and at runtime it points to whatever Exception object was received in catch block so it prints Exception1 (the object not type, which is still Exception) in my case. – mustafa1993 Nov 14 '17 at 10:49
  • 1
    @mustafa1993: At compile time, the *type* of `e` (the parameter) is `Exception`. At runtime, the object `e` refers to is an `Exception1` (if it was `Exception1` that was thrown), or an `Exception2` (if it was `Exception2` that was thrown). The difference between what an object *is* vs. the type of the reference we have to it is key to understanding lots of things in Java (and OOP in general). – T.J. Crowder Nov 14 '17 at 10:53
  • One thing that bothers me: why does this work: `catch (Error | RuntimeException t) { throw t; }`? If I understand correctly the type of `t` is `Throwable`, but then why does the compiler allow me to throw it (even in a method which does not declare or catch `Throwable`)? – Pepijn Schmitz Jan 22 '20 at 09:45
  • @PepijnSchmitz I've found the answer to my own question: https://docs.oracle.com/javase/8/docs/technotes/guides/language/catch-multiple.html - it seems that it is because of special support since Java 7; the `throws` clause is cleverer than to just look at the type of the expression. – Pepijn Schmitz Jan 22 '20 at 09:54
  • @T.J.Crowder I don't think that's accurate. `Throwable` _is_ a checked exception, as is every subclass which is not `Error` or `RuntimeException` - just try throwing `new Throwable()` or `new Throwable() {}`. (Note that I found the answer; check my second comment.) – Pepijn Schmitz Jan 22 '20 at 09:56
  • 1
    @PepijnSchmitz - Quite so, I'd misremembered. Because the exception hierarchy in Java is just plain weird. :-) – T.J. Crowder Jan 22 '20 at 10:02
1

The multi-catch seems to be your problem. You (the compiler) can only access methods which are defined on the common ancestors. Of course, "e" will be an Exception1 during runtime, but the compiler just cannot assume that, cause it could as well be an Exception2. Better create a catch block for both Exception1 and Exception2.

Mick
  • 954
  • 7
  • 17
0
catch (Exception1 | Exception2 e) {....}

Here e is reference variable of both Exception1 and Exception2. So at compile time e.info(); will throw exception since info() is not there for Exception2.

Better to use separate catch block for each since both classes don't have same method info().

} catch (Exception1 e) {
            e.info();  
            System.out.println(e);  
        } catch (Exception2 e) {
            System.out.println(e);
        }
  • I added info() in Exception2 as well. Still it gave same error at compile time. – mustafa1993 Nov 14 '17 at 10:14
  • 1
    Yes @mustafa1993, although you are having info() in both but the compiler is not smart enough to know which info() to call since e is a reference of type both. If you caste (or tell compiler) by casting as ((Exception1)e).info(); then it will call respective method. – Bikramjit Rajbongshi Nov 14 '17 at 10:48
  • Yeah it did. Got it. Thanks. – mustafa1993 Nov 14 '17 at 10:51