在Java中捕获异常

4

Java中有一些预定义的异常,如果抛出这些异常,说明发生了严重的问题,你最好改进你的代码,而不是在catch块中捕获它们(如果我理解正确的话)。但是我仍然发现许多程序中存在以下情况:

} catch (IOException e) {
     ...
} catch (FileNotFoundException e) {
     ....
}

我认为IOException和FileNotFoundException都属于不应该在catch块中捕获的异常。为什么有些人这样做呢?这样做是否更好?无论如何,Java编译器都会警告任何此类问题。谢谢。
7个回答

14

如果你确实能够处理IOExceptionFileNotFoundException,那么捕获它们并没有问题。这是最重要的部分 - 你是否真的能在异常出现时继续进行操作?有时你可以 - 例如在服务器的顶层,即使一个请求失败了也不意味着下一个不能继续进行。但在客户端应用程序中则较少见,尽管这很大程度上取决于具体情况。当你尝试进行批量导入时无法读取文件?好的,中止该操作,但不一定关闭整个进程...

诚然,你不应该这样将它们颠倒 - FileNotFoundException将被其继承的IOException所掩盖。幸运的是,编译器会严格阻止你这样做。


5
你展示的顺序,先捕获FileNotFoundException再捕获IOException是错误的。由于FileNotFoundException扩展了IOException,当抛出FileNotFoundException时,将使用第一个处理程序,并且第二个处理程序是无效代码。
我还没有尝试过,但如果这样编译,我会有点惊讶。像FindBugs这样的静态分析工具应该能够捕获此错误。
至于是否应该捕获FileNotFoundException,这取决于调用者。然而,我会说,FileNotFoundException通常可以以有意义的方式进行恢复 - 提示选择另一个文件,尝试备用位置 - 而不仅仅是记录错误或中止进程。

3
为了处理不同类型的异常,可以按照最具体的异常优先的原则进行捕捉。如果在catch块的开头使用更加宽泛的异常,那么会先执行这部分代码,然后进入finally块。
Jon是正确的,捕捉IOException的catch语句将捕捉所有类型的IOException和其子类型,由于FileNotFoundException是IOException的一种类型,因此它将永远不会进入第二个catch块。

3
Java有两种异常类型,分别是已检查异常和未检查异常。
已检查异常必须在catch块中处理,否则会导致编译错误。IOException就是一个已检查异常的例子,必须进行处理。具体处理方式取决于应用程序,但必须处理异常以使编译器满意。
未检查异常不需要被捕获。所有继承自RuntimeException的类都是未检查异常。NullPointerException或ArrayIndexOutOfBoundsException就是一个很好的例子。编译器不强制你捕获这些异常,但当程序运行时可能仍然会发生这些异常,导致程序崩溃。
值得一提的是,IOException可能出现在尝试打开不存在的文件时。最好处理这样的异常并优雅地恢复(向用户显示文件不存在的对话框,并重新显示打开文件对话框之类的操作),而不是让程序崩溃。

3

正如Jon所说,在许多情况下捕获这些异常是可以的。但你不应该捕获NullPointerException和ArrayIndexOutOfBoundsException这样的异常,因为它们表示你代码中存在错误。

Java有两种类型的异常:已检查异常和未检查异常(继承自RuntimeException的异常)。

已检查异常,例如IOException,通常用于无法通过编写更好的代码避免的不可预测情况。它们被标记为已检查的意味着编译器强制你编写考虑到异常情况的代码。例如,你必须考虑FileNotFoundException的可能性,因为你不能保证文件存在(有人可能在程序运行时移动了它)。IOException可能会发生,因为网络连接断开。即使只是通过允许异常向上传播以便调用代码处理,编译器也会强制你提供处理这些情况的策略。

另一方面,未经检查的异常最适合用于可以通过更改代码避免的事情。如果代码检查可能存在空引用的情况,则始终可以避免 NullPointerException。同样,如果您小心处理索引,就永远不会出现 ArrayIndexOutOfBoundsException。编译器不强制要求您处理这些情况,因为它们代表应该修复的错误。


3
我认为IOException和FileNotFoundException恰好是这种类型的异常。

不,它们实际上是“其他”类型的异常,超出了您的编程技能。无论您的编程水平有多好,编译器和库都会使您“意识到”可能会发生某些事情。

想象一下这种情况:

您创建了一个将数据保存到临时文件夹的应用程序。

一切都很顺利,您已经检查了文件夹是否存在,如果不存在,就自己创建它。

然后,您向该临时文件夹写入2 mb。

突然间,其他系统进程删除了您的临时文件夹,您无法再进行写入。

在代码中强制您检查此类异常,平台设计人员认为至少您知道了这一点。

Jon是正确的有时候你无能为力,可能在程序崩溃之前记录日志,这被认为是“处理异常”(处理不好,但至少处理了)

try { 
   .... 
} catch( IOException ioe ) { 
    logger.severe( 
        String.format("Got ioe while writting file %s. Data was acquired using id = %d, the message is: %s", 
             fileName, 
             idWhereDataCame,
             ioe.getMessage()) );
  throw ioe;
}

另一件事情是将异常"链接"起来以适应抽象层次。

可能你的应用程序有一个GUI界面,向用户显示IOException不会有任何意义,或者可能会导致安全漏洞。可以发送修改后的消息。

try { 
   .... 
} catch( IOException ioe ) { 
   throw new EndUserException("The operation you've requeste could not be completed, please contact your administrator" , ioe );
}    

EndUserException 可能会在 GUI 的某个地方被捕获,并以对话框消息的形式呈现给用户(而不是只是在他的眼中消失而没有更多信息)。当然,您无法恢复该 IOException,但至少您可以死得有风度:P

最后,客户端代码可以使用不同的实现,并且并非所有异常都有意义。

例如,请再次考虑第一个场景。同一“操作”可能有三种“插件”服务来执行数据保存。

a) Write the data  to a file.
b) Or, write to a db
c) Or write to a remote server. 

接口不应抛出异常:
java.io.IOException
java.sql.SQLException 

nor

java.net.UnknownHostException

但是实际上应该像这样:

my.application.DataNotSavedException

不同的实现将在正确的级别处理异常,并将其转换为适当的抽象:

客户端代码:

 DataSaver saver = DataServer.getSaverFor("someKeyIdString");

 try {
     saver.save( myData );
 } catch( DataNotSavedException dnse ) {

     // Oh well... .
     ShowEndUserError("Data could not be saved due to : " dnse.getMessage() );
 }

实现代码:

 class ServerSaver implements DataSaver { 
 ....
     public void save( Data data ) throws DataNotSavedException {
         // Connect the remore server.
         try { 
             Socket socket = new Socket( this.remoteServer, this.remotePort );
             OuputStream out = socket.getOut.... 

             ....
             ....
         } catch ( UnknownHostException uhe ) { 
            // Oops....
            throw new DataNotSavedException( uhe );
         }
    }
 }

FileSaver和DatabaseSaver会执行类似的操作。

所有这些都是Checked Exceptions,因为编译器让您检查它们。

何时使用其中之一(checked / unchecked):在这里

还有另外两种:在这里

最后,Runtime的更简单解释是:在这里


0

稍微偏离这个想法:也许在另一个领域,它更清晰

如果你前面的车突然停下来,你会怎么做。

停下来!

所以我们处理异常。

回到代码:

如果你需要的文件不可用,你该怎么办?

要么

  1. 备份。编译为资源,因为它是你程序的一部分。我不是开玩笑的。
  2. 如果它是用户提供的文件:告诉用户;这是他们的文件。
  3. 使用向用户发送消息中止程序,因为您的软件系统出现故障。

我认为没有第四个选项。

我们的C#/VC++同胞选择了未经检查的异常。许多“专家”认为已检查的异常很糟糕:我的观点是生活是困难的,克服它。 已检查的异常代表已知的故障模式:必须加以解决。 您的鱼骨图在正常操作时有一条直线,并在故障侧面分支。已检查的异常是预期的故障。

现在,一旦您开始处理运行时异常,那就变得有趣了。 Java程序可以在大多数情况下正常运行,即使其中的某些函数不起作用。 我的意思是它们会抛出空指针异常、数组边界错误、无效参数和堆空间不足等异常。这使得增量交付非常可行。

(如果您捕获运行时错误,请记录它们。否则,您永远不知道如何修复问题)


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