我可以在同一个catch子句中捕获多个Java异常吗?

856

在Java中,我想做类似于这样的事情:

try {
    ...     
} catch (/* code to catch IllegalArgumentException, SecurityException, 
            IllegalAccessException, and NoSuchFieldException at the same time */) {
   someCode();
}

...而不是:

try {
    ...     
} catch (IllegalArgumentException e) {
    someCode();
} catch (SecurityException e) {
    someCode();
} catch (IllegalAccessException e) {
    someCode();
} catch (NoSuchFieldException e) {
    someCode();
}

有没有办法做到这一点?

11个回答

1348

自Java 7开始,这就成为了可能。多重捕获块的语法如下:

try { 
  ...
} catch (IllegalArgumentException | SecurityException | IllegalAccessException |
            NoSuchFieldException e) { 
  someCode();
}

但是请注意,如果所有的异常都属于同一个类层次结构,您可以简单地捕获该基本异常类型。

还要注意,如果ExceptionBExceptionA继承(直接或间接),则无法在同一块中捕获ExceptionAExceptionB。编译器会报错:

Alternatives in a multi-catch statement cannot be related by subclassing
  Alternative ExceptionB is a subclass of alternative ExceptionA

解决方法是只在异常列表中包含祖先异常,因为它还会捕获子类异常。


94
为什么要重新定义“按位或”(|)运算符?为什么不使用逗号或更类似含义的运算符,比如“逻辑或”(||)? - ArtOfWarfare
14
也许他们认为,在已经为泛型的多重边界语法想出办法之后,这已经不再重要了。 - JimmyB
19
XOR符号(I)与OR(||)不同,A | B表示A或B中的一个,但不是两个都有;A || B表示A或B或两者都有。所以当存在例外时,它要么是exceptionA,要么是exceptionB,但不能同时存在两个。这就是为什么使用XOR符号而不是OR的原因,当放置两个异常时,你可以清楚地看到这一点,其中一个是另一个的子类型。 - user1512999
51
在Java中,按位异或运算符用符号^(脱字符号)表示,按位或运算符用符号|(竖线符号)表示。请参考以下链接了解更多信息:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html - Lewis Baumstark
10
值得一提的是,在多重捕获块中捕获的异常类型会被评估为最具体的共同父类。 - yanpas
显示剩余7条评论

119
在Java 7之前不太一样,但我会这样做:

Java 6及其之前版本:
try {
  //.....
} catch (Exception exc) {
  if (exc instanceof IllegalArgumentException || exc instanceof SecurityException || 
     exc instanceof IllegalAccessException || exc instanceof NoSuchFieldException ) {
    
     someCode();

  } else if (exc instanceof RuntimeException) {
     throw (RuntimeException) exc;     

  } else {
    throw new RuntimeException(exc);
  }

}

try {
  //.....
} catch ( IllegalArgumentException | SecurityException |
         IllegalAccessException| NoSuchFieldException exc) {
  someCode();
}

13
请注意,你的Java 6示例会破坏编译器判断异常抛出位置的能力。 - MichaelBlume
3
@MichaelBlume说得对,这并不[那么]糟糕。您始终可以使用exc.getCause()获取原始异常。值得一提的是,Robert C. Martin(以及其他人)建议使用未经检查的异常(编译器不知道从那里抛出了什么类型的异常)。请参考他的书籍《Clean code》中的第7章“错误处理”。 - user454322
4
在您的Java 6示例中,您应该重新抛出原始异常而不是创建新的异常实例,即使用throw exc而不是throw new RuntimeException(exc),请问这样理解是否正确? - David DeMar
5
从易读性的角度来看,这是相当糟糕的做法。 - Rajesh J Advani
3
Instanceof操作的成本比较高,尽可能避免使用。 - Paramesh Korrakuti
显示剩余4条评论

31
不,Java 7之前每个客户只能有一个。只要在所有情况下采取相同的操作,就可以捕获超类(例如java.lang.Exception)。
try {
    // some code
} catch(Exception e) { //All exceptions are caught here as all are inheriting java.lang.Exception
    e.printStackTrace();
}

但这可能不是最佳实践。只有在您有处理异常的策略时才应该捕获它 - 记录和重新抛出并不是“处理它”的意思。如果您没有纠正措施,最好将其添加到方法签名中,让它向上冒泡到可以处理情况的人手中。

使用 JDK 7 及更高版本,您可以这样做:

try {
    ...     
} catch (IllegalArgumentException | SecurityException | IllegalAccessException | NoSuchFieldException e) {
    someCode();
}

23
我能请您重新表述有关捕获 java.lang.Exception 部分的内容吗?我知道这只是一个例子,但我觉得有些人可能会读到这个回答然后说:“哦,好的,那我就捕获 Exception。”,而这可能不是他们想要做的(或者应该做的)。 - Rob Hruska
2
我知道那个,但我不想这样做...哦,好吧,看来我只能用4个catch了,直到Java的下一个版本... - froadie
6
我认为仅记录和重新抛出异常并不能算作异常处理。我更愿意将其传递给能够做出有意义响应的人处理。在最后一层中,异常不应该被传递出去(例如 Web 应用程序中的控制器),在这种情况下应该记录错误日志。 - duffymo
我是不是唯一一个觉得没有自动生成日志很荒谬的人?每当一些代码可能会抛出异常时,似乎我们都必须编写相同愚蠢的日志记录信息。 - ArtOfWarfare
1
日志记录是AOP的“hello world”。如果这很麻烦,那就编写一个切面,一劳永逸。 - duffymo
显示剩余4条评论

26

在Java 7中,您可以定义多个catch子句,例如:

catch (IllegalArgumentException | SecurityException e)
{
    ...
}

21

如果有异常的层次结构,你可以使用基类来捕获所有子类异常。在极端情况下,你可以使用以下方式来捕获所有Java异常:

若存在异常层级,则可使用基类来捕获其所有子类异常。在特殊情况下,你可以使用以下方法来捕获所有Java异常:

try {
   ...
} catch (Exception e) {
   someCode();
}

如果在一种更常见的情况下,RepositoryException是基类,而PathNotFoundException是派生类,则:

try {
   ...
} catch (RepositoryException re) {
   someCode();
} catch (Exception e) {
   someCode();
}

上述代码将捕获RepositoryException和PathNotFoundException用于一种异常处理方式,而其他所有异常都被归为一类。 自Java 7起,根据@OscarRyz上面的答案:

try { 
  ...
} catch( IOException | SQLException ex ) { 
  ...
}

7
顺便提一句,捕获异常的代码块是按顺序处理的,所以如果您在子类之前放置了一个父类异常,那么它永远不会被调用。例如:尝试执行某些操作 如果出现异常,捕获Exception类型的异常并执行someCode()函数 如果异常类型是RepositoryException,则这个代码块将永远不会执行。 - Michael Shopsin
4
因为这种代码永远无法达到,所以它甚至无法编译。 - polygenelubricants

11

对于 Java 6(也就是 Android)上 user454322 的回答而言,有一个更加简洁但不太常用的替代方案,即捕获所有 Exception 并重新抛出 RuntimeException。如果您计划在堆栈上方进一步捕获其他类型的异常(除非您也重新抛出它们),否则这种方法将无法工作,但可以有效地捕获所有 已检查 异常。

例如:

try {
    // CODE THAT THROWS EXCEPTION
} catch (Exception e) {
    if (e instanceof RuntimeException) {
        // this exception was not expected, so re-throw it
        throw e;
    } else {
        // YOUR CODE FOR ALL CHECKED EXCEPTIONS
    } 
}

话虽如此,为了更冗长,可能最好设置一个布尔变量或其他变量,并基于它在try-catch块之后执行一些代码。


1
这种方法防止编译器确定“catch块”是否可达。 - the_new_mr

4

在Pre-7版本中如何处理:

  Boolean   caught = true;
  Exception e;
  try {
     ...
     caught = false;
  } catch (TransformerException te) {
     e = te;
  } catch (SocketException se) {
     e = se;
  } catch (IOException ie) {
     e = ie;
  }
  if (caught) {
     someCode(); // You can reference Exception e here.
  }

4
caught的最终控制放在finally块里会是一个不错的解决方案。 - Andrea_86
这需要比原问题更多的代码行。 - Leandro Glossman

4

这很简单:

try { 
  // Your code here.
} catch (IllegalArgumentException | SecurityException | IllegalAccessException |
            NoSuchFieldException e) { 
  // Handle exception here.
}

4

对于Kotlin来说,目前还不支持,但是已经考虑添加: 来源
但是现在只有一个小技巧:

try {
    // code
} catch(ex:Exception) {
    when(ex) {
        is SomeException,
        is AnotherException -> {
            // handle
        }
        else -> throw ex
    }
}

0
捕获在异常层次结构中作为父类的异常。 当然,这是不好的实践。在您的情况下,常见的父级异常是Exception类,捕获任何Exception实例的异常都是不好的实践 - 像NullPointerException这样的异常通常是编程错误,并且通常应通过检查null值来解决。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接