Java 中文官方教程 2022 版(七)(3)https://developer.aliyun.com/article/1486322
优点 2:将错误传播到调用堆栈上
异常的第二个优点是能够将错误报告传播到方法的调用堆栈上。假设readFile
方法是主程序进行的一系列嵌套方法调用中的第四个方法:method1
调用method2
,method2
调用method3
,最终调用readFile
。
method1 { *call method2;* } method2 { *call method3;* } method3 { *call readFile;* }
假设method1
是唯一对readFile
中可能发生的错误感兴趣的方法。传统的错误通知技术会强制method2
和method3
将readFile
返回的错误代码传播到调用堆栈上,直到错误代码最终到达method1
——唯一对它们感兴趣的方法。
method1 { errorCodeType error; error = *call method2;* if (error) *doErrorProcessing;* else *proceed;* } errorCodeType method2 { errorCodeType error; error = call method3; if (error) return error; else *proceed;* } errorCodeType method3 { errorCodeType error; error = call readFile; if (error) return error; else *proceed;* }
请记住,Java 运行时环境会逆向搜索调用堆栈,以找到任何对处理特定异常感兴趣的方法。一个方法可以规避其内部抛出的任何异常,从而允许调用堆栈中更高层的方法捕获它。因此,只有关心错误的方法才需要担心检测错误。
method1 { try { *call method2;* } catch (*exception* e) { *doErrorProcessing;* } } method2 throws *exception* { *call method3;* } method3 throws exception { *call readFile;* }
然而,正如伪代码所示,规避异常需要中间方法付出一些努力。在方法内部可能抛出的任何已检查异常必须在其throws
子句中指定。
优势 3:分组和区分错误类型
因为程序中抛出的所有异常都是对象,异常的分组或分类是类层次结构的自然结果。Java 平台中一组相关的异常类的示例是java.io
中定义的那些 — IOException
及其后代。IOException
是最一般的,表示在执行 I/O 时可能发生的任何类型的错误。其后代表示更具体的错误。例如,FileNotFoundException
表示无法在磁盘上找到文件。
一个方法可以编写特定的处理程序,可以处理非常具体的异常。FileNotFoundException
类没有后代,因此以下处理程序只能处理一种类型的异常。
catch (FileNotFoundException e) { ... }
一个方法可以通过在catch
语句中指定任何异常的超类来基于其组或一般类型捕获异常。例如,为了捕获所有 I/O 异常,无论其具体类型如何,异常处理程序指定一个IOException
参数。
catch (IOException e) { ... }
这个处理程序将能够捕获所有 I/O 异常,包括FileNotFoundException
、EOFException
等。您可以通过查询传递给异常处理程序的参数来找到发生的详细信息。例如,使用以下内容打印堆栈跟踪。
catch (IOException e) { // Output goes to System.err. e.printStackTrace(); // Send trace to stdout. e.printStackTrace(System.out); }
你甚至可以设置一个异常处理程序,用于处理这里的任何Exception
。
// *A (too) general exception handler* catch (Exception e) { ... }
Exception
类接近Throwable
类层次结构的顶部。因此,此处理程序将捕获许多其他异常,除了处理程序打算捕获的异常之外。如果您只希望程序执行的操作是打印出用户的错误消息,然后退出,您可能希望以这种方式处理异常。
然而,在大多数情况下,您希望异常处理程序尽可能具体。原因是处理程序必须首先确定发生了什么类型的异常,然后才能决定最佳的恢复策略。实际上,通过不捕获特定错误,处理程序必须适应任何可能性。过于一般化的异常处理程序可能会使代码更容易出错,因为它会捕获和处理程序员未预料到的异常,而处理程序并不打算处理这些异常。
正如所指出的,您可以创建异常组并以一般方式处理异常,或者您可以使用特定的异常类型来区分异常并以精确方式处理异常。
摘要
原文:
docs.oracle.com/javase/tutorial/essential/exceptions/summary.html
程序可以使用异常来指示发生了错误。要抛出异常,请使用throw
语句,并提供一个异常对象 — 一个Throwable
的后代 — 以提供有关发生的具体错误的信息。抛出未捕获的已检查异常的方法必须在其声明中包含一个throws
子句。
程序可以通过使用try
、catch
和finally
块的组合来捕获异常。
try
块标识出可能发生异常的代码块。catch
块标识出一个代码块,称为异常处理程序,可以处理特定类型的异常。finally
块标识出保证执行的代码块,并且是关闭文件、恢复资源以及在try
块中封闭的代码之后进行清理的正确位置。
try
语句应至少包含一个catch
块或一个finally
块,并且可以有多个catch
块。
异常对象的类表示抛出的异常类型。异常对象可以包含有关错误的进一步信息,包括错误消息。通过异常链接,一个异常可以指向导致它的异常,后者又可以指向导致它的异常,依此类推。
问题和练习
原文:
docs.oracle.com/javase/tutorial/essential/exceptions/QandE/questions.html
问题
- 以下代码是否合法?
try { } finally { }
- 以下处理程序可以捕获哪些异常类型?
catch (Exception e) { }
- 使用这种类型的异常处理程序有什么问题?
- 以下异常处理程序的写法有什么问题?这段代码能编译吗?
try { } catch (Exception e) { } catch (ArithmeticException a) { }
- 将第一个列表中的每种情况与第二个列表中的一项进行匹配。
- `int[] A;
A[0] = 0;` - JVM 开始运行您的程序,但 JVM 找不到 Java 平台类。(Java 平台类位于
classes.zip
或rt.jar
中。) - 一个程序正在读取流并达到
流结束
标记。 - 在关闭流之前和达到
流结束
标记之后,一个程序尝试再次读取流。 - __ 错误
- __ 已检查异常
- __ 编译错误
- __ 无例外
- 练习
- 在
ListOfNumbers.java
中添加一个readList
方法。该方法应从文件中读取int
值,打印每个值,并将它们附加到向量的末尾。您应该捕获所有适当的错误。您还需要一个包含要读取的数字的文本文件。 - 修改以下
cat
方法以便能够编译。
public static void cat(File file) { RandomAccessFile input = null; String line = null; try { input = new RandomAccessFile(file, "r"); while ((line = input.readLine()) != null) { System.out.println(line); } return; } finally { if (input != null) { input.close(); } } }
- 检查您的答案。
课程:基本 I/O
本课程涵盖了用于基本 I/O 的 Java 平台类。它首先关注* I/O 流*,这是一个极大简化 I/O 操作的强大概念。该课程还涉及序列化,它允许程序将整个对象写入流并再次读取它们。然后课程将查看文件 I/O 和文件系统操作,包括随机访问文件。
大多数在“ I/O 流”部分涵盖的类位于java.io
包中。大多数在“文件 I/O”部分涵盖的类位于java.nio.file
包中。
I/O 流
- 字节流处理原始二进制数据的 I/O。
- 字符流处理字符数据的 I/O,自动处理与本地字符集之间的转换。
- 缓冲流通过减少对本机 API 的调用次数来优化输入和输出。
- 扫描和格式化允许程序读取和写入格式化文本。
- 从命令行进行 I/O 描述了标准流和控制台对象。
- 数据流处理基本数据类型和
String
值的二进制 I/O。 - 对象流处理对象的二进制 I/O。
文件 I/O(使用 NIO.2)
- 什么是路径?探讨了文件系统上路径的概念。
- Path 类介绍了
java.nio.file
包的基石类。 - 路径操作查看了
Path
类中处理语法操作的方法。 - 文件操作介绍了许多文件 I/O 方法共有的概念。
- 检查文件或目录展示了如何检查文件的存在性和可访问性级别。
- 删除文件或目录。
- 复制文件或目录。
- 移动文件或目录。
- 管理元数据解释了如何读取和设置文件属性。
- 读取、写入和创建文件展示了读取和写入文件的流和通道方法。
- 随机访问文件展示了如何以非顺序方式读取或写入文件。
- 创建和读取目录涵盖了特定于目录的 API,例如如何列出目录的内容。
- 链接,符号或其他涵盖了与符号链接和硬链接相关的特定问题。
- 遍历文件树演示了如何递归访问文件树中的每个文件和目录。
- 查找文件展示了如何使用模式匹配搜索文件。
- 监视目录变化展示了如何使用监视服务检测一个或多个目录中添加、删除或更新的文件。
- 其他有用的方法涵盖了在本课程中其他地方无法涵盖的重要 API。
- 旧版文件 I/O 代码展示了如何利用
Path
功能,如果你的旧代码使用了java.io.File
类。提供了一个将java.io.File
API 映射到java.nio.file
API 的表格。
总结
本教程涵盖的关键要点总结。
问题和练习
通过尝试这些问题和练习来测试你在本教程中学到的知识。
I/O 类的实际运用
下一个教程中的许多示例,自定义网络,使用了本课程中描述的 I/O 流来从网络连接读取和写入。
安全注意事项: 一些 I/O 操作需要当前安全管理器的批准。这些教程中包含的示例程序是独立应用程序,默认情况下没有安全管理器。要在小程序中运行,大多数这些示例都需要进行修改。查看小程序的能力和限制以获取有关小程序所受的安全限制的信息。
I/O 流
原文:
docs.oracle.com/javase/tutorial/essential/io/streams.html
I/O 流表示输入源或输出目的地。流可以表示许多不同类型的源和目的地,包括磁盘文件、设备、其他程序和内存数组。
流支持许多不同类型的数据,包括简单的字节、基本数据类型、本地化字符和对象。一些流只是传递数据;另一些以有用的方式操作和转换数据。
无论它们内部如何工作,所有流对使用它们的程序呈现相同简单的模型:流是一系列数据。程序使用输入流从源读取数据,一次读取一个项目:
将信息读入程序。
程序使用输出流向目的地写入数据,一次写入一个项目:
将信息从程序写入。
在本课程中,我们将看到可以处理从基本值到高级对象的各种数据的流。
上图中的数据源和数据目的地可以是任何保存、生成或消耗数据的东西。显然,这包括磁盘文件,但源或目的地也可以是另一个程序、外围设备、网络套接字或数组。
在下一节中,我们将使用最基本的流类型,字节流,来演示流 I/O 的常见操作。作为示例输入,我们将使用示例文件xanadu.txt
,其中包含以下诗句:
In Xanadu did Kubla Khan A stately pleasure-dome decree: Where Alph, the sacred river, ran Through caverns measureless to man Down to a sunless sea.
字节流
原文:
docs.oracle.com/javase/tutorial/essential/io/bytestreams.html
程序使用字节流来执行 8 位字节的输入和输出。所有字节流类都是从InputStream
和OutputStream
继承而来。
有许多字节流类。为了演示字节流的工作原理,我们将重点放在文件 I/O 字节流FileInputStream
和FileOutputStream
上。其他类型的字节流使用方式基本相同;它们主要在构造方式上有所不同。
使用字节流
我们将通过检查一个名为CopyBytes
的示例程序来探讨FileInputStream
和FileOutputStream
,该程序使用字节流逐字节复制xanadu.txt
。
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyBytes { public static void main(String[] args) throws IOException { FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("xanadu.txt"); out = new FileOutputStream("outagain.txt"); int c; while ((c = in.read()) != -1) { out.write(c); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } }
CopyBytes
在一个简单的循环中花费大部分时间,逐字节读取输入流并写入输出流,如下图所示。
简单的字节流输入和输出。
总是关闭流
当不再需要流时关闭流非常重要—非常重要,以至于CopyBytes
使用finally
块来确保即使发生错误,两个流也将被关闭。这种做法有助于避免严重的资源泄漏。
一个可能的错误是CopyBytes
无法打开一个或两个文件。当发生这种情况时,对应于文件的流变量从未从其初始的null
值更改。这就是为什么CopyBytes
确保每个流变量在调用close
之前包含一个对象引用。
何时不使用字节流
CopyBytes
看起来像一个普通程序,但实际上代表了一种应该避免的低级 I/O。由于xanadu.txt
包含字符数据,最好的方法是使用字符流,如下一节所讨论的。还有用于更复杂数据类型的流。字节流应该仅用于最基本的 I/O。
那么为什么要谈论字节流呢?因为所有其他流类型都是建立在字节流之上的。
字符流
原文:
docs.oracle.com/javase/tutorial/essential/io/charstreams.html
Java 平台使用 Unicode 约定存储字符值。字符流 I/O 会自动将内部格式与本地字符集进行转换。在西方区域,本地字符集通常是 ASCII 的 8 位超集。
对于大多数应用程序,使用字符流进行 I/O 与使用字节流进行 I/O 并无太大区别。使用流类进行的输入和输出会自动转换为本地字符集。使用字符流而不是字节流的程序会自动适应本地字符集,并且为国际化做好准备,而无需程序员额外努力。
如果国际化不是首要任务,您可以简单地使用字符流类,而不必过多关注字符集问题。稍后,如果国际化成为首要任务,您的程序可以在不进行大量重编码的情况下进行调整。查看国际化教程以获取更多信息。
使用字符流
所有字符流类都是从Reader
和Writer
继承而来。与字节流一样,有专门用于文件 I/O 的字符流类:FileReader
和FileWriter
。CopyCharacters
示例演示了这些类。
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyCharacters { public static void main(String[] args) throws IOException { FileReader inputStream = null; FileWriter outputStream = null; try { inputStream = new FileReader("xanadu.txt"); outputStream = new FileWriter("characteroutput.txt"); int c; while ((c = inputStream.read()) != -1) { outputStream.write(c); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } }
CopyCharacters
与CopyBytes
非常相似。最重要的区别在于,CopyCharacters
使用FileReader
和FileWriter
进行输入和输出,而不是FileInputStream
和FileOutputStream
。请注意,CopyBytes
和CopyCharacters
都使用一个int
变量进行读取和写入。但是,在CopyCharacters
中,int
变量在其最后 16 位中保存字符值;而在CopyBytes
中,int
变量在其最后 8 位中保存byte
值。
使用字节流的字符流
字符流通常是字节流的“包装器”。字符流使用字节流执行物理 I/O,而字符流处理字符和字节之间的转换。例如,FileReader
使用FileInputStream
,而FileWriter
使用FileOutputStream
。
有两个通用的字节到字符的“桥梁”流:InputStreamReader
和OutputStreamWriter
。当没有符合您需求的预打包字符流类时,请使用它们来创建字符流。网络教程中的套接字课程展示了如何从套接字类提供的字节流创建字符流。
面向行的 I/O
字符 I/O 通常以比单个字符更大的单位进行。一个常见的单位是行:一串带有行终止符的字符。行终止符可以是回车/换行序列("\r\n"
),单个回车("\r"
)或单个换行("\n"
)。支持所有可能的行终止符允许程序读取在任何广泛使用的操作系统上创建的文本文件。
让我们修改CopyCharacters
示例以使用面向行的 I/O。为此,我们必须使用两个以前未见过的类,BufferedReader
和PrintWriter
。我们将在缓冲 I/O 和格式化中更深入地探讨这些类。现在,我们只关注它们对面向行的 I/O 的支持。
CopyLines
示例调用BufferedReader.readLine
和PrintWriter.println
来逐行进行输入和输出。
import java.io.FileReader; import java.io.FileWriter; import java.io.BufferedReader; import java.io.PrintWriter; import java.io.IOException; public class CopyLines { public static void main(String[] args) throws IOException { BufferedReader inputStream = null; PrintWriter outputStream = null; try { inputStream = new BufferedReader(new FileReader("xanadu.txt")); outputStream = new PrintWriter(new FileWriter("characteroutput.txt")); String l; while ((l = inputStream.readLine()) != null) { outputStream.println(l); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } }
调用readLine
返回带有行的文本。CopyLines
使用println
输出每一行,该方法会附加当前操作系统的行终止符。这可能与输入文件中使用的行终止符不同。
除了字符和行之外,还有许多结构化文本输入和输出的方式。更多信息,请参见扫描和格式化。