首页 > 解决方案 > 这段代码中的异常处理得好吗?是否缺少关闭文件对象的 finally 块?

问题描述

void appendFile() throws IOException{
    
    
    FileWriter print = new FileWriter(f, true);
    
    
    String info = GetData.getWord("Write desired info to File");
    print.append(" "); //Write Data
    print.append(info);
    
    System.out.println("this executes");
    print.flush();
    print.close();
      
}
boolean fileExist(){
    return f.exists();
    
}
try{
        if (f.fileExist())
            f.appendFile();
        else {
            throw new IOException();
        }
        
    }
    catch(IOException e) {
        e.printStackTrace();
    }
    

我不确定 ecxeption 是否处理得很好?如果存在 fileNotFoundException,则不会创建 FileWriter,因此不需要关闭。但是,此代码是否有可能在打开文件后引发另一种 IOException?在这种情况下,我是否需要 finally 块来关闭文件。

标签: javaioexception

解决方案


不。

它不会安全地关闭资源

一般规则是,如果您调用表示可关闭资源的对象的构造函数,或者调用返回的方法,该方法记录了这被视为“打开资源”,这通常但不总是包括new作为方法名称的一部分(示例:socket.getInputStream(), Files.newInputStream),那么您有责任关闭它。

问题是,如果发生异常怎么办?所以,你必须使用 try/finally,除了那是一口,所以有一个方便的语法。

appendFile 方法应该使用它;不是,这使它成为糟糕的代码。这是对的:

try (FileWriter print = new FileWriter(f, true)) {
    String info = GetData.getWord("Write desired info to File");
    print.append(" "); //Write Data
    print.append(info);
    System.out.println("this executes");
}

不是在关闭之前不需要刷新(close暗示flush),在这种情况下,没有必要close()- try 构造为您完成。如果您退出via语句、控制流 ( ) 或异常,或者只是运行到并正常退出,它也会{}为您执行此操作。无论如何 - 资源已关闭。应该如此。tryreturnbreak}

它抛出无描述的异常

else throw new IOException();不好;添加解释异常发生原因的描述。throw new IOException("File not found")更好,但throw new FileNotFoundException(f.toString())更好:消息应该传达有用的信息,仅此而已(换句话说,throw new IOException("Something went wrong")很糟糕,不要那样做,该消息没有添加有用的信息),不应以标点符号结尾(throw new IOException("File not found!")不好) , 并且应该抛出一个最合适的类型(如果文件不存在,显然FileNotFoundException,它是 , 的子类型IOException更合适)。

它犯了死罪

你永远不应该写一个内容只是e.printStackTrace();. 这总是错误的。

这是您对已检查异常执行的操作:

  1. 首先,考虑异常的含义以及您的方法的性质是否固有地暗示可能会发生此异常(相对于它是一个实现细节)。在这种情况下,您没有向我们展示包含该 try/catch 内容的方法甚至做了什么。但是假设它被称为appendFile显然包含文本“文件”的方法会执行 I/O,因此,该方法应该使用throws IOException. appendFile名为与文件交互的方法不是实现细节。这是它的本性。

这在旁​​观者的眼中有点。想象一个名为saveGame. 这不太清楚。也许保存机制可能涉及数据库,在这种情况下SQLException是正常的。这是一个方法示例,其中“它与文件系统交互”是一个实现细节。

  1. 异常信号的问题是合乎逻辑的,但需要更抽象。

见上文:保存文件系统显然无法保存,但错误的确切性质是抽象的:如果保存文件系统是基于 DB 的,错误将以 ; 的形式显示SQLException。如果是文件系统IOException,等等。但是,保存可能会失败,并且尝试保存的代码有合理的机会可以从中恢复的想法是显而易见的。如果是游戏,则有用户界面;您绝对应该告诉玩家保存失败,而不是将一些堆栈跟踪分流到他们可能从未看过的 sysout 中!告诉用户某事失败是处理事情的一种方式,并不总是最好的,但在这里它适用)。

在这种情况下,使用包装器构造函数创建自己的异常类型并抛出它:

public class SaveException extends Exception {
  public SaveException(Throwable cause) {
    super(cause);
  }
}

// and to use:

public void save() throws SaveException {
  try {
    try (OutputStream out = Files.newOutputStream(savePath)) {
      game.saveTo(out);
    }
  } catch (IOException e) {
    throw new SaveException(e);
  }
}
  1. 如果两者都不适用,那么异常可能本质上是不可处理的或不可预期的,或者几乎总是一个错误。例如,写入您知道是 a 的输出流ByteArrayOutputStream(不能抛出),尝试加载UTF-8字符集(由 JVM 规范保证,因此不可能 throw NoSuchCharsetException) - 这些都是不可预料的。可能Pattern.compile("Some-regexp-here")会失败(并非所有字符串都是有效的正则表达式),但是由于 Java 中的绝大多数正则表达式都是程序员编写的文字,因此它们中的任何错误都必然是一个错误。这些也可以作为 RuntimeExceptions 正确完成(您不必在throws线)。不可处理的东西主要是应用程序逻辑级别的东西。运行时异常的所有公平游戏。自己制作或使用适用的东西:
public void save(int saveSlot) {
  if (saveSlot < 1 || saveSlot > 9) throw new IllegalArgumentException("Choose a saveslot from 1 to 9");
  // ... rest of code.
}

这真的感觉像是一号门:无论采用哪种方法,都可能需要声明为throws IOException并且根本不进行捕获或尝试。

次要问题:使用旧 API

包中有用于文件内容的新 API java.nio.file。它“更好”,因为旧 API 做了很多坏事,例如通过boolean标志返回失败而不是正确地(通过抛出异常),并且新 API 对各种位和文件系统的功能,例如对文件链接和创建时间戳的支持。


推荐阅读