20

“可能出错的事情和不可能出错的事情之间的主要区别在于,当一个不可能出错的事情出错时,通常结果是无法找到或修复。” -道格拉斯·亚当斯

我有一个类 FileItems。FileItems 构造函数接受一个文件,如果该文件不存在,则引发异常 (FileNotFoundException)。该类的其他方法也涉及文件操作,因此能够抛出 FileNotFoundException。我想找到一个更好的解决方案。不需要其他程序员处理所有这些极不可能的 FileNotFoundExceptions 的解决方案。

事情的真相:

  1. 该文件已被检查是否存在,但极不可能的可能性是,由于现实的某些重大故障,该文件可能会在调用此方法之前被删除。
  2. 由于 1 发生的概率非常不同且不可恢复,因此我更愿意定义一个未经检查的异常。
  3. 该文件已经被发现存在,强迫其他程序员编写代码并捕获检查的 FileNotFoundException 似乎乏味无用。该程序应该在那时完全失败。例如,计算机总是有可能着火,但没有人疯狂到强迫其他程序员将其作为检查异常处理
  4. 我不时遇到这种异常问题,每次遇到这个问题(我的旧解决方案)时定义自定义的未经检查的异常是很烦人的,并且会增加代码膨胀。

代码目前看起来像这样

 public Iterator getFileItemsIterator() {
    try{
        Scanner sc = new Scanner(this.fileWhichIsKnowToExist);
        return new specialFileItemsIterator(sc);        
       } catch (FileNotFoundException e){ //can never happen} 

    return null;
 }

在不定义自定义未检查 FileNotFoundException 的情况下,我怎样才能做得更好?有没有办法将checkedException转换为uncheckException?

4

8 回答 8

53

处理这种情况的常用模式是异常链接。您只需将 FileNotFoundException 包装在 RuntimeException 中:

catch(FileNotFoundException e) {
    throw new RuntimeException(e);
}

这种模式不仅适用于在特定情况下(比如你的)不能发生异常的情况,也适用于你没有办法或意图真正处理异常的情况(比如数据库链接失败)。

编辑:当心这种看起来相似的反模式,我在野外经常看到它:

catch(FileNotFoundException e) {
    throw new RuntimeException(e.getMessage());
}

通过这样做,您会丢弃原始堆栈跟踪中的所有重要信息,这通常会使问题难以追踪。

另一个编辑:正如 Thorbjørn Ravn Andersen 在他的回复中正确指出的那样,说明为什么你在评论中链接异常并没有什么坏处,或者更好的是,作为异常消息:

catch(FileNotFoundException e) {
    throw new RuntimeException(
        "This should never happen, I know this file exists", e);
}
于 2009-06-25T19:46:17.293 回答
12

您可以通过将已检查异常嵌套在 RuntimException 中来将已检查异常转换为未检查异常。如果异常在堆栈中的较高位置被捕获并使用 printStackTrace() 输出,则原始异常的堆栈也将被显示。

try {
    // code...
}
catch (IOException e) {
    throw new RuntimeException(e);
}

这是一个很好的解决方案,在这些情况下您应该毫不犹豫地使用它。

于 2009-06-25T19:45:28.747 回答
11

考虑使用表格

throw new RuntimeException("This should never happen", e);

反而。这使您可以向维护者传达关注您的意义,无论是在阅读代码时还是应该在某些奇怪的情况下碰巧抛出异常。

编辑:这也是通过不期望这些异常的机制传递异常的好方法。例如,如果您有一个“从数据库中获取更多行”迭代器,则 Iterator 接口不允许抛出例如 FileNotFoundException,因此您可以像这样包装它。在使用迭代器的代码中,您可以捕获运行时异常并使用 getCause() 检查原始异常。在通过遗留代码路径时非常有用。

于 2009-06-25T19:58:28.703 回答
8

如果您的立场是这不太可能并且应该结束程序,请使用现有的运行时异常,甚至 RuntimeException 本身(如果不是 IllegalStateException)。

try {
  ....

} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
}
于 2009-06-25T19:46:27.573 回答
1

您可能最好IOException在代码中涉及从文件读取或写入的任何位置抛出超类和更通用的异常。

当您的类的构造函数运行时,该文件可能存在,但这并不能保证:

  1. 调用方法时存在
  2. 它是可写/可读的
  3. 另一个线程无法访问它并以某种方式搞砸了您的流
  4. 资源不会在处理过程中消失

等等

java.io我不会重新发明轮子,而是说只要你使用的 JDK/类强制你这样做,就重新抛出 IOException 。

我也讨厌从构造函数中抛出异常的类——如果我是你,我会摆脱这些。

于 2009-06-25T19:47:08.943 回答
1

我做了一点谷歌搜索,发现了这段代码。我认为这是一种更灵活的方法

这篇文章的点赞

class SomeOtherException extends Exception {}

public class TurnOffChecking {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    WrapCheckedException wce = new WrapCheckedException();
    // You can call f() without a try block, and let
    // RuntimeExceptions go out of the method:
    wce.throwRuntimeException(3);
    // Or you can choose to catch exceptions:
    for(int i = 0; i < 4; i++)
      try {
        if(i < 3)
          wce.throwRuntimeException(i);
        else
          throw new SomeOtherException();
      } catch(SomeOtherException e) {
          System.out.println("SomeOtherException: " + e);
      } catch(RuntimeException re) {
        try {
          throw re.getCause();
        } catch(FileNotFoundException e) {
          System.out.println(
            "FileNotFoundException: " + e);
        } catch(IOException e) {
          System.out.println("IOException: " + e);
        } catch(Throwable e) {
          System.out.println("Throwable: " + e);
        }
      }
    monitor.expect(new String[] {
      "FileNotFoundException: " +
      "java.io.FileNotFoundException",
      "IOException: java.io.IOException",
      "Throwable: java.lang.RuntimeException: Where am I?",
      "SomeOtherException: SomeOtherException"
    });
  }
} ///:~
于 2009-06-25T19:57:08.300 回答
1

我也遇到过同样的问题。

在最简单的情况下,您通知用户错误并建议重复或取消操作。

在一般情况下,工作流是一系列操作(包括 I/O),其中每个操作“假定”前一个操作已成功。

我选择的方法是创建一个“回滚”操作列表。如果工作流成功,它们将被忽略。如果发生异常,我会执行回滚并向用户显示异常。

这:

  • 保持数据的完整性
  • 允许编码更容易

典型功能如下:

returntype func(blah-blah-blah, Transaction tr)
{
    // IO example
    Stream s = null;
    try
    {
        s = new FileStream(filename);
        tr.AddRollback(File.Delete(filename));
    }
    finally
    {
        if (s != null)
            s.close();
    }
}

典型用法是:

Transaction tr = new Transaction();
try
{
    DoAction1(blah1, tr);
    DoAction2(blah2, tr);
    //...
}
catch (Exception ex)
{
    tr.ExecuteRollbacks();
    // queue the exception message to the user along with a command to repeat all the actions above
}

这在现实世界中有点棘手,因为

  • 有时需要在执行动作之前获得一堆锁
  • 回滚代码本身应该是异常静默的(例如)

但是我已经习惯了这种方法,现在我的应用程序更稳定了。

于 2009-06-25T20:02:36.887 回答
1

RuntimeException当您遇到不太可能的错误情况时,不要只用 a 来处理整个应用程序。RuntimeException应该保留给程序员错误,并且IOException很可能不是由程序员错误引起的。

相反,将较低级别的异常封装在较高级别的异常中并重新抛出。然后在调用链上处理更高级别的异常。

例如:

class SomeClass {

  public void doActions() {
    try {
      someAction();
    } catch (HigherLevelException e) {
      notifyUser();
    }

    someOtherUnrelatedAction();
  }

  public void someAction() throws HigherLevelException {  
    try {
      // user lower-level abstraction to do our bidding
    } catch(LowerLevelException e) {
      throw new HigherLevelException(e);
    }
  }

  public void someOtherUnrelatedAction() {
    // does stuff
  }
}

引发异常的调用堆栈很可能正在您的应用程序中执行某些任务。RuntimeException而不是通过弄清楚在该任务期间发生问题时该怎么做来强制使整个应用程序崩溃。例如,您是否尝试保存文件?不要崩溃,而是通知用户有问题。

于 2014-04-15T22:04:45.303 回答