Skip to main content

Allow grouping of multiple catch statements

25 replies [Last post]
cowwoc
Offline
Joined: 2003-08-24
Points: 0

This is syntactic suger (much like Generics) that would improve the maintainability and readability of Java code:

[...]
try
{
doSomething();
}
catch (Exception1, Exception2, Exception3 e)
{
e.printStackTrace();
}
catch (RuntimeException e)
{
doSomethingElse();
}

Expected behavior: The grouped catch statement would be translated by the compiler into:

catch (Exception1 e)
{
e.printStackTrace();
}
catch (Exception2 e)
{
e.printStackTrace();
}
catch (Exception3 e)
{
e.printStackTrace();
}

Within the context of the grouped catch statement "e" would consist of the intersection of all classes. That is, you may only invoke methods that are common within all possible classes.

The above example is a trivial use of this feature but in cases where your catch body is much larger (you do a lot of stuff) it is a maintenance nightmare to have to keep multiple catch statements synced with one another.

I look forward to your feedback.

Gili

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
seanreilly
Offline
Joined: 2004-08-30
Points: 0

I have an alternative question. What if doSomething() evolves to throw an additional checked exception over time? I would consider the desired outcome to be that the compiler would issue an error where this pattern was used. What I see happening instead would be that the new exception would be eaten/suppressed -- hopefully I'm wrong?

I suppose that this could also happen if doSomething() throws [i]Exception1 Exception2[/i] and [i]Exception3[/i], but the caller forgets to handle [i]Exception3[/i] in the calling block.

yishai
Offline
Joined: 2003-11-16
Points: 0

> The best compiler can say
> is that it's the common base class of X1, X2, X3. So
> it could be just Throwable or Exception. So, what is
> the type compiler should check for?

In principal, it could be the lowest possible base class of all three, which might be nothing higher than Throwable but could be a BaseX. However, for me it would be enough if it was always Throwable (that is the minimal base class that can be in a catch statement).

> And just think
> about casting.

That would be no different than passing that base type into a method. The casing rules would be the same.

chandra
Offline
Joined: 2003-06-12
Points: 0

I was referring to type of e in catch(X1, X2, X3 e){}. For a typed language, the compiler needs to now the type of e so that the type safety of catch block can be checked. Mind you that compiler, unlike runtime, doesn't have reflective information. It can only say that the type of e could be X1 or X2 or X3. So, what is the type of e? The best compiler can say is that it's the common base class of X1, X2, X3. So it could be just Throwable or Exception. So, what is the type compiler should check for? And just think about casting.

cowwoc
Offline
Joined: 2003-08-24
Points: 0

I don't claim to be a compiler wizard but I my feeling is that the compiler knows more at compile-time than reflection knows at runtime. For example, it know the Generic parameter type and can get at Javadoc comments and other syntax that is not available at runtime.

Can someone at Sun comment on this issue (preferrably someone who works on javac)?

chandra
Offline
Joined: 2003-06-12
Points: 0

Aha! That's because generics in 5.0 is implemented using erasure not reification.Still, compiler only knows as much as the author of the code. In this case, even the author of the code couldn't tell the type of e.

yishai
Offline
Joined: 2003-11-16
Points: 0

> How about this method for those rare cases:
>
> private FooException wrap(Exception exc) {
> log.error(exc, exc);
> return new FooException(exc);
> }
>

We have similar code hundreds of times. The problem with moving that into a utility class is that logging is often location sensitive, so you can't have the logging happen elsewhere. Of course you could engineer your way out of this problem, but why should the syntax make such a common use case hard?

jayjacobson
Offline
Joined: 2005-01-18
Points: 0

I think this idea would be a nice addition to the language. Here is another variation on how this could be represented in code:

try{
errorfunction();
}catch(Exception e){
e.printStackTrace();
}

This would seem to have some advantages from a syntax parsing point of view. In addition, it might be a generically reusable construct which could be applied in other cases. This basically would reuse the J2SE 5.0 template wildcard syntax with an alternate way to bound the wildcard.

brucechapman
Offline
Joined: 2004-03-18
Points: 0

This is one of the situations where I find java prevents me from applying DRY (Don't repeat yourself) in an intuitive manner.

One way to code this situation is like this,

[code]try {
doSomething();
} catch (Exception e){
if(e instanceof Exception1 ||
e instanceof Exception2 ||
e instanceof Exception3 ) {
e.printStackTrace();
} else if(e instanceof RuntimeException) {
doSomethingElse();
} else {
/* we didn't really want to catch it */
throw e;
}
}
[/code]which achieves the aim of a single shared block for each of the 3 exceptions (motivated by DRY), but has a lot of syntactic baggage which is rather too heavy unless the shared block has quite a few lines of code.

But with a more general Mustang wishlist language feature "Switch on Runtime Type" (see http://forums.java.net/jive/thread.jspa?threadID=153&messageID=8165#8165 ) it would simplify to
[code]try {
doSomething();
} catch (Exception e){
switch (e instanceof ?) {
case Exception1:
case Exception2:
case Exception3:
e.printStackTrace();
break;
case RuntimeException:
doSomethingElse();
break;
default:
throw e;
}
}[/code]Which is not quite as optimal as the proposed new catch syntax but gets most of the way there with a more general purpose facility.

Note: the "switch on Runtime Type" semantics to do this are slightly less strict than those originally proposed in the link above (esp. the drop through requirements).

seanreilly
Offline
Joined: 2004-08-30
Points: 0

> [code]try {
> doSomething();
> } catch (Exception e){
> switch (e instanceof ?) {
> case Exception1:
> case Exception2:
> case Exception3:
> e.printStackTrace();
> break;
> case RuntimeException:
> doSomethingElse();
> break;
> default:
> throw e;
> }
> }[/code]

Interesting, and a very novel idea. Is there a way to avoid having the method that contains this block throw the common super class of the exception being caught? As I read it, the [code]catch (Exception e)[/code] and [code]throw e;[/code] clauses in the current example would require this method to throw java.lang.Exception. If a way can be found to avoid that issue, then this idea will have serious promise.

brucechapman
Offline
Joined: 2004-03-18
Points: 0

> Is there a way
> to avoid having the method that contains this block
> throw the common super class of the exception being
> caught? As I read it, the [i]catch (Exception e)[/i] and
> [i]throw e;[/i] clauses in the
> current example would require this method to throw
> java.lang.Exception. If a way can be found to avoid
> that issue, then this idea will have serious promise.

Good point, but this isn't an issue, (though I hadn't considered it initially, and I did a little panic when I first read your post - luckily [i]the simplest thing that might work[/i] does still work)

If doSomething() only declares the checked exceptions [i]Exception1 Exception2 & Exception3[/i], then you wouldn't need the [i]throw e;[/i] This is the same as when using conventional try with catches.

If doSomething() does declare another checked exception(s) then yes, your code block would either need to catch it, or declare it thrown, again, just as you would using conventional try with catches.

However a problem might occur in that an exception thrown in the try block gets caught by the general catch but has no code in the switch to handle it. It would unwittingly get swallowed up and the compiler couldn't tell you that you had not handled it because as far as the compiler is concerned it is handled. That particular problem would not occur with an extended syntax for multi-catch.

seanreilly
Offline
Joined: 2004-08-30
Points: 0

>
> If doSomething() only declares the checked exceptions
> [i]Exception1 Exception2 & Exception3[/i], then you
> wouldn't need the [i]throw e;[/i] This is the same as
> when using conventional try with catches.
>

Ok, makes sense. What if doSomething() throws (or could throw) [i]Exception1 Exception2 Exception3[/i] and [i]RuntimeException[/i]? Also, what if I wanted the catch block around doSomething() to handle a checked exception and an Error? Is there a way to ensure that the default block of the case statement will never be executed?

brucechapman
Offline
Joined: 2004-03-18
Points: 0

> Also, what if I wanted
> the catch block around doSomething() to handle a
> checked exception and an Error? Is there a way to
> ensure that the default block of the case statement
> will never be executed?

And only write one bit of handling code?

Well with a single catch, you'd need to catch Throwable, in which case you'd also catch any RuntimeExceptions, and other checked exceptions. The checked ones aren't so bad, because you ought to know what they can be. For the runtimes, you can specifically process RuntimeException (rather than as default), cast them to RuntimeException if using the NOW style if statements (the switch-on-runtime-type would cast automatically for you) before throwing and so they wouldn't need to be declared as thrown.

seanreilly
Offline
Joined: 2004-08-30
Points: 0

>
> ... you'd need to catch
> Throwable, in which case you'd also catch any
> RuntimeExceptions, and other checked exceptions. The
> checked ones aren't so bad, because you ought to know
> what they can be. For the runtimes, you can
> specifically process RuntimeException (rather than as
> default), cast them to RuntimeException if using the
> NOW style if statements (the switch-on-runtime-type
> would cast automatically for you) before throwing and
> so they wouldn't need to be declared as thrown.
>

So then the java implementation would look something like this?:

[code]
try
{
doSomething();
}
catch (Throwable t)
{
switch (t instanceof ?)
{
case Exception1:
case Exception2:
case Exception3:
case SpecificError:
/* specific error processing goes here */
break;
case RuntimeException:
throw (RuntimeException) t;
case Error:
throw (Error) t;
default:
/* this should never happen! */
/* can't put "throw t;" here! */
}
}
[/code]

Looks awfully complicated compared to the alternative:

[code]
try
{
doSomething();
}
catch (Exception1, Exception2, Exception3, SpecificError e)
{
/* specific error processing goes here */
}
[/code]

Also, (as I noted in another post) what happens when doSomething() changes, and throws additional checked exceptions at a later date? Or if a programmer forgets one of the checked exceptions that doSomething() throws? What must happen is that an error occur when compiling the calling code. Instead, with the construction we've been discussing, any new or missing exceptions would get silently discarded. Needless to say, I regard this as a serious liability.

chandra
Offline
Joined: 2003-06-12
Points: 0

What will be the type of e in this case? The only type a compiler could infer is the common base class of Exception1, Exception2, Exception3. Also, should the cast be allowed such as ((Exception1) e)).someException1Method()?

jwenting
Offline
Joined: 2003-12-02
Points: 0

Reflection can always get the true type if needed.
As it is syntactic sugar the compiler would add a preparsing run in which the statement "catch (X1, X2, X3 e){}" is replaced by "catch (X1 e){} catch (X2 e) {} catch (X3 e) {}".

I can see a potential problem with this as the compiler will need to place them in the proper order in the exception tree.

What about the following:

[code]
try
{
// blah
}
catch (X1 e) {}
catch (X2 e, X3 e, X4 e) {}
catch (X5 e) {}
[/code]

what would the inheritance rules need to be?
Logically X5 should be mandated to be a supertype of all of X2, X3, and X4 or unrelated to any of them and similarly X1 should be either a subtype or unrelated.

Also if X2, X3, and X4 ARE related the compiler will have to be smart enough to place the generated blocks in the correct order even if they are not listed in that order.

But in principle the idea looks sound to me, in fact I've caught myself typing something like this quite a few times (and I must admit sometimes the compiler catches me at it ;) ).

seanreilly
Offline
Joined: 2004-08-30
Points: 0

>
> what would the inheritance rules need to be?
> Logically X5 should be mandated to be a supertype of
> all of X2, X3, and X4 or unrelated to any of them and
> similarly X1 should be either a subtype or
> unrelated.
>
> Also if X2, X3, and X4 ARE related the compiler will
> have to be smart enough to place the generated blocks
> in the correct order even if they are not listed in
> that order.

The compiler could just include them in the order they are specified, and then (like it does now) complain if that order is invalid.

seanreilly
Offline
Joined: 2004-08-30
Points: 0

I really like this. This syntax is important because when an exception is encountered, there are basically 4 ways of dealing with it.

a) Take compensating actions to recover from or mitigate the exception.

b) Propagate the exception up the call stack as is.

c) Ignore the exception (doing nothing but reporting/logging the exception counts here too).

d) Hoist the exception: Wrap it in an exception more appropriate for this api and throw the wrapped exception.

With case a, there's not much that can be done to reduce the amount of code required; each compensating action is by definition unique, and would require unique code.

Case b is already possible with minimal code; just add a throws clause to the method in question.

However this feature can dramatically reduce the size of catch blocks that follow cases c and d. Ignoring or logging an exception requires either no code at all, or a java.lang.Exception (with most logging frameworks). Similarly, since java 1.4 and chained exceptions, wrapping the exception can be done as well.

For those who want a concrete example, suppose I have an api Foo that will throw the checked exception FooException where appropriate. Let's also assume that there may be multiple possible implementations of the foo api (one that uses jdbc, others that use file i/o), but that they all use SAX. They also all use the Bar api, which throws BarException where appropriate.

The implementation of Foo that uses jdbc can deal with some problems and recover from them. However, sometimes it can't deal with any of these exceptions well; its only choice is to fail and throw a FooException.

So you could easily have a block of code like this:

try
{
//..stuff happens here
}
catch (final SqlException e)
{
log.error(e,e);
throw new FooException(e);
}
catch (final BarException e)
{
log.error(e,e);
throw new FooException(e);
}
catch (final SaxException e)
{
throw new FooException(e);
}

and so on ( note that I have forgotten the log statement in the third catch block. Duplication leads to silly errors). Catching java.lang.Exception (or worse, Throwable) is not a good idea, RuntimeExceptions should go on their way unmolested by my code. Creating a common super class for these exceptions isn't possible; I don't control SqlException or SaxException.

With the suggested new syntax, that block of code could now be:

try
{
//..stuff happens here
}
catch (final SqlException, BarException, SaxException e)
{
log.error(e,e);
throw new FooException(e);
}

Much more concise, and the missing log statement in the one case is now not possible. It's still fully strongly typed, as the compiler can verify that every possible exception caught by this block can be executed correctly.

tackline
Offline
Joined: 2003-06-19
Points: 0

How about this method for those rare cases:

private FooException wrap(Exception exc) {
log.error(exc, exc);
return new FooException(exc);
}

Suppose this proposal went through. You would still be duplicating the exception code for each try statement. So the proposal doesn't really help much.

cowwoc
Offline
Joined: 2003-08-24
Points: 0

> How about this method for those rare cases:
>
> private FooException wrap(Exception exc) {
> log.error(exc, exc);
> return new FooException(exc);
> }
>
> Suppose this proposal went through. You would still
> be duplicating the exception code for each try
> statement. So the proposal doesn't really help much.

I have no idea what you're talking about. There is absolutely nothing preventing you from grouping multiple catch statements that wrap the exception and either return or throw the enclosing exception. This RFE does cover this use-case because (as explained at the top) it is nothing but syntactic suger. Under the hood, the compiler will translate the single catch statement into multiple statements (one per exception) with an identical body.

Gili

xhq
Offline
Joined: 2003-06-15
Points: 0

+1

Especially Java has no "Nested functions" as in D or "anonymous delegation" as in C#. This fact leads to:

try {
...
} catch {AException e) {
same code here
} catch (BException e) {
same code here
} catch (CException e) {
same code here
} catch (Exception e) {
different code here
}

sat1196
Offline
Joined: 2003-11-08
Points: 0

What about allowing one line statements in the catch clause. E.g.
[code]
try{
...
} catch(Exception e)
e.printStackTrace();
[/code]
instead of
[code]
} catch(Exception e){
e.printStackTrace();
}
[/code]

yishai
Offline
Joined: 2003-11-16
Points: 0

+1, but I would limit the type of e to the common base class of those exceptions, rather than allowing untyped method calls just because the signatures match, or dynamically determining the common interfaces (since you can't catch an interface).

For a real world example of this:

[code]
try {
retDataObjectBase = (DataObjectBase) Class.forName(dataObjectBaseClassName).newInstance();
}
catch(ClassNotFoundException e) {
throw new OtherException(e);
}
catch(InstantiationException e) {
throw new OtherException(e);
}
catch(IllegalAccessException e) {
throw new OtherException(e);
}
[/code]

Arguably those classes should all extend a common ReflectionException, but since they don't, here we are.

Another thing which might help this situation is if RuntimeException didn't extend Exception, but I don't know if that is fixable at this point.

tackline
Offline
Joined: 2003-06-19
Points: 0

I've had a long stream of catch clauses. But only using reflection, and I can't think of ever having more than one in a single program. Had to be careful of the order to get a sufficiently long list (you can't catch a more specialised exception after a base). Where else would it be useful? It's not as if there is more than one line of code in each catch block (introduce a method if there is).

kirillcool
Offline
Joined: 2004-11-17
Points: 0

Why can't you define a common base class and catch it? As you say, you want to use only the common functionality...

For example, if
public class A extends Exception {
public void foo();
}

public class B extends Exception {
public void foo();
}

You wouldn't call "syntactic sugar" calling foo() either on A or on B. Even when these classes define the same signature, without the reflection you can't say that foo() is defined on both of them.

If, on the other hand you have base class C
public class C extends Exception {
public void foo();
}

public class A extends C {
public void bar1();
}

public class B extends C {
public void bar2();
}

Then you can catch C and use only foo(), as you wanted.

Of course, you didn't give a real example of Exception1, Exception2, Exception3. If they don't have nothing in common except Exception or Throwable, there will be no common methods except those that come from Exception or Throwable. If they inherit from the same (your) base class, just catch that class.

Kirill
http://jroller.com/page/kirillcool/Weblog?catname=/Java

cowwoc
Offline
Joined: 2003-08-24
Points: 0

Sorry guys, restricting this solution to exceptions that extend the same base class defeats the purpose of the RFE. If these exceptions all had a common base-class I would just be catching it in the first place already.

The point is that the compiler should allow me to do:

[code]
try
{
doSomething();
}
catch (IOException, IllegalAccessException e)
{
throw IllegalStateException(e.getCause());
}
[/code]

and validate that it is legal to do this for all possible varients of e, being IOException and IllegalAccessException. I'd like to see a comment from the javac guys as to how difficult this sort of thing would be, but personally I think it would be useful to a large number of Java developers.