Determining compile-time multicatch exception type
Asked Answered
S

3

13

I built something I don't really understand - I don't know how it works. I've familiarized myself with this multicatch explaination article.

Consider these two exceptions and code:

public class MyException1 extends Exception {
  // constructors, etc
  String getCustomValue();
}

public class MyException2 extends Exception {
  // constructors, etc
  String getCustomValue() { return "foo"; }
}

try {
  //...
} catch (MyException1|MyException2 e) {
  e.getCustomValue(); // won't work, as I expected
}

I won't be able to call getCustomValue(), even though the method is the same, because inside Java the above try/catch is supposed to actually be casting the MyException1/2 to Exception (that's how I understood the docs).

However, if I introduce an interface like this:

public interface CustomValueGetter {
  String getCustomValue();
}

public class MyException1 extends Exception implements CustomValueGetter /*...*/
public class MyException2 extends Exception implements CustomValueGetter /*...*/

and add it to both exceptions, Java is actually able to allow me to use that method. And then calling this is valid:

try {
  //...
} catch (MyException1|MyException2 e) {
  e.getCustomValue(); // does work
}

In short, my question is: what is actually happening here: (MyException1|MyException2 e).

What is e?

  • Is the closest superclass chosen as the type of e? This question asks about it and that's, supposedly, the answer. If so, then why is the interface CustomValueGetter "visible" when I access e? It shouldn't be if, in my case, e is an Exception.

  • And if not, if the real class is either MyException1 or MyException2 why am I not simply able to call the same method available for both of those classes?

  • Is e an instance of a dynamically generated class which implements all common interfaces of both exceptions and is of the nearest common supperclass type?

Stable answered 27/8, 2015 at 6:10 Comment(0)
N
4

As Ischuetze said, e is looking for the class or interface that both exceptions do share. In your first example in can´t find a a sharing class despite the Exception class hence it is only able to use the methodes provided by it.

Changing your example to this code will be able to compile again.

public class MyException12 extends Exception {
    public String getCustomValue(){ return "boo"; };
}

public class MyException1 extends MyException12{
    public String getCustomValue() { return "foo"; };
}

public class MyException2 extends MyException12{
    // constructors, etc
    public String getCustomValue() { return "foo"; };
}

As in your Example with the interface, the Exceptions notifies that both MyException1 and MyException2 have MyException12 and hence are able to use it´s functions.

Here is a SO question answering the whole problem and what the type of e will be.

a quote from the link in the answer:

Changing the handling of exception types affects the type system in two ways: in addition to the usual type checking performed on all types, exception types undergo an additional compile time analysis. For the purpose of type checking, a catch parameter declared with a disjunction has type lub(t1, t2, ...) (JLSv3 §15.12.2.7) where the ti are the exception types the catch clause is declared to handle. Informally, the lub (least upper bound) is the most specific supertype of the types in question. In the case of a multi-catch exception parameter, the least upper bound of the types in question always exists since the types of all the caught exceptions must be subclasses of Throwable. Therefore, Throwable is an upper bound of the types in question, but it may not be the least upper bound since some subclass of Throwable may be a superclass (and thereby also a supertype) of the types in question and the exception types in question may implement a common interface. (A lub can be an intersection type of a superclass and one or more interfaces.) For the purpose of exception checking (JLSv3 §11.2), a throw statement (JLSv3 §11.2.2) that rethrows a final or effectively final catch parameter is treated as throwing precisely those exception types that:

Napper answered 27/8, 2015 at 6:25 Comment(3)
If the accepted answer of the link posted in your answer is right, then the interface I used should never be resolved in e, because the superclass of both of those exceptions does not implement it.Stable
@Stable i edid and quoted the link in the answer of the other question, Notify the later part saying: A lub can be an intersection type of a superclass and one or more interfaces.Napper
Too bad that wording didn't make it into the actual Java Language documentation (which is very obscurely written). I guess the closest is the description of Intersection Types, which mentions "type inference (§15.12.2.7)", and so does the description of the catch clause.Skeleton
J
4

In the first code example Java cannot inference automatically, that both MyException1 and MyException2 implement the function getCustomValue. So e is the greatest common denominator in the type hierarchy of both; that is Exception and does'nt have the function getCustomValue. Thus, its not working.

Java is strongly typed and despite that the functions are named similary its not the same as if they are declared in the same type.

In the second code both exceptions implement CustomValueGetter. Thus, eis the greatest common denominator CustomValueGetter which implements getCustomValue.

Jobie answered 27/8, 2015 at 6:15 Comment(0)
N
4

As Ischuetze said, e is looking for the class or interface that both exceptions do share. In your first example in can´t find a a sharing class despite the Exception class hence it is only able to use the methodes provided by it.

Changing your example to this code will be able to compile again.

public class MyException12 extends Exception {
    public String getCustomValue(){ return "boo"; };
}

public class MyException1 extends MyException12{
    public String getCustomValue() { return "foo"; };
}

public class MyException2 extends MyException12{
    // constructors, etc
    public String getCustomValue() { return "foo"; };
}

As in your Example with the interface, the Exceptions notifies that both MyException1 and MyException2 have MyException12 and hence are able to use it´s functions.

Here is a SO question answering the whole problem and what the type of e will be.

a quote from the link in the answer:

Changing the handling of exception types affects the type system in two ways: in addition to the usual type checking performed on all types, exception types undergo an additional compile time analysis. For the purpose of type checking, a catch parameter declared with a disjunction has type lub(t1, t2, ...) (JLSv3 §15.12.2.7) where the ti are the exception types the catch clause is declared to handle. Informally, the lub (least upper bound) is the most specific supertype of the types in question. In the case of a multi-catch exception parameter, the least upper bound of the types in question always exists since the types of all the caught exceptions must be subclasses of Throwable. Therefore, Throwable is an upper bound of the types in question, but it may not be the least upper bound since some subclass of Throwable may be a superclass (and thereby also a supertype) of the types in question and the exception types in question may implement a common interface. (A lub can be an intersection type of a superclass and one or more interfaces.) For the purpose of exception checking (JLSv3 §11.2), a throw statement (JLSv3 §11.2.2) that rethrows a final or effectively final catch parameter is treated as throwing precisely those exception types that:

Napper answered 27/8, 2015 at 6:25 Comment(3)
If the accepted answer of the link posted in your answer is right, then the interface I used should never be resolved in e, because the superclass of both of those exceptions does not implement it.Stable
@Stable i edid and quoted the link in the answer of the other question, Notify the later part saying: A lub can be an intersection type of a superclass and one or more interfaces.Napper
Too bad that wording didn't make it into the actual Java Language documentation (which is very obscurely written). I guess the closest is the description of Intersection Types, which mentions "type inference (§15.12.2.7)", and so does the description of the catch clause.Skeleton
S
2

Digging on @Kevin Eshche's JLS link I seem to have found a proper answer.

At compile time e is actually a least upper bound, which is defined in JLS-15.12.2.7.

An excerpt of the documentation:

Computing the intersection is more complicated than one might first realize. Given that a type parameter is constrained to be a supertype of two distinct invocations of a generic type, say List and List, the naive intersection operation might yield Object. However, a more sophisticated analysis yields a set containing List. Similarly, if a type parameter T is constrained to be a supertype of two unrelated interfaces I and J, we might infer T must be Object, or we might obtain a tighter bound of I & J. These issues are discussed in more detail later in this section.

In short, it is a class of the closest common supertype of all |'d exceptions (in my case, an Exception) and it implements all interfaces all the exceptions do (in my case, CustomValueGetter and Serializable).

Stable answered 27/8, 2015 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.